517 lines
14 KiB
C
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
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|