00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "CMSWindowsRelauncher.h"
00020 #include "CThread.h"
00021 #include "TMethodJob.h"
00022 #include "CLog.h"
00023 #include "CArch.h"
00024 #include "Version.h"
00025 #include "CArchDaemonWindows.h"
00026 #include "XArchWindows.h"
00027 #include "CApp.h"
00028 #include "CArgsBase.h"
00029 #include "CIpcLogOutputter.h"
00030 #include "CIpcServer.h"
00031 #include "CIpcMessage.h"
00032 #include "Ipc.h"
00033
00034 #include <Tlhelp32.h>
00035 #include <UserEnv.h>
00036 #include <sstream>
00037 #include <Wtsapi32.h>
00038 #include <Shellapi.h>
00039
00040 enum {
00041 kOutputBufferSize = 4096
00042 };
00043
00044 typedef VOID (WINAPI *SendSas)(BOOL asUser);
00045
00046 CMSWindowsRelauncher::CMSWindowsRelauncher(
00047 bool autoDetectCommand,
00048 CIpcServer& ipcServer,
00049 CIpcLogOutputter& ipcLogOutputter) :
00050 m_thread(NULL),
00051 m_autoDetectCommand(autoDetectCommand),
00052 m_running(true),
00053 m_commandChanged(false),
00054 m_stdOutWrite(NULL),
00055 m_stdOutRead(NULL),
00056 m_ipcServer(ipcServer),
00057 m_ipcLogOutputter(ipcLogOutputter),
00058 m_elevateProcess(false)
00059 {
00060 }
00061
00062 CMSWindowsRelauncher::~CMSWindowsRelauncher()
00063 {
00064 }
00065
00066 void
00067 CMSWindowsRelauncher::startAsync()
00068 {
00069 m_thread = new CThread(new TMethodJob<CMSWindowsRelauncher>(
00070 this, &CMSWindowsRelauncher::mainLoop, nullptr));
00071
00072 m_outputThread = new CThread(new TMethodJob<CMSWindowsRelauncher>(
00073 this, &CMSWindowsRelauncher::outputLoop, nullptr));
00074 }
00075
00076 void
00077 CMSWindowsRelauncher::stop()
00078 {
00079 m_running = false;
00080
00081 m_thread->wait(5);
00082 delete m_thread;
00083
00084 m_outputThread->wait(5);
00085 delete m_outputThread;
00086 }
00087
00088
00089
00090 DWORD
00091 CMSWindowsRelauncher::getSessionId()
00092 {
00093 return WTSGetActiveConsoleSessionId();
00094 }
00095
00096 BOOL
00097 CMSWindowsRelauncher::isProcessInSession(const char* name, DWORD sessionId, PHANDLE process = NULL)
00098 {
00099
00100 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
00101 if (snapshot == INVALID_HANDLE_VALUE) {
00102 LOG((CLOG_ERR "could not get process snapshot (error: %i)",
00103 GetLastError()));
00104 return 0;
00105 }
00106
00107 PROCESSENTRY32 entry;
00108 entry.dwSize = sizeof(PROCESSENTRY32);
00109
00110
00111
00112 BOOL gotEntry = Process32First(snapshot, &entry);
00113 if (!gotEntry) {
00114 LOG((CLOG_ERR "could not get first process entry (error: %i)",
00115 GetLastError()));
00116 return 0;
00117 }
00118
00119
00120 std::list<std::string> nameList;
00121
00122
00123 DWORD pid = 0;
00124 while(gotEntry) {
00125
00126
00127 if (entry.th32ProcessID != 0) {
00128
00129 DWORD processSessionId;
00130 BOOL pidToSidRet = ProcessIdToSessionId(
00131 entry.th32ProcessID, &processSessionId);
00132
00133 if (!pidToSidRet) {
00134 LOG((CLOG_ERR "could not get session id for process id %i (error: %i)",
00135 entry.th32ProcessID, GetLastError()));
00136 return 0;
00137 }
00138
00139
00140 if (processSessionId == sessionId) {
00141
00142
00143 nameList.push_back(entry.szExeFile);
00144
00145 if (_stricmp(entry.szExeFile, name) == 0) {
00146 pid = entry.th32ProcessID;
00147 }
00148 }
00149 }
00150
00151
00152 gotEntry = Process32Next(snapshot, &entry);
00153 if (!gotEntry) {
00154
00155 DWORD err = GetLastError();
00156 if (err != ERROR_NO_MORE_FILES) {
00157
00158
00159 LOG((CLOG_ERR "could not get subsiquent process entry (error: %i)",
00160 GetLastError()));
00161 return 0;
00162 }
00163 }
00164 }
00165
00166 std::string nameListJoin;
00167 for(std::list<std::string>::iterator it = nameList.begin();
00168 it != nameList.end(); it++) {
00169 nameListJoin.append(*it);
00170 nameListJoin.append(", ");
00171 }
00172
00173 LOG((CLOG_DEBUG "processes in session %d: %s",
00174 sessionId, nameListJoin.c_str()));
00175
00176 CloseHandle(snapshot);
00177
00178 if (pid) {
00179 if (process != NULL) {
00180
00181
00182 LOG((CLOG_DEBUG "found %s in session %i", name, sessionId));
00183 *process = OpenProcess(MAXIMUM_ALLOWED, FALSE, pid);
00184 }
00185 return true;
00186 }
00187 else {
00188 return false;
00189 }
00190 }
00191
00192 HANDLE
00193 CMSWindowsRelauncher::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security)
00194 {
00195 HANDLE sourceToken;
00196
00197 BOOL tokenRet = OpenProcessToken(
00198 process,
00199 TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
00200 &sourceToken);
00201
00202 if (!tokenRet) {
00203 LOG((CLOG_ERR "could not open token, process handle: %d (error: %i)", process, GetLastError()));
00204 return NULL;
00205 }
00206
00207 LOG((CLOG_DEBUG "got token %i, duplicating", sourceToken));
00208
00209 HANDLE newToken;
00210 BOOL duplicateRet = DuplicateTokenEx(
00211 sourceToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, security,
00212 SecurityImpersonation, TokenPrimary, &newToken);
00213
00214 if (!duplicateRet) {
00215 LOG((CLOG_ERR "could not duplicate token %i (error: %i)",
00216 sourceToken, GetLastError()));
00217 return NULL;
00218 }
00219
00220 LOG((CLOG_DEBUG "duplicated, new token: %i", newToken));
00221 return newToken;
00222 }
00223
00224 HANDLE
00225 CMSWindowsRelauncher::getUserToken(DWORD sessionId, LPSECURITY_ATTRIBUTES security)
00226 {
00227
00228
00229
00230
00231 if (m_elevateProcess || isProcessInSession("logonui.exe", sessionId)) {
00232
00233 LOG((CLOG_DEBUG "getting elevated token, %s",
00234 (m_elevateProcess ? "elevation required" : "at login screen")));
00235
00236 HANDLE process;
00237 if (isProcessInSession("winlogon.exe", sessionId, &process)) {
00238 return duplicateProcessToken(process, security);
00239 }
00240 else {
00241 LOG((CLOG_ERR "could not find winlogon in session %i", sessionId));
00242 return NULL;
00243 }
00244 }
00245 else {
00246 LOG((CLOG_DEBUG "getting non-elevated token"));
00247 return getSessionToken(sessionId, security);
00248 }
00249 }
00250
00251 HANDLE
00252 CMSWindowsRelauncher::getSessionToken(DWORD sessionId, LPSECURITY_ATTRIBUTES security)
00253 {
00254 HANDLE sourceToken;
00255 if (!WTSQueryUserToken(sessionId, &sourceToken)) {
00256 LOG((CLOG_ERR "could not get token from session %d (error: %i)", sessionId, GetLastError()));
00257 return 0;
00258 }
00259
00260 HANDLE newToken;
00261 if (!DuplicateTokenEx(
00262 sourceToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, security,
00263 SecurityImpersonation, TokenPrimary, &newToken)) {
00264
00265 LOG((CLOG_ERR "could not duplicate token (error: %i)", GetLastError()));
00266 return 0;
00267 }
00268
00269 LOG((CLOG_DEBUG "duplicated, new token: %i", newToken));
00270 return newToken;
00271 }
00272
00273 void
00274 CMSWindowsRelauncher::mainLoop(void*)
00275 {
00276 shutdownExistingProcesses();
00277
00278 SendSas sendSasFunc = NULL;
00279 HINSTANCE sasLib = LoadLibrary("sas.dll");
00280 if (sasLib) {
00281 LOG((CLOG_DEBUG "found sas.dll"));
00282 sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS");
00283 }
00284
00285 DWORD sessionId = -1;
00286 bool launched = false;
00287
00288 SECURITY_ATTRIBUTES saAttr;
00289 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
00290 saAttr.bInheritHandle = TRUE;
00291 saAttr.lpSecurityDescriptor = NULL;
00292
00293 if (!CreatePipe(&m_stdOutRead, &m_stdOutWrite, &saAttr, 0)) {
00294 throw XArch(new XArchEvalWindows());
00295 }
00296
00297 PROCESS_INFORMATION pi;
00298 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
00299
00300 int failures = 0;
00301
00302 while (m_running) {
00303
00304 HANDLE sendSasEvent = 0;
00305 if (sasLib && sendSasFunc) {
00306
00307
00308 sendSasEvent = CreateEvent(NULL, FALSE, FALSE, "Global\\SendSAS");
00309 }
00310
00311 DWORD newSessionId = getSessionId();
00312
00313 bool running = false;
00314 if (launched) {
00315
00316 DWORD exitCode;
00317 GetExitCodeProcess(pi.hProcess, &exitCode);
00318 running = (exitCode == STILL_ACTIVE);
00319
00320 if (!running) {
00321 failures++;
00322 LOG((CLOG_INFO "detected application not running, pid=%d, failures=%d", pi.dwProcessId, failures));
00323
00324
00325 int timeout = (failures * 2) < 10 ? (failures * 2) : 10;
00326 LOG((CLOG_DEBUG "waiting, backoff period is %d seconds", timeout));
00327 ARCH->sleep(timeout);
00328
00329
00330 GetExitCodeProcess(pi.hProcess, &exitCode);
00331 running = (exitCode == STILL_ACTIVE);
00332 }
00333 else {
00334
00335 failures = 0;
00336 }
00337 }
00338
00339
00340
00341 bool sessionChanged = ((newSessionId != sessionId) && (newSessionId != -1));
00342
00343
00344 bool stoppedRunning = (launched && !running);
00345
00346 if (stoppedRunning || sessionChanged || m_commandChanged) {
00347
00348 m_commandChanged = false;
00349
00350 if (launched) {
00351 LOG((CLOG_DEBUG "closing existing process to make way for new one"));
00352 shutdownProcess(pi.hProcess, pi.dwProcessId, 20);
00353 launched = false;
00354 }
00355
00356
00357 sessionId = newSessionId;
00358
00359 SECURITY_ATTRIBUTES sa;
00360 ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
00361
00362 HANDLE userToken = getUserToken(sessionId, &sa);
00363 if (userToken == NULL) {
00364
00365 launched = true;
00366 continue;
00367 }
00368
00369 std::string cmd = command();
00370 if (cmd == "") {
00371
00372
00373
00374 LOG((CLOG_DEBUG "nothing to launch, no command specified."));
00375 continue;
00376 }
00377
00378
00379 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
00380
00381 STARTUPINFO si;
00382 ZeroMemory(&si, sizeof(STARTUPINFO));
00383 si.cb = sizeof(STARTUPINFO);
00384 si.lpDesktop = "winsta0\\Default";
00385 si.hStdError = m_stdOutWrite;
00386 si.hStdOutput = m_stdOutWrite;
00387 si.dwFlags |= STARTF_USESTDHANDLES;
00388
00389 LPVOID environment;
00390 BOOL blockRet = CreateEnvironmentBlock(&environment, userToken, FALSE);
00391 if (!blockRet) {
00392 LOG((CLOG_ERR "could not create environment block (error: %i)",
00393 GetLastError()));
00394 continue;
00395 }
00396
00397 DWORD creationFlags =
00398 NORMAL_PRIORITY_CLASS |
00399 CREATE_NO_WINDOW |
00400 CREATE_UNICODE_ENVIRONMENT;
00401
00402
00403 LOG((CLOG_INFO "starting new process"));
00404 BOOL createRet = CreateProcessAsUser(
00405 userToken, NULL, LPSTR(cmd.c_str()),
00406 &sa, NULL, TRUE, creationFlags,
00407 environment, NULL, &si, &pi);
00408
00409 DestroyEnvironmentBlock(environment);
00410 CloseHandle(userToken);
00411
00412 if (!createRet) {
00413 LOG((CLOG_ERR "could not launch (error: %i)", GetLastError()));
00414 continue;
00415 }
00416 else {
00417 LOG((CLOG_DEBUG "launched in session %i (cmd: %s)",
00418 sessionId, cmd.c_str()));
00419 launched = true;
00420 }
00421 }
00422
00423 if (sendSasEvent) {
00424
00425 if (WaitForSingleObject(sendSasEvent, 1000) == WAIT_OBJECT_0 && sendSasFunc) {
00426 LOG((CLOG_DEBUG "calling SendSAS"));
00427 sendSasFunc(FALSE);
00428 }
00429 CloseHandle(sendSasEvent);
00430 }
00431 else {
00432
00433 ARCH->sleep(1);
00434 }
00435 }
00436
00437 if (launched) {
00438 LOG((CLOG_DEBUG "terminated running process on exit"));
00439 shutdownProcess(pi.hProcess, pi.dwProcessId, 20);
00440 }
00441
00442 LOG((CLOG_DEBUG "relauncher main thread finished"));
00443 }
00444
00445 void
00446 CMSWindowsRelauncher::command(const std::string& command, bool elevate)
00447 {
00448 LOG((CLOG_INFO "service command updated"));
00449 m_command = command;
00450 m_elevateProcess = elevate;
00451 m_commandChanged = true;
00452 }
00453
00454 std::string
00455 CMSWindowsRelauncher::command() const
00456 {
00457 if (!m_autoDetectCommand) {
00458 return m_command;
00459 }
00460
00461
00462 const char* launchName = CApp::instance().argsBase().m_pname;
00463 std::string args = ARCH->commandLine();
00464
00465
00466 std::stringstream cmdTemp;
00467 cmdTemp << launchName << args;
00468
00469 std::string cmd = cmdTemp.str();
00470
00471 size_t i;
00472 std::string find = "--relaunch";
00473 while((i = cmd.find(find)) != std::string::npos) {
00474 cmd.replace(i, find.length(), "");
00475 }
00476
00477 return cmd;
00478 }
00479
00480 void
00481 CMSWindowsRelauncher::outputLoop(void*)
00482 {
00483
00484 CHAR buffer[kOutputBufferSize + 1];
00485
00486 while (m_running) {
00487
00488 DWORD bytesRead;
00489 BOOL success = ReadFile(m_stdOutRead, buffer, kOutputBufferSize, &bytesRead, NULL);
00490
00491
00492
00493 if (!success || bytesRead == 0) {
00494 ARCH->sleep(1);
00495 }
00496 else {
00497 buffer[bytesRead] = '\0';
00498
00499
00500
00501 m_ipcLogOutputter.write(kINFO, buffer, true);
00502 }
00503
00504 }
00505 }
00506
00507 void
00508 CMSWindowsRelauncher::shutdownProcess(HANDLE handle, DWORD pid, int timeout)
00509 {
00510 DWORD exitCode;
00511 GetExitCodeProcess(handle, &exitCode);
00512 if (exitCode != STILL_ACTIVE)
00513 return;
00514
00515 CIpcShutdownMessage shutdown;
00516 m_ipcServer.send(shutdown, kIpcClientNode);
00517
00518
00519 double start = ARCH->time();
00520 while (true)
00521 {
00522 GetExitCodeProcess(handle, &exitCode);
00523 if (exitCode != STILL_ACTIVE) {
00524
00525 LOG((CLOG_INFO "process %d was shutdown gracefully", pid));
00526 break;
00527 }
00528 else {
00529
00530 double elapsed = (ARCH->time() - start);
00531 if (elapsed > timeout) {
00532
00533
00534
00535
00536 LOG((CLOG_WARN "shutdown timed out after %d secs, forcefully terminating", (int)elapsed));
00537 TerminateProcess(handle, kExitSuccess);
00538 break;
00539 }
00540
00541 ARCH->sleep(1);
00542 }
00543 }
00544 }
00545
00546 void
00547 CMSWindowsRelauncher::shutdownExistingProcesses()
00548 {
00549
00550 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
00551 if (snapshot == INVALID_HANDLE_VALUE) {
00552 LOG((CLOG_ERR "could not get process snapshot (error: %i)",
00553 GetLastError()));
00554 return;
00555 }
00556
00557 PROCESSENTRY32 entry;
00558 entry.dwSize = sizeof(PROCESSENTRY32);
00559
00560
00561
00562 BOOL gotEntry = Process32First(snapshot, &entry);
00563 if (!gotEntry) {
00564 LOG((CLOG_ERR "could not get first process entry (error: %i)",
00565 GetLastError()));
00566 return;
00567 }
00568
00569
00570 DWORD pid = 0;
00571 while(gotEntry) {
00572
00573
00574 if (entry.th32ProcessID != 0) {
00575
00576 if (_stricmp(entry.szExeFile, "synergyc.exe") == 0 ||
00577 _stricmp(entry.szExeFile, "synergys.exe") == 0) {
00578
00579 HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
00580 shutdownProcess(handle, entry.th32ProcessID, 10);
00581 }
00582 }
00583
00584
00585 gotEntry = Process32Next(snapshot, &entry);
00586 if (!gotEntry) {
00587
00588 DWORD err = GetLastError();
00589 if (err != ERROR_NO_MORE_FILES) {
00590
00591
00592 LOG((CLOG_ERR "could not get subsiquent process entry (error: %i)",
00593 GetLastError()));
00594 return;
00595 }
00596 }
00597 }
00598
00599 CloseHandle(snapshot);
00600 }