// ---------------------------------------------------------------------------- // // UManClnt.c // // Utility Manager client depending code (used by UtilMan and UManDlg) // // Author: J. Eckhardt, ECO Kommunikation // Copyright (c) 1997-1999 Microsoft Corporation // // History: created oct-98 by JE // JE nov-15-98: removed any code related to key hook // YX may-27-99: functions to start apps under current user account // YX may-29-99: apps start under user account even from LogOn desktop, // if possible // YX jun-04-99: code to report the app processes status even if they // have been started outside Utilman // YX jun-23-99: IsAdmin function added (used in the dialog) // Bug Fixes and Changes Anil Kumar 1999 // ---------------------------------------------------------------------------- #include #include #include #include "_UMTool.h" #include "w95trace.c" #include "UtilMan.h" #include "_UMClnt.h" #include "ums_ctrl.h" #include "w95trace.h" // Handle to the utilman instance that is showing UI HANDLE g_hUIProcess = 0; // From Terminal services extern BOOL GetWinStationUserToken(ULONG, PHANDLE); // Private User function that returns user token for session 0 only HANDLE GetCurrentUserTokenW( WCHAR WinSta[], DWORD desiredAccess); #include #define MAX_NUMBER_OF_PROCESSES 2048 // // RunningInMySession - returns TRUE if the specified process ID is // running in the same session as UtilMan. In Whistler, with terminal // services integrated, UtilMan is able to get information about // processes that are not running in the same session. We must // avoid impacting these processes. // BOOL RunningInMySession(DWORD dwProcessId) { DWORD dwSessionId = -1; static DWORD dwMySessionId = -1; if (-1 == dwMySessionId) { ProcessIdToSessionId(GetCurrentProcessId(), &dwMySessionId); } ProcessIdToSessionId(dwProcessId, &dwSessionId); return (dwSessionId == dwMySessionId)?TRUE:FALSE; } // These are for compiling with irnotig.lib // To be REMOVED once that becomes an API of advapi.lib PVOID MIDL_user_allocate(IN size_t BufferSize) { return( LocalAlloc(0, BufferSize)); } VOID MIDL_user_free(IN PVOID Buffer) { LocalFree( Buffer ); } BOOL StartAppAsUser( LPCTSTR appPath, LPTSTR cmdLine, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation); BOOL GetApplicationProcessInfo(umclient_tsp tspClient, BOOL fCloseHandle); BOOL CloseAllWindowsByProcessID(DWORD procID); // --------------------------------- BOOL StartClient(HWND hParent,umclient_tsp client) { if (client->runCount >= client->machine.MaxRunCount || client->runCount >= MAX_APP_RUNCOUNT) { DBPRINTF(_TEXT("StartClient run count >= max run count\r\n")); return FALSE; } switch (client->machine.ApplicationType) { case APPLICATION_TYPE_APPLICATION: { BOOL fStarted; TCHAR ApplicationPath[MAX_APPLICATION_PATH_LEN+100]; if (!GetClientApplicationPath( client->machine.ApplicationName , ApplicationPath , MAX_APPLICATION_PATH_LEN)) { return FALSE; } fStarted = StartApplication(ApplicationPath , UTILMAN_STARTCLIENT_ARG , client->user.fCanRunSecure , &client->processID[client->runCount] , &client->hProcess[client->runCount] , &client->mainThreadID[client->runCount]); if (!fStarted) { return FALSE; } client->runCount++; client->state = UM_CLIENT_RUNNING; break; } case APPLICATION_TYPE_SERVICE: { DWORD i = 0; SERVICE_STATUS ssStatus; SC_HANDLE hService; TCHAR arg2[200]; LPTSTR args[2]; SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (!hSCM) return FALSE; hService = OpenService(hSCM, client->machine.ApplicationName, SERVICE_ALL_ACCESS); CloseServiceHandle(hSCM); if (!hService) { return FALSE; } arg2[0] = 0; args[0] = UTILMAN_STARTCLIENT_ARG; args[1] = arg2; if (!StartService(hService,1,args)) { CloseServiceHandle(hService); return FALSE; } Sleep(1000); while(QueryServiceStatus(hService, &ssStatus)) { if (ssStatus.dwCurrentState == SERVICE_RUNNING) break; Sleep(1000); i++; if (i >= 60) break; } if (ssStatus.dwCurrentState != SERVICE_RUNNING) { CloseServiceHandle(hService); return FALSE; } CloseServiceHandle(hService); client->runCount++; client->processID[0] = 0; client->mainThreadID[0] = 0; client->hProcess[0] = NULL; client->state = UM_CLIENT_RUNNING; break; } default: return FALSE; } return TRUE; } // --------------------------------- // The hParent window is used to signal whether the stop is interactive // (and thus WM_COLSE could be used) or is a reaction to the desktop // change BOOL StopClient(umclient_tsp client) { if (!client->runCount || client->runCount > MAX_APP_RUNCOUNT) return FALSE; switch (client->machine.ApplicationType) { case APPLICATION_TYPE_APPLICATION: { DWORD j, runCount = client->runCount; for (j = 0; j < runCount; j++) { // If client was started outside UtilMan then try to get its process ID if (client->mainThreadID[j] == 0) { if (!GetApplicationProcessInfo(client, FALSE)) { // could not find the client, so prevent attempts to stop it client->hProcess[j] = NULL; } } if (client->hProcess[j]) { // Try to close the application by sending a WM_CLOSE message to // all the windows in opened by the process. Then just kill it. BOOL sent = CloseAllWindowsByProcessID(client->processID[j]); if (!sent) { TerminateProcess(client->hProcess[j],1); } client->processID[j] = 0; CloseHandle(client->hProcess[j]); client->hProcess[j] = NULL; client->mainThreadID[j] = 0; client->runCount--; if (!client->runCount) client->state = UM_CLIENT_NOT_RUNNING; } } if (runCount != client->runCount) { for (j = 0; j < (runCount-1); j++) { if (!client->hProcess[j]) { memmove(&client->processID[j], &client->processID[j+1],sizeof(DWORD)*(runCount-j-1)); memmove(&client->hProcess[j], &client->hProcess[j+1],sizeof(HANDLE)*(runCount-j-1)); memmove(&client->mainThreadID[j], &client->mainThreadID[j+1],sizeof(DWORD)*(runCount-j-1)); } } } break; } case APPLICATION_TYPE_SERVICE: { SERVICE_STATUS ssStatus; SC_HANDLE hService; SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (!hSCM) return FALSE; hService = OpenService(hSCM, client->machine.ApplicationName, SERVICE_ALL_ACCESS); CloseServiceHandle(hSCM); if (!hService) return FALSE; if (ControlService(hService, SERVICE_CONTROL_STOP, &ssStatus)) { DWORD i = 0; Sleep(1000); while(QueryServiceStatus(hService, &ssStatus)) { if (ssStatus.dwCurrentState == SERVICE_STOPPED) break; Sleep(1000); i++; if (i >= 60) break; } if (ssStatus.dwCurrentState != SERVICE_STOPPED) { CloseServiceHandle(hService); return FALSE; } } CloseServiceHandle(hService); client->runCount--; client->processID[0] = 0; client->hProcess[0] = NULL; client->mainThreadID[0] = 0; if (!client->runCount) client->state = UM_CLIENT_NOT_RUNNING; break; } default: return FALSE; } return TRUE; }//StopClient // --------------------------------- BOOL GetClientApplicationPath(LPTSTR ApplicationName, LPTSTR ApplicationPath,DWORD len) { HKEY hKey, sKey; DWORD ec, slen,type; ec = RegOpenKeyEx(HKEY_LOCAL_MACHINE, UM_REGISTRY_KEY,0,KEY_READ,&hKey); if (ec != ERROR_SUCCESS) return FALSE; ec = RegOpenKeyEx(hKey,ApplicationName,0,KEY_READ,&sKey); if (ec != ERROR_SUCCESS) { RegCloseKey(hKey); return FALSE; } slen = sizeof(TCHAR)*len; ec = RegQueryValueEx(sKey,UMR_VALUE_PATH,NULL,&type,(LPBYTE)ApplicationPath,&slen); if ((ec != ERROR_SUCCESS) || (type != REG_SZ)) { ApplicationPath[0] = TEXT('\0'); RegCloseKey(sKey); RegCloseKey(hKey); return FALSE; } ApplicationPath[slen-1] = TEXT('\0'); RegCloseKey(sKey); RegCloseKey(hKey); return (slen)?TRUE:FALSE; }//GetClientApplicationPath BOOL TestServiceClientRuns(umclient_tsp client,SERVICE_STATUS *ssStatus) { SC_HANDLE hService; SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (!hSCM) return FALSE; hService = OpenService(hSCM, client->machine.ApplicationName, SERVICE_ALL_ACCESS); CloseServiceHandle(hSCM); if (!hService) return FALSE; if (!QueryServiceStatus(hService, ssStatus) || (ssStatus->dwCurrentState == SERVICE_STOPPED)) { CloseServiceHandle(hService); return FALSE; } CloseServiceHandle(hService); return TRUE; } // // CheckStatus - Called from utilman's main timer and the dialog's timer // to detect the state of running applications and pick up // any that are started outside of utilman. // // Returns: TRUE if the state of any application has changed else FALSE // BOOL CheckStatus(umclient_tsp c, DWORD cClients) { DWORD i; BOOL fAnyChanges = FALSE; for (i = 0; i < cClients && cClients < MAX_NUMBER_OF_CLIENTS; i++) { // detect the client process started outside UMan if ( (!c[i].runCount)) { if (GetApplicationProcessInfo(&c[i], TRUE)) { fAnyChanges = TRUE; } } // detect clients not running anymore or not responding switch (c[i].machine.ApplicationType) { case APPLICATION_TYPE_APPLICATION: { DWORD j, dwRunCount = c[i].runCount; for (j = 0; j < dwRunCount && j < MAX_APP_RUNCOUNT; j++) { // step 1: test if terminated if (!GetProcessVersion(c[i].processID[j])) { c[i].runCount--; c[i].hProcess[j] = NULL; c[i].processID[j] = 0; c[i].mainThreadID[j] = 0; c[i].state = UM_CLIENT_NOT_RUNNING; c[i].user.fRestartOnDefaultDesk = FALSE; fAnyChanges = TRUE; continue; // its not running anymore } // step 2: test if responding (only processes started by utilman - mainThreadID != 0) if (c[i].mainThreadID[j] != 0) { if (!PostThreadMessage(c[i].mainThreadID[j],WM_QUERYENDSESSION,0,ENDSESSION_LOGOFF)) { c[i].state = UM_CLIENT_NOT_RESPONDING; fAnyChanges = TRUE; continue; // its not responding } } if (c[i].state != UM_CLIENT_RUNNING) { fAnyChanges = TRUE; } c[i].state = UM_CLIENT_RUNNING; } if (dwRunCount != c[i].runCount) { for (j = 0; j < (dwRunCount-1) && j < (MAX_APP_RUNCOUNT-1); j++) { if (!c[i].processID[j]) { memmove(&c[i].processID[j], &c[i].processID[j+1],sizeof(DWORD)*(dwRunCount-j-1)); memmove(&c[i].hProcess[j], &c[i].hProcess[j+1],sizeof(HANDLE)*(dwRunCount-j-1)); memmove(&c[i].mainThreadID[j], &c[i].mainThreadID[j+1],sizeof(DWORD)*(dwRunCount-j-1)); } } } break; } case APPLICATION_TYPE_SERVICE: { SERVICE_STATUS ssStatus; if (!TestServiceClientRuns(&c[i],&ssStatus)) { c[i].runCount--; c[i].processID[0] = 0; c[i].mainThreadID[0] = 0; c[i].state = UM_CLIENT_NOT_RUNNING; fAnyChanges = TRUE; } break; } } } return fAnyChanges; } __inline DWORD GetCurrentSession() { static DWORD dwSessionId = -1; if (-1 == dwSessionId) { ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionId); } return dwSessionId; } // GetUserAccessToken - return the logged on user's access token // // If fNeedImpersonationToken is true the token will be an // impersonation token otherwise it will be a primary token. // The returned token will be 0 if security calls fail. // // Notes: Caller must call CloseHandle on the returned handle. // HANDLE GetUserAccessToken(BOOL fNeedImpersonationToken, BOOL *fError) { HANDLE hUserToken = 0; HANDLE hImpersonationToken = 0; *fError = FALSE; if (!GetWinStationUserToken(GetCurrentSession(), &hImpersonationToken)) { // Call private API in the case where terminal services aren't running HANDLE hPrimaryToken = 0; DWORD dwFlags = TOKEN_QUERY | TOKEN_DUPLICATE; dwFlags |= (fNeedImpersonationToken)? TOKEN_IMPERSONATE : TOKEN_ASSIGN_PRIMARY; hPrimaryToken = GetCurrentUserTokenW (L"WinSta0", dwFlags); // GetCurrentUserTokenW returns a primary token; turn // it into an impersonation token if needed if (hPrimaryToken && fNeedImpersonationToken) { if (!DuplicateToken(hPrimaryToken, SecurityImpersonation, &hUserToken)) { *fError = TRUE; DBPRINTF(TEXT("GetUserAccessToken: DuplicateToken returned %d\r\n"), GetLastError()); } CloseHandle(hPrimaryToken); } else { // otherwise, give out the primary token even if NULL hUserToken = hPrimaryToken; } } else { // Terminal services are running see if we need primary token if (hImpersonationToken && !fNeedImpersonationToken) { if (!DuplicateTokenEx(hImpersonationToken, 0, NULL , SecurityImpersonation, TokenPrimary, &hUserToken)) { *fError = TRUE; DBPRINTF(TEXT("GetUserAccessToken: DuplicateTokenEx returned %d\r\n"), GetLastError()); } CloseHandle(hImpersonationToken); } else { // otherwise, give out the impersonation token even if NULL hUserToken = hImpersonationToken; } } return hUserToken; } // StartAppAsUser - start the app in the context of the logged on user // BOOL StartAppAsUser( LPCTSTR appPath, LPTSTR cmdLine, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation) { HANDLE hNewToken = 0; BOOL fStarted = FALSE; BOOL fError; // Get the our process's primary token (only succeeds if we are SYSTEM) hNewToken = GetUserAccessToken(FALSE, &fError); if (hNewToken) { // running in system context so impersonate the logged on user fStarted = CreateProcessAsUser( hNewToken, appPath, cmdLine, 0, 0, FALSE, NORMAL_PRIORITY_CLASS , 0, 0, lpStartupInfo, lpProcessInformation ); CloseHandle( hNewToken ); DBPRINTF(TEXT("StartAppAsUser: CreateProcessAsUser(%s, %s) returns %d\r\n"), appPath, cmdLine, fStarted); } else if (IsInteractiveUser()) { TCHAR szArg[] = UTILMAN_STARTCLIENT_ARG; // Running in interactive user's context, just do normal create. Since // we are the interactive user default security descriptors will do. fStarted = CreateProcess(appPath, szArg , NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE , NULL, NULL, lpStartupInfo, lpProcessInformation); DBPRINTF(TEXT("StartAppAsUser: CreateProcess(%s, %s) returns %d\r\n"), appPath, UTILMAN_STARTCLIENT_ARG, fStarted); } // caller is going to close handles return fStarted; } // Functions to detect running copies of Accessibility utilities // FindProcess - Searches the running processes by application name. If found, // returns the process id. Else returns zero. If the process // id is returned then phProcess is the process handle. The // caller must close the process handle. // // pszApplicationName [in] - application as base.ext // phProcess [out] - pointer to memory to receive process handle // // returns the process Id. // DWORD FindProcess(LPCTSTR pszApplicationName, HANDLE *phProcess) { DWORD dwProcId = 0; DWORD adwProcess[MAX_NUMBER_OF_PROCESSES]; // array to receive the process identifiers DWORD cProcesses; DWORD dwThisProcess = GetCurrentProcessId(); unsigned int i; *phProcess = 0; // Get IDs of all running processes if (!EnumProcesses(adwProcess, sizeof(adwProcess), &cProcesses)) return 0; // cProcesses is returned as bytes; convert to number of processes cProcesses = cProcesses/sizeof(DWORD); if (cProcesses > MAX_NUMBER_OF_PROCESSES) cProcesses = MAX_NUMBER_OF_PROCESSES; // open each process and test against pszApplicationName for (i = 0; i < cProcesses; i++) { HANDLE hProcess; // // EnumProcesses returns process IDs across all sessions but // we are only interested in processes in our session // if (!RunningInMySession(adwProcess[i])) continue; // Skip this process if (dwThisProcess == adwProcess[i]) continue; hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ , FALSE, adwProcess[i]); if (hProcess != NULL) { HMODULE hMod; TCHAR szProcessName[MAX_PATH]; DWORD ccbProcess; // find the module handle of exe of this process then it's base name (name.ext) if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &ccbProcess) ) { DWORD ctch = GetModuleBaseName(hProcess, hMod, szProcessName, MAX_PATH); if (ctch && _wcsicmp(szProcessName, pszApplicationName) == 0) { *phProcess = hProcess; // found it dwProcId = adwProcess[i]; break; } } CloseHandle(hProcess); } } return dwProcId; } // GetApplicationProcessInfo - Tries to find the process running for this application BOOL GetApplicationProcessInfo(umclient_tsp tspClient, BOOL fCloseHandle) { DWORD dwProcId; HANDLE hProcess; TCHAR ApplicationPath[MAX_PATH]; TCHAR szDrive[_MAX_DRIVE]; TCHAR szPath[_MAX_PATH]; TCHAR szName[_MAX_FNAME+_MAX_EXT]; TCHAR szExt[_MAX_EXT]; if (GetClientApplicationPath(tspClient->machine.ApplicationName, ApplicationPath, MAX_PATH)) { // ApplicationPath may include path information but we need just the base name _wsplitpath(ApplicationPath, szDrive, szPath, szName, szExt); lstrcat(szName, szExt); dwProcId = FindProcess(szName, &hProcess); if (dwProcId) { tspClient->processID[0] = dwProcId; // I do not know how to get main thread ID tspClient->mainThreadID[0] = 0; tspClient->runCount = 1; tspClient->state = UM_CLIENT_RUNNING; // In order to keep the HANDLE usable, we may have to keep it open. // So, we do not close it here, but I consider it relatively safe, // since we cannot execute this code more than once without // terminating the process first (and thus closing the handle) if (fCloseHandle) { CloseHandle(hProcess); tspClient->hProcess[0] = NULL; } else { tspClient->hProcess[0] = hProcess; } return TRUE; // the application is running } } return FALSE; // the application is not running } // YX 06-15-99 [ // Code to finid the window by its Process ID static BOOL SentClose; BOOL CALLBACK FindWindowByID(HWND hWnd, LPARAM lParam) { DWORD procID; if (GetWindowThreadProcessId(hWnd, &procID) != 0) { if (procID == (DWORD)lParam) { // The process, We are looking for // Send a message to close this window // CAUTION: A SendMessage is Synchronous, So It will freeze UM if the // message doenot return so PostMessage is safer or a SendMessageTimeout() // PostMessage is sufficient.... PostMessage(hWnd, WM_CLOSE, 0, 0); SentClose = TRUE; } } return TRUE; } BOOL CloseAllWindowsByProcessID(DWORD procID) { BOOL rc = FALSE; SentClose = FALSE; rc = EnumWindows(FindWindowByID, (LPARAM)procID); return SentClose; } // IsAdmin - Returns TRUE if our process has admin priviliges else FALSE // BOOL IsAdmin() { BOOL fStatus = FALSE; BOOL fIsAdmin = FALSE; PSID AdministratorsSid = AdminSid(TRUE); if (AdministratorsSid) { fStatus = CheckTokenMembership(NULL, AdministratorsSid, &fIsAdmin); } return (fStatus && fIsAdmin); } // IsInteractiveUser - Returns TRUE if our process has an Interactive User SID // BOOL IsInteractiveUser() { BOOL fStatus = FALSE; BOOL fIsInteractiveUser = FALSE; PSID psidInteractive = InteractiveUserSid(TRUE); if (psidInteractive) { fStatus = CheckTokenMembership(NULL, psidInteractive, &fIsInteractiveUser); } return (fStatus && fIsInteractiveUser); } // IsSystem - Returns TRUE if our process is running as SYSTEM // BOOL IsSystem() { BOOL fStatus = FALSE; BOOL fIsLocalSystem = FALSE; SID_IDENTIFIER_AUTHORITY siaLocalSystem = SECURITY_NT_AUTHORITY; PSID psidSystem = SystemSid(TRUE); if (psidSystem) { fStatus = CheckTokenMembership(NULL, psidSystem, &fIsLocalSystem); } return (fStatus && fIsLocalSystem); } BOOL StartApplication( LPTSTR pszPath, // IN path + filename of application to start LPTSTR pszArg, // IN command line argument(s) BOOL fIsTrusted, // IN TRUE if app can run on secure desktop DWORD *pdwProcessId, // OUT if not NULL, returned process Id HANDLE *phProcess, // OUT if not NULL, returned process handle (caller must close) DWORD *pdwThreadId // OUT if not NULL, returned thread Id ) { STARTUPINFO si; PROCESS_INFORMATION pi; desktop_ts desktop; BOOL fStarted; memset(&si,0,sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); memset(&pi,0,sizeof(PROCESS_INFORMATION)); QueryCurrentDesktop(&desktop, TRUE); DBPRINTF(TEXT("StartApplication: pszPath=%s pszArg=%s fIsTrusted=%d Utilman is SYSTEM=%d\r\n"), pszPath, pszArg, fIsTrusted, IsSystem()); // If not on the winlogon desktop, first try starting the app as the interactive // user. If that fails (eg. the case when OOBE runs after setup when there is // no interactive user) then, if its the winlogon desktop or utilman is running // SYSTEM and the app is trusted then use CreateProcess (the app will be running // as SYSTEM). The latter case (running SYSTEM and the app is trusted allows // applets to run when OOBE is running. fStarted = FALSE; if (desktop.type != DESKTOP_WINLOGON) { si.lpDesktop = desktop.name; fStarted = StartAppAsUser(pszPath, pszArg, &si,&pi); } if (!fStarted && (desktop.type == DESKTOP_WINLOGON || (IsSystem() && fIsTrusted))) { if (fIsTrusted) { si.lpDesktop = 0; // Since we only run trusted apps we can run with default security descriptor fStarted = CreateProcess(pszPath, pszArg, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL, &si, &pi); DBPRINTF(TEXT("StartApplication: trusted CreateProcess(%s, %s) returns %d\r\n"), pszPath, pszArg, fStarted); } } if (fStarted) { if (pdwProcessId) { *pdwProcessId = pi.dwProcessId; } if (phProcess) { *phProcess = pi.hProcess; } else { CloseHandle(pi.hProcess); } if (pdwThreadId) { *pdwThreadId = pi.dwThreadId; } CloseHandle(pi.hThread); } return fStarted; }