//+---------------------------------------------------------------------------- // // Job Scheduler Service // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1996. // // File: getuser.cxx // // Contents: Get the identity of the logged in user. // // History: 19-Jun-96 EricB created // // Notes: This is for NT only since Win95 doesn't have security. // //----------------------------------------------------------------------------- // // Some NT header definitions conflict with some of the standard windows // definitions. Thus, the project precompiled header can't be used. // extern "C" { #include // NT definitions #include // NT runtime library definitions #include #include // BUGBUG 254102 } #include #define SECURITY_WIN32 // needed by security.h #include // GetUserNameEx #include // SecureZeroMemory #include #include // BUGBUG 254102 #include // BUGBUG 254102 #include <..\..\..\smdebug\smdebug.h> #include #include "globals.hxx" #include const int SCH_BIGBUF_LEN = 256; // // Registry key/value for default shell. // #define SHELL_REGKEY L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" #define SHELL_REGVAL L"Shell" #define DEFAULT_SHELL L"explorer.exe" // This function is actually declared in proto.hxx. But including proto.hxx // brings in alot of things we don't need. Just define it here to the includes // simple. // HANDLE ImpersonateUser(HANDLE hUserToken, HANDLE hImpersonationToken); // so's this one - here's to hoping that type-safe linkage works well... HANDLE ImpersonateLoggedInUser(void); BOOL StopImpersonating(HANDLE ThreadHandle, BOOL fCloseHandle); // toggle to allow conditional compilation of fix for RAID 720688 // "old" method found user token for shell process (usually explorer.exe) // "new" method uses Terminal Server functions #define FIND_USER_WITH_TS //+---------------------------------------------------------------------------- // // Function: LogonSessionDataCleanup // // Synopsis: Close all open handles and free memory. // // Notes: **** Important **** // // No need to enter gcsLogonSessionInfoCritSection prior to // calling this function since it is entered here. // //----------------------------------------------------------------------------- void LogonSessionDataCleanup(void) { EnterCriticalSection(gUserLogonInfo.CritSection); if (gUserLogonInfo.ImpersonationThread != NULL) { CloseHandle(gUserLogonInfo.ImpersonationThread); gUserLogonInfo.ImpersonationThread = NULL; } if (gUserLogonInfo.DomainUserName != NULL) { delete gUserLogonInfo.DomainUserName; gUserLogonInfo.DomainUserName= NULL; } if (gUserLogonInfo.ShellToken) { CloseHandle(gUserLogonInfo.ShellToken); gUserLogonInfo.ShellToken = NULL; } SecureZeroMemory(gUserLogonInfo.Sid, sizeof(gUserLogonInfo.Sid)); LeaveCriticalSection(gUserLogonInfo.CritSection); } //+---------------------------------------------------------------------------- // // Function: ImpersonateUser // // Synopsis: Impersonate the user associated with the token. // // Arguments: [hUserToken] - Handle to the token to be impersonated. // [ThreadHandle] - Handle to the thread that is to impersonate // hUserToken. If this is NULL, the function opens a handle // to the current thread. // // Returns: Handle to the thread that is impersonating hUserToken. // // Notes: BUGBUG : This code was taken from RAS. It is quite different // than that in winlogon // (windows\gina\winlogon\secutil.c). // //----------------------------------------------------------------------------- HANDLE ImpersonateUser(HANDLE hUserToken, HANDLE ThreadHandle) { NTSTATUS Status; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; OBJECT_ATTRIBUTES ObjectAttributes; HANDLE ImpersonationToken; BOOL ThreadHandleOpened = FALSE; if (ThreadHandle == NULL) { // // Get a handle to the current thread. // Once we have this handle, we can set the user's impersonation // token into the thread and remove it later even though we ARE // the user for the removal operation. This is because the handle // contains the access rights - the access is not re-evaluated // at token removal time. // Status = NtDuplicateObject( NtCurrentProcess(), // Source process NtCurrentThread(), // Source handle NtCurrentProcess(), // Target process &ThreadHandle, // Target handle THREAD_SET_THREAD_TOKEN,// Access 0L, // Attributes DUPLICATE_SAME_ATTRIBUTES); if (!NT_SUCCESS(Status)) { ERR_OUT("ImpersonateUser: NtDuplicateObject", Status); return(NULL); } ThreadHandleOpened = TRUE; } // // If the usertoken is NULL, there's nothing to do // if (hUserToken != NULL) { // // hUserToken is a primary token - create an impersonation token // version of it so we can set it on our thread // InitializeObjectAttributes(&ObjectAttributes, NULL, 0L, NULL, // UserProcessData->NewThreadTokenSD); NULL); SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService; Status = NtDuplicateToken(hUserToken, TOKEN_IMPERSONATE | TOKEN_READ, &ObjectAttributes, FALSE, TokenImpersonation, &ImpersonationToken); if (!NT_SUCCESS(Status)) { ERR_OUT("ImpersonateUser: NtDuplicateToken", Status); if (ThreadHandleOpened) { NtClose(ThreadHandle); } return(NULL); } // // Set the impersonation token on this thread so we 'are' the user // Status = NtSetInformationThread(ThreadHandle, ThreadImpersonationToken, (PVOID)&ImpersonationToken, sizeof(ImpersonationToken)); // // We're finished with our handle to the impersonation token // NtClose(ImpersonationToken); // // Check we set the token on our thread ok // if (!NT_SUCCESS(Status)) { ERR_OUT("ImpersonateUser: NTSetInformationThread", Status); if (ThreadHandleOpened) { NtClose(ThreadHandle); } return(NULL); } } return(ThreadHandle); } //+---------------------------------------------------------------------------- // // Function: GetLoggedOnUser // // Synopsis: Called when a user logs in. // // Returns: None. Sets the global gUserLogonInfo. // // Notes: **** Important **** // // Caller must have entered the gcsLogonSessionInfoCritSection // critical section for the duration of this call and continue // to remain in this critical section for the lifetime use of // the returned string. // // DO NOT attempt to dealloc the returned string! It is a // pointer to global memory. // //----------------------------------------------------------------------------- void GetLoggedOnUser(void) { LPWSTR pwszLoggedOnUser; DWORD cchName = 0; DWORD dwErr = ERROR_SUCCESS; if (gUserLogonInfo.DomainUserName != NULL) { // // Already done. // return; } // // Impersonate the logged in user. // if (ImpersonateLoggedInUser()) { // // Get the size of the user name string. // if (!GetUserNameEx(NameSamCompatible, NULL, &cchName)) { dwErr = GetLastError(); if (dwErr != ERROR_MORE_DATA || cchName == 0) { StopImpersonating(gUserLogonInfo.ImpersonationThread, TRUE); ERR_OUT("GetLoggedOnUser: GetUserName", dwErr); return; } } cchName++; // contrary to docs, cchName excludes the null // // Allocate the user name string buffer and get the user name. // pwszLoggedOnUser = new WCHAR[cchName * 2]; if (pwszLoggedOnUser != NULL) { if (!GetUserNameEx(NameSamCompatible, pwszLoggedOnUser, &cchName)) { dwErr = GetLastError(); ERR_OUT("GetLoggedOnUser: GetUserName", dwErr); delete pwszLoggedOnUser; } else { schDebugOut((DEB_ITRACE, "GetLoggedOnUser: got '%S'\n", pwszLoggedOnUser)); cchName++; // contrary to docs, cchName excludes the null // // This name is in the format "domain\\user". // Make a copy of the domain name right after it, so // we end up with a single buffer in the format // "domain\\user\0domain". Set up pointers into this // buffer for all 3 parts of the name: // domain // user // domain\user // gUserLogonInfo.DomainUserName = pwszLoggedOnUser; WCHAR *pSlash = wcschr(pwszLoggedOnUser, L'\\'); schAssert(pSlash != NULL); gUserLogonInfo.UserName = pSlash + 1; DWORD cchDomain = (DWORD) (pSlash - pwszLoggedOnUser); gUserLogonInfo.DomainName = pwszLoggedOnUser + cchName; wcsncpy(gUserLogonInfo.DomainName, pwszLoggedOnUser, cchDomain); gUserLogonInfo.DomainName[cchDomain] = L'\0'; schDebugOut((DEB_ITRACE, "GetLoggedOnUser: domain '%S', user '%S'\n", gUserLogonInfo.DomainName, gUserLogonInfo.UserName)); } } else { ERR_OUT("GetLoggedOnUser", ERROR_OUTOFMEMORY); } // // BUGBUG 254102 - Cache the logged-on user's SID, since // LookupAccountName doesn't do it when offline. // Remove this code when bug 254102 is fixed. // #define USER_TOKEN_STACK_BUFFER_SIZE \ (sizeof(TOKEN_USER) + sizeof(SID_AND_ATTRIBUTES) + MAX_SID_SIZE) BYTE rgbTokenInformation[USER_TOKEN_STACK_BUFFER_SIZE]; TOKEN_USER * pTokenUser = (TOKEN_USER *)rgbTokenInformation; DWORD cbReturnLength; if (!GetTokenInformation(gUserLogonInfo.ShellToken, TokenUser, pTokenUser, USER_TOKEN_STACK_BUFFER_SIZE, &cbReturnLength)) { schAssert(GetLastError() != ERROR_INSUFFICIENT_BUFFER); CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError())); SecureZeroMemory(gUserLogonInfo.Sid, sizeof(gUserLogonInfo.Sid)); } else if (!CopySid(sizeof(gUserLogonInfo.Sid), gUserLogonInfo.Sid, pTokenUser->User.Sid)) { schAssert(!"CopySid failed"); CHECK_HRESULT(HRESULT_FROM_WIN32(GetLastError())); SecureZeroMemory(gUserLogonInfo.Sid, sizeof(gUserLogonInfo.Sid)); } StopImpersonating(gUserLogonInfo.ImpersonationThread, FALSE); } } //+---------------------------------------------------------------------------- // // Function: StopImpersonating // // Synopsis: Stop impersonating. // // Notes: This code was taken from winlogon. Specifically: // windows\gina\winlogon\secutil.c. // //----------------------------------------------------------------------------- BOOL StopImpersonating(HANDLE ThreadHandle, BOOL fCloseHandle) { NTSTATUS Status, IgnoreStatus; HANDLE ImpersonationToken; // // Remove the user's token from our thread so we are 'ourself' again // ImpersonationToken = NULL; Status = NtSetInformationThread(ThreadHandle, ThreadImpersonationToken, (PVOID)&ImpersonationToken, sizeof(ImpersonationToken)); // // We're finished with the thread handle // if (fCloseHandle) { IgnoreStatus = NtClose(ThreadHandle); schAssert(NT_SUCCESS(IgnoreStatus)); } if (!NT_SUCCESS(Status)) { schDebugOut((DEB_ERROR, "Failed to remove user impersonation token from SA service, " \ "status = 0x%lx", Status)); } return(NT_SUCCESS(Status)); } #ifdef FIND_USER_WITH_TS //+---------------------------------------------------------------------------- // // Function: ImpersonateLoggedInUser // // Synopsis: Impersonate the shell user. // // Returns: Handle to thread that's impersonating user // // Notes: **** Important **** // // Caller must have entered the gcsLogonSessionInfoCritSection // critical section for the duration of this call. // // GLOBALS: sets gUserLogonInfo.ShellToken and gUserLogonInfo.ImpersonationThread // //----------------------------------------------------------------------------- HANDLE ImpersonateLoggedInUser(void) { if (gUserLogonInfo.ShellToken) { CloseHandle(gUserLogonInfo.ShellToken); gUserLogonInfo.ShellToken = NULL; } DWORD sessionID; sessionID = WTSGetActiveConsoleSessionId(); if (sessionID == 0xFFFFFFFF) return NULL; if (!WTSQueryUserToken(sessionID, &gUserLogonInfo.ShellToken)) return NULL; if (gUserLogonInfo.ImpersonationThread) CloseHandle(gUserLogonInfo.ImpersonationThread); return (gUserLogonInfo.ImpersonationThread = ImpersonateUser( gUserLogonInfo.ShellToken, gUserLogonInfo.ImpersonationThread)); } #else // #ifdef FIND_USER_WITH_TS HANDLE GetShellProcessHandle(void); PSYSTEM_PROCESS_INFORMATION GetSystemProcessInfo(void); PSYSTEM_PROCESS_INFORMATION FindProcessByName(PSYSTEM_PROCESS_INFORMATION, LPWSTR); VOID FreeSystemProcessInfo(PSYSTEM_PROCESS_INFORMATION pProcessInfo); //+---------------------------------------------------------------------------- // // Function: GetShellProcessHandle // // Synopsis: Initialize & return the shell handle of the current logged // on user, gUserLogonInfo.ShellHandle. // // Returns: ERROR_SUCCESS or an error code. // // Notes: **** Important **** // // Caller must have entered gUserLogonInfo.CriticalSection // for the duration of this call and continue to remain in // in it for the lifetime use of the returned handle. // // DO NOT close the returned handle. It is a global handle. // //----------------------------------------------------------------------------- HANDLE GetShellProcessHandle(void) { PSYSTEM_PROCESS_INFORMATION pSystemInfo, pProcessInfo; WCHAR wszShellName[MAX_PATH + 1]; WCHAR * pwszShellName = wszShellName; WCHAR * pwsz; HKEY hReg = NULL; HANDLE hProcess = NULL; DWORD dwErr = ERROR_SUCCESS; DWORD dwType; DWORD dwSize; // // Get the shell process name. We will look for this // to find out who the currently logged-on user is. // if (gUserLogonInfo.ShellHandle != NULL) { // // Check if the handle is valid. // if (WaitForSingleObject(gUserLogonInfo.ShellHandle, 0) == WAIT_TIMEOUT) { // // Still valid. // return(gUserLogonInfo.ShellHandle); } // // Re-acquire handle. // CloseHandle(gUserLogonInfo.ShellHandle); gUserLogonInfo.ShellHandle = NULL; } StringCchCopy(pwszShellName, MAX_PATH + 1, DEFAULT_SHELL); if ((dwErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, SHELL_REGKEY, 0, KEY_READ, &hReg)) == ERROR_SUCCESS) { dwSize = sizeof(wszShellName); dwErr = RegQueryValueEx(hReg, SHELL_REGVAL, NULL, &dwType, (PBYTE)pwszShellName, &dwSize); RegCloseKey(hReg); } if (dwErr != ERROR_SUCCESS) { ERR_OUT("GetShellProcessHandle: RegQueryValueEx", dwErr); return(NULL); } // // Remove parameters from command line. // pwsz = pwszShellName; while (*pwsz != L' ' && *pwsz != L'\0') { pwsz++; } *pwsz = L'\0'; // // Get the process list. // pSystemInfo = GetSystemProcessInfo(); if (pSystemInfo == NULL) { return(NULL); } // // See if wszShell is running. // pProcessInfo = FindProcessByName(pSystemInfo, pwszShellName); if (pProcessInfo != NULL) { // // Open the process. // hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, HandleToUlong(pProcessInfo->UniqueProcessId)); #if DBG == 1 if (hProcess == NULL) { ERR_OUT("GetShellProcessHandle: OpenProcess", GetLastError()); } #endif } // // Free resources. // FreeSystemProcessInfo(pSystemInfo); // // Return process handle. // return(gUserLogonInfo.ShellHandle = hProcess); } //+---------------------------------------------------------------------------- // // Function: GetShellProcessToken // // Synopsis: // // Returns: ERROR_SUCCESS or an error code. // // Notes: **** Important **** // // Caller must have entered the gcsLogonSessionInfoCritSection // critical section for the duration of this call and continue // to remain in this critical section for the lifetime use of // the returned handle. // // DO NOT close the returned handle. It is a global handle. // //----------------------------------------------------------------------------- HANDLE GetShellProcessToken(void) { HANDLE hProcess = GetShellProcessHandle(); if (hProcess == NULL) { return(NULL); } HANDLE hToken = gUserLogonInfo.ShellToken; if (gUserLogonInfo.ShellToken == NULL) { if (OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &hToken)) { return(gUserLogonInfo.ShellToken = hToken); } else { ERR_OUT("GetShellProcessToken: OpenProcessToken", GetLastError()); return(NULL); } } return gUserLogonInfo.ShellToken; } //+---------------------------------------------------------------------------- // // Function: GetSystemProcessInfo // // Synopsis: Return a block containing information about all processes // currently running in the system. // // Returns: A pointer to the system process information or NULL if it could // not be allocated or retrieved. // //----------------------------------------------------------------------------- PSYSTEM_PROCESS_INFORMATION GetSystemProcessInfo(void) { #define SYSTEM_PROCESS_BUFFER_INCREMENT 4096 NTSTATUS Status = 0; PUCHAR pBuffer; DWORD cbBufferSize; // // Get the process list. // cbBufferSize = SYSTEM_PROCESS_BUFFER_INCREMENT; pBuffer = (PUCHAR)LocalAlloc(LMEM_FIXED, cbBufferSize); if (pBuffer == NULL) { ERR_OUT("GetSystemProcessInfo: LocalAlloc", GetLastError()); return(NULL); } for (;;) { Status = NtQuerySystemInformation(SystemProcessInformation, pBuffer, cbBufferSize, NULL); if (Status == STATUS_SUCCESS) { break; } else if (Status == STATUS_INFO_LENGTH_MISMATCH) { cbBufferSize += SYSTEM_PROCESS_BUFFER_INCREMENT; PUCHAR pTempBuffer = (PUCHAR)LocalReAlloc(pBuffer, cbBufferSize, LMEM_MOVEABLE); if (pTempBuffer == NULL) { LocalFree(pBuffer); // original handle is still valid; use it to free the memory ERR_OUT("GetSystemProcessInfo: LocalReAlloc", GetLastError()); return(NULL); } pBuffer = pTempBuffer; // LocalReAlloc succeeded, so use the new handle now } else { break; } } if (Status != STATUS_SUCCESS && pBuffer != NULL) { LocalFree(pBuffer); pBuffer = NULL; } return (PSYSTEM_PROCESS_INFORMATION)pBuffer; } //+---------------------------------------------------------------------------- // // Function: FindProcessByName // // Synopsis: Given a pointer returned by GetSystemProcessInfo(), find // a process by name. // Hydra modification: Only processes on the physical console // session are included. // // Arguments: [pProcessInfo] - a pointer returned by GetSystemProcessInfo(). // [lpExeName] - a pointer to a Unicode string containing the // process to be found. // // Returns: A pointer to the process information for the supplied // process or NULL if it could not be found. // //----------------------------------------------------------------------------- PSYSTEM_PROCESS_INFORMATION FindProcessByName(PSYSTEM_PROCESS_INFORMATION pProcessInfo, LPWSTR lpExeName) { PUCHAR pLargeBuffer = (PUCHAR)pProcessInfo; ULONG ulTotalOffset = 0; // // Look in the process list for lpExeName. // for (;;) { if (pProcessInfo->ImageName.Buffer != NULL) { schDebugOut((DEB_USER3, "FindProcessByName: process: %S (%d)\n", pProcessInfo->ImageName.Buffer, pProcessInfo->UniqueProcessId)); if (!_wcsicmp(pProcessInfo->ImageName.Buffer, lpExeName)) { // // Pick this process only if it's // running on the physical console session // DWORD dwSessionId; if (! ProcessIdToSessionId( HandleToUlong(pProcessInfo->UniqueProcessId), &dwSessionId)) { schDebugOut((DEB_ERROR, "ProcessIdToSessionId FAILED, %lu\n", GetLastError)); } else if (dwSessionId == 0) { return pProcessInfo; } } } // // Increment offset to next process information block. // if (!pProcessInfo->NextEntryOffset) { break; } ulTotalOffset += pProcessInfo->NextEntryOffset; pProcessInfo = (PSYSTEM_PROCESS_INFORMATION)&pLargeBuffer[ulTotalOffset]; } schDebugOut((DEB_ITRACE, "FindProcessByName: process %ws not found\n", lpExeName)); return NULL; } //+---------------------------------------------------------------------------- // // Function: FreeSystemProcessInfo // // Synopsis: Free a buffer returned by GetSystemProcessInfo(). // // Arguments: [pProcessInfo] - a pointer returned by GetSystemProcessInfo(). // //----------------------------------------------------------------------------- VOID FreeSystemProcessInfo(PSYSTEM_PROCESS_INFORMATION pProcessInfo) { LocalFree(pProcessInfo); } //+---------------------------------------------------------------------------- // // Function: ImpersonateLoggedInUser // // Synopsis: Impersonate the shell user. // // Returns: // // Notes: **** Important **** // // Caller must have entered the gcsLogonSessionInfoCritSection // critical section for the duration of this call. // //----------------------------------------------------------------------------- HANDLE ImpersonateLoggedInUser(void) { BOOL fDuplicateToken; // // Open the impersonation token for the // process we want to impersonate. // if (gUserLogonInfo.ImpersonationThread == NULL) { if (gUserLogonInfo.ShellHandle == NULL) { if (GetShellProcessHandle() == NULL) { return(NULL); } } if (gUserLogonInfo.ShellToken == NULL) { if (GetShellProcessToken() == NULL) { return(NULL); } } } return(gUserLogonInfo.ImpersonationThread = ImpersonateUser( gUserLogonInfo.ShellToken, gUserLogonInfo.ImpersonationThread)); } #endif // FIND_USER_WITH_TS