// Copyright (c) 1998-1999 Microsoft Corporation /****************************************************************************** * * WSTUTIL.C * * Various useful utilities for dealing with multi-user WinStations and User * accounts that are useful across a range of utilities and apps. * * * *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * RefreshAllCaches * * Invalidate any caches maintained by the UTILSUB.DLL * * This does not need to be called for utilities that exit when done, but * are for server, or monitoring type programs that need to periodicly * see the latest system information. * IE: A new user could have logged onto a given winstation since the last * call. * * * Exit * * Any caches in the UTILSUB.DLL have been invalidated insuring fresh * system information on future calls. * */ VOID WINAPI RefreshAllCaches() { RefreshWinStationCaches(); RefreshProcessObjectCaches(); } /* * RefreshWinStationCaches * * Invalidate any caches maintained by the WinStation helper utilities. * * This does not need to be called for utilities that exit when done, but * are for server, or monitoring type programs that need to periodicly * see the latest system information. * IE: A new user could have logged onto a given winstation since the last * call. * * * Exit * * Makes sure that any WinStation helper utility calls will return the * system information at least up to date as the time that this call * was made. * */ VOID WINAPI RefreshWinStationCaches() { RefreshWinStationObjectCache(); RefreshWinStationNameCache(); } /* * GetCurrentLogonId * * Gets the WinStation ID for the current processes WinStation * * Exit * * ID of the current processes WinStation * */ ULONG WINAPI GetCurrentLogonId() { return( NtCurrentPeb()->SessionId ); } /* * GetCurrentWinStationName * * Get the current UNICODE name for the WinStation for this process * * Input: * * pName - Pointer to wide character buffer for name * * MaxSize - Maximum number of characters in buffer (including terminator). * * pName - Pointer to wide character buffer for name * * Output: * */ VOID WINAPI GetCurrentWinStationName( PWCHAR pName, int MaxSize ) { GetWinStationNameFromId( NtCurrentPeb()->SessionId, pName, MaxSize ); } /* * This is the cache maintained by the GetWinStationNameFromId function * * It is thread safe through the use of WLock. */ typedef struct TAGWINSTATIONLIST { struct TAGWINSTATIONLIST *Next; LOGONID LogonId; } WINSTATIONLIST, *PWINSTATIONLIST; static PWINSTATIONLIST pWList = NULL; static RTL_CRITICAL_SECTION WLock; static BOOLEAN WLockInited = FALSE; /*************************************************************************** * * InitWLock * * Since we do not require the user to call an initialize function, * we must initialize our critical section in a thread safe manner. * * The problem is, a critical section is needed to guard against multiple * threads trying to init the critical section at the same time. * * The solution that Nt uses, in which RtlInitializeCriticalSection itself * uses, is to wait on a kernel supported process wide Mutant before proceding. * This Mutant almost works by itself, but RtlInitializeCriticalSection does * not wait on it until after trashing the semaphore count. So we wait on * it ourselves, since it can be acquired recursively. * ***************************************************************************/ NTSTATUS InitWLock() { NTSTATUS status = STATUS_SUCCESS; RtlEnterCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock); /* * Make sure another thread did not beat us here */ if( WLockInited == FALSE ){ status = RtlInitializeCriticalSection( &WLock ); if (status == STATUS_SUCCESS) { WLockInited = TRUE; } } RtlLeaveCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock); return status; } /*************************************************************************** * * RefreshWinStationNameCache * * Invalidate the WinStation Name cache so that the newest information * will be fetched from the system. * ***************************************************************************/ VOID WINAPI RefreshWinStationNameCache( ) { NTSTATUS status = STATUS_SUCCESS; PWINSTATIONLIST pEntry, pNext; if( pWList == NULL ) return; /* * Make sure critical section has been inited */ if( !WLockInited ) { status = InitWLock(); } if (status == STATUS_SUCCESS) { RtlEnterCriticalSection( &WLock ); pEntry = pWList; while( pEntry ) { pNext = pEntry->Next; free( pEntry ); pEntry = pNext; } pWList = NULL; RtlLeaveCriticalSection( &WLock ); } } /* * GetWinStationNameFromId * * Attempt to retrieve the WinStationName for the given LogonId. * * Parameters: * * LogonId (input) * Unique LogonId * * pName (output) * Pointer to buffer for name * * MaxSize (input) * Maximum number of characters in buffer (including terminator). * * Returns * TRUE if name was retreived, FALSE otherwise. * */ BOOLEAN WINAPI GetWinStationNameFromId( ULONG LogonId, PWCHAR pName, int MaxSize ) { return xxxGetWinStationNameFromId( SERVERNAME_CURRENT , LogonId , pName , MaxSize ); } /*---------------------------------------------------------------------------------------- /* * xxxGetWinStationNameFromId * * Attempt to retrieve the WinStationName for the given LogonId. * * Parameters: * * hServer ( input ) * rpc handle to termsrv * * LogonId (input) * Unique LogonId * * pName (output) * Pointer to buffer for name * * MaxSize (input) * Maximum number of characters in buffer (including terminator). * * Returns * TRUE if name was retreived, FALSE otherwise. * */ BOOLEAN WINAPI xxxGetWinStationNameFromId( HANDLE hServer , ULONG LogonId, PWCHAR pName, int MaxSize ) { NTSTATUS status = STATUS_SUCCESS; PLOGONID pIdBase, pId; int rc; ULONG Count; PWINSTATIONLIST pEntryBase, pEntry; // Since We do not have a WinStationNamefromId Sm Api like we do for // LogonIdfromName, we will perform a WinStationEnumerate function across // all WinStations known by the Session Manager, and store them in a locally // maintained list. We do this so we that this search against the session // manager is not done every time we're called. // // Another alternative that was tested is to open the WinStation itself // and then do a WinStationQueryInformation against it in order to // retrieve its name from itself. This is much slower because we must // set up and tear down an LPC connection to each WinStation, as opposed // to the one connection we get to the session manager. /* * Make sure critical section has been inited */ if( !WLockInited ) { status = InitWLock(); } if (status == STATUS_SUCCESS) { RtlEnterCriticalSection( &WLock ); // Initialize the list the first time if( pWList == NULL ) { rc = WinStationEnumerate( hServer, &pIdBase, &Count ); if( rc ) { /* * Allocate an Entry for each enumerated winstation. */ pEntryBase = (PWINSTATIONLIST)malloc( Count * sizeof(WINSTATIONLIST) ); if( pEntryBase == NULL ) { pWList = NULL; // We are having severe problems SetLastError( ERROR_NOT_ENOUGH_MEMORY ); WinStationFreeMemory(pIdBase); RtlLeaveCriticalSection( &WLock ); return( FALSE ); } /* * Load up Entries. */ for ( pEntry = pEntryBase, pId = pIdBase; Count ; Count--, pEntry++, pId++ ) { pEntry->LogonId = *pId; pEntry->Next = pWList; pWList = pEntry; } /* * Free enumerate buffer. */ WinStationFreeMemory(pIdBase); } } // End if pWList == NULL pEntry = pWList; while ( pEntry ) { if( pEntry->LogonId.LogonId == LogonId ) { wcsncpy( pName, pEntry->LogonId.WinStationName, MaxSize-1 ); pName[MaxSize-1] = 0; RtlLeaveCriticalSection( &WLock ); return( TRUE ); } pEntry = pEntry->Next; } RtlLeaveCriticalSection( &WLock ); } // If we can not find its name, print its ID # wsprintf( pName, L"ID %d", LogonId ); return( TRUE ); } /*---------------------------------------------------------------------------------------- /* * GetCurrentUserName * * Get the current UNICODE name for the logon USER for this process * * Input: * * pName - Pointer to wide character buffer for name * * MaxSize - Maximum number of characters in buffer (including terminator) * * * Output: * */ VOID WINAPI GetCurrentUserName( PWCHAR pName, int MaxSize ) { /* * The quickest way appears to open the current processes WinStation and * get the name from it. The other way would be to open the process, then * its token, extract the SID, then lookup the SID in the SAM database. * We have conviently stored the user name in the WinStation at Logon * time, so we'll use that. */ GetWinStationUserName( SERVERNAME_CURRENT , LOGONID_CURRENT, pName, MaxSize ); return; } /* * GetWinStationUserName * * Get the UNICODE name for the USER for the winstation * * Input: * * hServer - handle to termsrv * * LogonId - integer identifier for WinStation * * pName - Pointer to wide character buffer for name * * MaxSize - Maximum number of characters in buffer (including terminator) * * * Output: * */ BOOLEAN WINAPI GetWinStationUserName( HANDLE hServer , ULONG LogonId, PWCHAR pName, int MaxSize ) { BOOLEAN rc; ULONG ReturnLength; WINSTATIONINFORMATION Info; if( MaxSize == 0) return( FALSE ); memset( &Info, 0, sizeof(WINSTATIONINFORMATION) ); rc = WinStationQueryInformation( hServer, LogonId, WinStationInformation, (PVOID)&Info, sizeof(WINSTATIONINFORMATION), &ReturnLength); if(!rc){ pName[0] = 0; return( FALSE ); } if(ReturnLength != sizeof(WINSTATIONINFORMATION)) { pName[0] = 0; // Version mismatch return( FALSE ); } /* * Now copy the name out */ if( MaxSize > USERNAME_LENGTH ) { MaxSize = USERNAME_LENGTH; } wcsncpy( pName, Info.UserName, MaxSize-1 ); pName[MaxSize-1] = 0; // insure null termination if string is truncated return( TRUE ); } /* * These variables maintain a one entry cache so that we * do not have to keep querying the winstation (causes an LPC) * each time called. */ static ULONG CachedId = (ULONG)(-1); static WCHAR CachedUserName[USERNAME_LENGTH]; /************************************************************************** * * RefreshWinStationObjectCache * * Flush the cache for the WinStationObject name comparision function. * **************************************************************************/ VOID WINAPI RefreshWinStationObjectCache() { CachedId = (ULONG)(-1); CachedUserName[0] = 0; } /* * WinStationObjectMatch * * General Name match function against a WinStation. * * The admin utilities can take a user name, winstation name, or * a winstation id as an argument to a command that targets a winstation * for some action (send a message, query status, reset, etc.) * * This function does general compares of the supplied name to see if it * applies to the given winstation because the name represents the logged * on user of the winstation, the winstations system name when attached, or * the winstations unique id. * * * NOTE: The caching for this function assumes typical use of comparing this * winstation against a list of names across multiple calls. * It does not optimize for comparing one name at a time across all * winstation(s) in succession. * * Parameters: * * hServer ( input ) remote termsrv * * Id (input) WinStation Id for do the match against * * pName (input) UNICODE name for match testing */ BOOLEAN WINAPI WinStationObjectMatch( HANDLE hServer , PLOGONID Id, PWCHAR pName ) { ULONG tmp; /* * Handle the wild card case */ if( pName[0] == L'*' ) { return( TRUE ); } /* * See if the supplied name is the name assigned to the WinStation */ if( !_wcsnicmp( pName, Id->WinStationName, WINSTATIONNAME_LENGTH ) ) { return( TRUE ); } /* * See if it represents the numerical id for the winstation */ if( iswdigit( pName[0] ) ) { tmp = (ULONG)wcstol( pName, NULL, 10 ); if( tmp == Id->LogonId ) { return( TRUE ); } } /* * Else extract the logged on user name from the winstation itself * and compare this. */ if( CachedId == Id->LogonId ) { if( !_wcsnicmp( CachedUserName, pName, USERNAME_LENGTH ) ) { return( TRUE ); } } if ( Id->State == State_Down ) return( FALSE ); if( GetWinStationUserName( hServer , Id->LogonId, CachedUserName, USERNAME_LENGTH ) ) { CachedId = Id->LogonId; } else { CachedId = (ULONG)(-1); // In case name was trashed return( FALSE ); } if( !_wcsnicmp( CachedUserName, pName, USERNAME_LENGTH ) ) { return( TRUE ); } return( FALSE ); }