557 lines
14 KiB
C
557 lines
14 KiB
C
// 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 <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <windows.h>
|
|
#include <ntddkbd.h>
|
|
#include <ntddmou.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <process.h>
|
|
#include <string.h>
|
|
#include <malloc.h>
|
|
|
|
#include <winstaw.h>
|
|
#include <utilsub.h>
|
|
|
|
/*
|
|
* 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 );
|
|
}
|
|
|