windows-nt/Source/XPSP1/NT/termsrv/admtools/utilsub/procutil.c
2020-09-26 16:20:57 +08:00

517 lines
14 KiB
C

// Copyright (c) 1998-1999 Microsoft Corporation
/******************************************************************************
*
* PROCUTIL.C
*
* Various useful utilities for dealing with processes
* 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>
// from helpers.c
BOOL GetResourceStringFromUtilDll(UINT uID, LPTSTR szBuffer, int iBufferSize);
void ErrorOutFromResource(UINT uiStringResource, ...);
#include "utilsubres.h" // resources refrenced in this file.
/*
* Local function prototypes.
*/
VOID LookupSidUser( PSID pSid, PWCHAR pUserName, PULONG pcbUserName );
/*
* RefreshProcessObjectCaches()
*
* Refresh (invalidate) any caches that may be used by process object
* utilities.
*
* This is currently a place holder, but is here so that utilities can call
* it, thus being isolated from any future decisions to add caching.
*/
VOID WINAPI
RefreshProcessObjectCaches()
{
RefreshUserSidCrcCache();
}
/******************************************************************************
*
* ProcessObjectMatch
*
* General Name match function against a process.
*
* The multi-user admin utilities can take a user name, winstation name,
* a winstation id, or process id as an argument to a command that targets
* a process for some action (query status, kill, etc.)
*
* This function does general compares of the supplied name to see if it
* applies to the given process because the name represents the NT user
* account, a winstations system name, the winstations unique id, or the
* processes unique id.
*
* The various information about a process is supplied by the caller. Because
* of the way processes are enumerated from the NT system, it is easier
* and faster for the caller to supply this information than for the routine
* to retrieve it itself. This could be folded into a general EnumerateProcess()
* if needed. Currently this routine serves the purpose of having one unified
* way of handling process objects across all utilities.
*
*
* Matching:
*
* An integer number is assumed to be an NT process ID unless NumberIsLogonId
* is set, which then says to treat it as a LogonId.
*
* A name starting with a character is tested first as a winstation name, then
* as a user name, finally as a program image name. A user or group name
* could stand alone, or be preceded by a '\' to be [somewhat] compatible
* with the OS/2 product.
*
* Parameters:
*
* Pid (input)
* Windows NT unique process identifier
* LogonId (input)
* Logon (also called Session) ID the process is executing on.
* NumberIsLogonId (input)
* Treat a number in pMatchName as a LogonId not an PID number.
* pMatchName (input)
* Name for match testing
* pWinStationName (input)
* Name of WinStation for process.
* pUserName (input)
* Name of User for process.
* pImageName (input)
* Image name of executing program for process.
*
*****************************************************************************/
BOOLEAN WINAPI
ProcessObjectMatch( HANDLE Pid,
ULONG LogonId,
int NumberIsLogonId,
PWCHAR pMatchName,
PWCHAR pWinStationName,
PWCHAR pUserName,
PWCHAR pImageName )
{
ULONG tmp;
HANDLE htmp;
/*
* Check for wild card
*/
if( pMatchName[0] == L'*' ) return( TRUE );
/*
* If someone puts a '\' in front of pMatchName, strip it off
*/
if( pMatchName[0] == L'\\' ) pMatchName++;
/*
* First, if the match name is a number, check for == to process ID or
* LogonId.
*/
if( iswdigit( pMatchName[0] ) ) {
tmp = wcstol( pMatchName, NULL, 10 );
htmp = LongToPtr (tmp);
if( NumberIsLogonId && (tmp == LogonId) )
return( TRUE );
else if( htmp == Pid )
return( TRUE );
else
return( FALSE );
}
/*
* Then, check the match name against the WinStation Name of the process.
*/
if ( !_wcsicmp( pWinStationName, pMatchName ) ) {
return( TRUE );
}
/*
* Then, check the match name against the UserName of the process.
*/
if( !_wcsicmp( pUserName, pMatchName ) ) {
return( TRUE );
}
/*
* Finally, check the match name against the image name of the process.
*/
if( !_wcsicmp( pImageName, pMatchName ) ) {
return(TRUE);
}
return( FALSE );
}
/*
* This is the cache maintained by the GetUserNameFromSid function
*
* It is thread safe through the use of ULock.
*/
typedef struct TAGUSERSIDLIST {
struct TAGUSERSIDLIST *Next;
USHORT SidCrc;
WCHAR UserName[USERNAME_LENGTH];
} USERSIDLIST, *PUSERSIDLIST;
static PUSERSIDLIST pUList = NULL;
static RTL_CRITICAL_SECTION ULock;
static BOOLEAN ULockInited = FALSE;
/***************************************************************************
*
* InitULock
*
* 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 InitULock()
{
NTSTATUS status = STATUS_SUCCESS;
RtlEnterCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock);
/*
* Make sure another thread did not beat us here
*/
if( ULockInited == FALSE ){
status = RtlInitializeCriticalSection( &ULock );
if (status == STATUS_SUCCESS) {
ULockInited = TRUE;
}
}
RtlLeaveCriticalSection((PRTL_CRITICAL_SECTION)NtCurrentPeb()->LoaderLock);
return status;
}
/***************************************************************************
*
* RefreshUserSidCrcCache
*
* Invalidate the User/SidCrc cache so that the newest information
* will be fetched from the system.
*
***************************************************************************/
VOID WINAPI
RefreshUserSidCrcCache( )
{
NTSTATUS status = STATUS_SUCCESS;
PUSERSIDLIST pEntry, pNext;
if( pUList == NULL ) return;
/*
* Make sure critical section has been inited
*/
if( !ULockInited ) {
status = InitULock();
}
if (status == STATUS_SUCCESS) {
RtlEnterCriticalSection( &ULock );
pEntry = pUList;
while( pEntry ) {
pNext = pEntry->Next;
free( pEntry );
pEntry = pNext;
}
pUList = NULL;
RtlLeaveCriticalSection( &ULock );
}
}
/******************************************************************************
*
* GetUserNameFromSid
*
* Attempts to retrieve the user (login) name of the process by first looking
* in our User/SidCrc cache table, then (if no match) looking up the SID in
* the SAM database and adding the new entry to the User/SidCrc table.
*
* Input
*
* IN pUserSid Sid pointer
*
* OUT NameBuf WCHAR pointer to buffer for name
*
* IN/OUT pBufSize PULONG NameBuf size
*
* Will always return a user name, which will be "(unknown)" if the SID is
* invalid or can't determine the user/SID relationship for any other reason.
*
*****************************************************************************/
VOID WINAPI
GetUserNameFromSid( PSID pUserSid, PWCHAR pBuffer, PULONG pcbBuffer )
{
NTSTATUS status = STATUS_SUCCESS;
USHORT SidCrc = 0;
PUSERSIDLIST pEntry;
/*
* Make sure critical section has been inited
*/
if( !ULockInited ) {
status = InitULock();
}
/*
* Determine SID length in bytes and calculate a 16-bit CRC for it,
* to facilitate quick matching.
*/
if ( pUserSid )
SidCrc = CalculateCrc16( (PBYTE)pUserSid,
(USHORT)GetLengthSid(pUserSid) );
/*
* First: Before performing the expensive LookupAccountSid() function,
* see if we've encountered this SID already, and match the user name
* if so.
*/
if ( status == STATUS_SUCCESS && pUList ) {
RtlEnterCriticalSection( &ULock );
pEntry = pUList;
while( pEntry ) {
if ( SidCrc == pEntry->SidCrc ) {
wcsncpy( pBuffer, pEntry->UserName, (*pcbBuffer)-1 );
pBuffer[(*pcbBuffer)-1] = 0;
*pcbBuffer = wcslen(pBuffer);
RtlLeaveCriticalSection( &ULock );
return;
}
pEntry = pEntry->Next;
}
RtlLeaveCriticalSection( &ULock );
}
/*
* Last resort: Determine the user name associated with the SID using
* the LookupAccountSid() API, embedded in our local function
* LookupSidUser().
*/
LookupSidUser( pUserSid, pBuffer, pcbBuffer );
/*
* Add this new User/Sid relationship in our User/Sid cache list.
*/
if (status == STATUS_SUCCESS) {
RtlEnterCriticalSection( &ULock );
if ( (pEntry = (PUSERSIDLIST)malloc(sizeof(USERSIDLIST))) ) {
pEntry->SidCrc = SidCrc;
wcsncpy( pEntry->UserName, pBuffer, USERNAME_LENGTH - 1 );
pEntry->UserName[USERNAME_LENGTH-1] = 0;
pEntry->Next = pUList;
pUList = pEntry;
}
RtlLeaveCriticalSection( &ULock );
}
}
/******************************************************************************
* LookupSidUser
*
* Fetch the user name associated with the specified SID.
*
* ENTRY:
* pSid (input)
* Points to SID to match to user name.
* pUserName (output)
* Points to buffer to place the user name into.
* pcbUserName (input/output)
* Specifies the size in bytes of the user name buffer. The returned
* user name will be truncated to fit this buffer (including NUL
* terminator) if necessary and this variable set to the number of
* characters copied to pUserName.
*
* EXIT:
*
* LookupSidUser() will always return a user name. If the specified
* SID fails to match to a user name, then the user name "(unknown)" will
* be returned.
*
*****************************************************************************/
VOID
LookupSidUser( PSID pSid,
PWCHAR pUserName,
PULONG pcbUserName )
{
WCHAR DomainBuffer[DOMAIN_LENGTH], UserBuffer[USERNAME_LENGTH];
DWORD cbDomainBuffer=sizeof(DomainBuffer), cbUserBuffer=sizeof(UserBuffer),
Error;
PWCHAR pDomainBuffer = NULL, pUserBuffer = NULL;
SID_NAME_USE SidNameUse;
/*
* Fetch user name from SID: try user lookup with a reasonable Domain and
* Sid buffer size first, before resorting to alloc.
*/
if ( !LookupAccountSid( NULL, pSid,
UserBuffer, &cbUserBuffer,
DomainBuffer, &cbDomainBuffer, &SidNameUse ) ) {
if ( ((Error = GetLastError()) == ERROR_INSUFFICIENT_BUFFER) ) {
if ( cbDomainBuffer > sizeof(DomainBuffer) ) {
if ( !(pDomainBuffer =
(PWCHAR)malloc(
cbDomainBuffer * sizeof(WCHAR))) ) {
Error = ERROR_NOT_ENOUGH_MEMORY;
goto BadDomainAlloc;
}
}
if ( cbUserBuffer > sizeof(UserBuffer) ) {
if ( !(pUserBuffer =
(PWCHAR)malloc(
cbUserBuffer * sizeof(WCHAR))) ) {
Error = ERROR_NOT_ENOUGH_MEMORY;
goto BadUserAlloc;
}
}
if ( !LookupAccountSid( NULL, pSid,
pUserBuffer ?
pUserBuffer : UserBuffer,
&cbUserBuffer,
pDomainBuffer ?
pDomainBuffer : DomainBuffer,
&cbDomainBuffer,
&SidNameUse ) ) {
Error = GetLastError();
goto BadLookup;
}
} else {
goto BadLookup;
}
}
/*
* Copy the user name into the specified buffer, truncating if necessary.
*/
wcsncpy( pUserName, pUserBuffer ? pUserBuffer : UserBuffer,
(*pcbUserName)-1 );
pUserName[(*pcbUserName)-1] = 0;
*pcbUserName = wcslen(pUserName);
/*
* Free our allocs (if any) and return.
*/
if ( pDomainBuffer )
free(pDomainBuffer);
if ( pUserBuffer )
free(pUserBuffer);
return;
/*--------------------------------------
* Error clean-up and return...
*/
BadLookup:
BadUserAlloc:
BadDomainAlloc:
if ( pDomainBuffer )
free(pDomainBuffer);
if ( pUserBuffer )
free(pUserBuffer);
GetResourceStringFromUtilDll(IDS_UNKNOWN_USERNAME, pUserName, (*pcbUserName)-1);
pUserName[(*pcbUserName)-1] = 0;
*pcbUserName = wcslen(pUserName);
return;
}
/*******************************************************************************
*
* AreWeRunningTerminalServices
*
* Check if we are running terminal server
*
* ENTRY:
*
* EXIT: BOOL: True if we are running Terminal Services False if we
* are not running Terminal Services
*
*
******************************************************************************/
BOOL AreWeRunningTerminalServices(void)
{
OSVERSIONINFOEX osVersionInfo;
DWORDLONG dwlConditionMask = 0;
ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osVersionInfo.wSuiteMask = VER_SUITE_TERMINAL | VER_SUITE_SINGLEUSERTS;
VER_SET_CONDITION( dwlConditionMask, VER_SUITENAME, VER_OR );
return VerifyVersionInfo(
&osVersionInfo,
VER_SUITENAME,
dwlConditionMask
);
}