Added get_debug_token().
[nssm.git] / process.cpp
1 #include "nssm.h"\r
2 \r
3 extern imports_t imports;\r
4 \r
5 HANDLE get_debug_token() {\r
6   long error;\r
7   HANDLE token;\r
8   if (! OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, &token)) {\r
9     error = GetLastError();\r
10     if (error == ERROR_NO_TOKEN) {\r
11       (void) ImpersonateSelf(SecurityImpersonation);\r
12       (void) OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, &token);\r
13     }\r
14   }\r
15   if (! token) return INVALID_HANDLE_VALUE;\r
16 \r
17   TOKEN_PRIVILEGES privileges, old;\r
18   unsigned long size = sizeof(TOKEN_PRIVILEGES);\r
19   LUID luid;\r
20   if (! LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {\r
21     CloseHandle(token);\r
22     return INVALID_HANDLE_VALUE;\r
23   }\r
24 \r
25   privileges.PrivilegeCount = 1;\r
26   privileges.Privileges[0].Luid = luid;\r
27   privileges.Privileges[0].Attributes = 0;\r
28 \r
29   if (! AdjustTokenPrivileges(token, false, &privileges, size, &old, &size)) {\r
30     CloseHandle(token);\r
31     return INVALID_HANDLE_VALUE;\r
32   }\r
33 \r
34   old.PrivilegeCount = 1;\r
35   old.Privileges[0].Luid = luid;\r
36   old.Privileges[0].Attributes |= SE_PRIVILEGE_ENABLED;\r
37 \r
38   if (! AdjustTokenPrivileges(token, false, &old, size, NULL, NULL)) {\r
39     CloseHandle(token);\r
40     return INVALID_HANDLE_VALUE;\r
41   }\r
42 \r
43   return token;\r
44 }\r
45 \r
46 void service_kill_t(nssm_service_t *service, kill_t *k) {\r
47   if (! service) return;\r
48   if (! k) return;\r
49 \r
50   ZeroMemory(k, sizeof(*k));\r
51   k->name = service->name;\r
52   k->process_handle = service->process_handle;\r
53   k->pid = service->pid;\r
54   k->exitcode = service->exitcode;\r
55   k->stop_method = service->stop_method;\r
56   k->kill_console_delay = service->kill_console_delay;\r
57   k->kill_window_delay = service->kill_window_delay;\r
58   k->kill_threads_delay = service->kill_threads_delay;\r
59   k->status_handle = service->status_handle;\r
60   k->status = &service->status;\r
61   k->creation_time = service->creation_time;\r
62   k->exit_time = service->exit_time;\r
63 }\r
64 \r
65 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {\r
66   FILETIME creation_time, exit_time, kernel_time, user_time;\r
67 \r
68   if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {\r
69     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);\r
70     return 1;\r
71   }\r
72 \r
73   memmove(ft, &creation_time, sizeof(creation_time));\r
74 \r
75   return 0;\r
76 }\r
77 \r
78 int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {\r
79   FILETIME creation_time, exit_time, kernel_time, user_time;\r
80 \r
81   if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {\r
82     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);\r
83     return 1;\r
84   }\r
85 \r
86   if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2;\r
87   memmove(ft, &exit_time, sizeof(exit_time));\r
88 \r
89   return 0;\r
90 }\r
91 \r
92 int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) {\r
93   /* Check parent process ID matches. */\r
94   if (pe->th32ParentProcessID != ppid) return 1;\r
95 \r
96   /*\r
97     Process IDs can be reused so do a sanity check by making sure the child\r
98     has been running for less time than the parent.\r
99     Though unlikely, it's possible that the parent exited and its process ID\r
100     was already reused, so we'll also compare against its exit time.\r
101   */\r
102   HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);\r
103   if (! process_handle) {\r
104     TCHAR pid_string[16];\r
105     _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);\r
106     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);\r
107     return 2;\r
108   }\r
109 \r
110   FILETIME ft;\r
111   if (get_process_creation_time(process_handle, &ft)) {\r
112     CloseHandle(process_handle);\r
113     return 3;\r
114   }\r
115 \r
116   CloseHandle(process_handle);\r
117 \r
118   /* Verify that the parent's creation time is not later. */\r
119   if (CompareFileTime(&k->creation_time, &ft) > 0) return 4;\r
120 \r
121   /* Verify that the parent's exit time is not earlier. */\r
122   if (CompareFileTime(&k->exit_time, &ft) < 0) return 5;\r
123 \r
124   return 0;\r
125 }\r
126 \r
127 /* Send some window messages and hope the window respects one or more. */\r
128 int CALLBACK kill_window(HWND window, LPARAM arg) {\r
129   kill_t *k = (kill_t *) arg;\r
130 \r
131   unsigned long pid;\r
132   if (! GetWindowThreadProcessId(window, &pid)) return 1;\r
133   if (pid != k->pid) return 1;\r
134 \r
135   /* First try sending WM_CLOSE to request that the window close. */\r
136   k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);\r
137 \r
138   /*\r
139     Then tell the window that the user is logging off and it should exit\r
140     without worrying about saving any data.\r
141   */\r
142   k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);\r
143 \r
144   return 1;\r
145 }\r
146 \r
147 /*\r
148   Try to post a message to the message queues of threads associated with the\r
149   given process ID.  Not all threads have message queues so there's no\r
150   guarantee of success, and we don't want to be left waiting for unsignalled\r
151   processes so this function returns only true if at least one thread was\r
152   successfully prodded.\r
153 */\r
154 int kill_threads(nssm_service_t *service, kill_t *k) {\r
155   int ret = 0;\r
156 \r
157   /* Get a snapshot of all threads in the system. */\r
158   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);\r
159   if (snapshot == INVALID_HANDLE_VALUE) {\r
160     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0);\r
161     return 0;\r
162   }\r
163 \r
164   THREADENTRY32 te;\r
165   ZeroMemory(&te, sizeof(te));\r
166   te.dwSize = sizeof(te);\r
167 \r
168   if (! Thread32First(snapshot, &te)) {\r
169     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);\r
170     CloseHandle(snapshot);\r
171     return 0;\r
172   }\r
173 \r
174   /* This thread belongs to the doomed process so signal it. */\r
175   if (te.th32OwnerProcessID == k->pid) {\r
176     ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);\r
177   }\r
178 \r
179   while (true) {\r
180     /* Try to get the next thread. */\r
181     if (! Thread32Next(snapshot, &te)) {\r
182       unsigned long error = GetLastError();\r
183       if (error == ERROR_NO_MORE_FILES) break;\r
184       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);\r
185       CloseHandle(snapshot);\r
186       return ret;\r
187     }\r
188 \r
189     if (te.th32OwnerProcessID == k->pid) {\r
190       ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);\r
191     }\r
192   }\r
193 \r
194   CloseHandle(snapshot);\r
195 \r
196   return ret;\r
197 }\r
198 \r
199 int kill_threads(kill_t *k) {\r
200   return kill_threads(NULL, k);\r
201 }\r
202 \r
203 /* Give the process a chance to die gracefully. */\r
204 int kill_process(nssm_service_t *service, kill_t *k) {\r
205   if (! k) return 1;\r
206 \r
207   unsigned long ret;\r
208   if (GetExitCodeProcess(k->process_handle, &ret)) {\r
209     if (ret != STILL_ACTIVE) return 1;\r
210   }\r
211 \r
212   /* Try to send a Control-C event to the console. */\r
213   if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) {\r
214     if (! kill_console(k)) return 1;\r
215   }\r
216 \r
217   /*\r
218     Try to post messages to the windows belonging to the given process ID.\r
219     If the process is a console application it won't have any windows so there's\r
220     no guarantee of success.\r
221   */\r
222   if (k->stop_method & NSSM_STOP_METHOD_WINDOW) {\r
223     EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k);\r
224     if (k->signalled) {\r
225       if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1;\r
226       k->signalled = 0;\r
227     }\r
228   }\r
229 \r
230   /*\r
231     Try to post messages to any thread message queues associated with the\r
232     process.  Console applications might have them (but probably won't) so\r
233     there's still no guarantee of success.\r
234   */\r
235   if (k->stop_method & NSSM_STOP_METHOD_THREADS) {\r
236     if (kill_threads(k)) {\r
237       if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1;\r
238     }\r
239   }\r
240 \r
241   /* We tried being nice.  Time for extreme prejudice. */\r
242   if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) {\r
243     return TerminateProcess(k->process_handle, k->exitcode);\r
244   }\r
245 \r
246   return 0;\r
247 }\r
248 \r
249 int kill_process(kill_t *k) {\r
250   return kill_process(NULL, k);\r
251 }\r
252 \r
253 /* Simulate a Control-C event to our console (shared with the app). */\r
254 int kill_console(nssm_service_t *service, kill_t *k) {\r
255   unsigned long ret;\r
256 \r
257   if (! k) return 1;\r
258 \r
259   /* Check we loaded AttachConsole(). */\r
260   if (! imports.AttachConsole) return 4;\r
261 \r
262   /* Try to attach to the process's console. */\r
263   if (! imports.AttachConsole(k->pid)) {\r
264     ret = GetLastError();\r
265 \r
266     switch (ret) {\r
267       case ERROR_INVALID_HANDLE:\r
268         /* The app doesn't have a console. */\r
269         return 1;\r
270 \r
271       case ERROR_GEN_FAILURE:\r
272         /* The app already exited. */\r
273         return 2;\r
274 \r
275       case ERROR_ACCESS_DENIED:\r
276       default:\r
277         /* We already have a console. */\r
278         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0);\r
279         return 3;\r
280     }\r
281   }\r
282 \r
283   /* Ignore the event ourselves. */\r
284   ret = 0;\r
285   BOOL ignored = SetConsoleCtrlHandler(0, TRUE);\r
286   if (! ignored) {\r
287     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);\r
288     ret = 4;\r
289   }\r
290 \r
291   /* Send the event. */\r
292   if (! ret) {\r
293     if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {\r
294       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0);\r
295       ret = 5;\r
296     }\r
297   }\r
298 \r
299   /* Detach from the console. */\r
300   if (! FreeConsole()) {\r
301     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0);\r
302   }\r
303 \r
304   /* Wait for process to exit. */\r
305   if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6;\r
306 \r
307   /* Remove our handler. */\r
308   if (ignored && ! SetConsoleCtrlHandler(0, FALSE)) {\r
309     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);\r
310   }\r
311 \r
312   return ret;\r
313 }\r
314 \r
315 int kill_console(kill_t *k) {\r
316   return kill_console(NULL, k);\r
317 }\r
318 \r
319 void walk_process_tree(nssm_service_t *service, walk_function_t fn, kill_t *k, unsigned long ppid) {\r
320   if (! k) return;\r
321   /* Shouldn't happen unless the service failed to start. */\r
322   if (! k->pid) return; /* XXX: needed? */\r
323   unsigned long pid = k->pid;\r
324 \r
325   TCHAR pid_string[16], code[16];\r
326   _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);\r
327   _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode);\r
328   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0);\r
329 \r
330   /* We will need a process handle in order to call TerminateProcess() later. */\r
331   HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);\r
332   if (process_handle) {\r
333     /* Kill this process first, then its descendents. */\r
334     TCHAR ppid_string[16];\r
335     _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);\r
336     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0);\r
337     k->process_handle = process_handle; /* XXX: open directly? */\r
338     if (! fn(service, k)) {\r
339       /* Maybe it already died. */\r
340       unsigned long ret;\r
341       if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {\r
342         if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);\r
343         else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);\r
344       }\r
345     }\r
346 \r
347     CloseHandle(process_handle);\r
348   }\r
349   else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);\r
350 \r
351   /* Get a snapshot of all processes in the system. */\r
352   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\r
353   if (snapshot == INVALID_HANDLE_VALUE) {\r
354     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0);\r
355     return;\r
356   }\r
357 \r
358   PROCESSENTRY32 pe;\r
359   ZeroMemory(&pe, sizeof(pe));\r
360   pe.dwSize = sizeof(pe);\r
361 \r
362   if (! Process32First(snapshot, &pe)) {\r
363     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);\r
364     CloseHandle(snapshot);\r
365     return;\r
366   }\r
367 \r
368   /* This is a child of the doomed process so kill it. */\r
369   if (! check_parent(k, &pe, pid)) {\r
370     k->pid = pe.th32ProcessID;\r
371     walk_process_tree(service, fn, k, ppid);\r
372   }\r
373   k->pid = pid;\r
374 \r
375   while (true) {\r
376     /* Try to get the next process. */\r
377     if (! Process32Next(snapshot, &pe)) {\r
378       unsigned long ret = GetLastError();\r
379       if (ret == ERROR_NO_MORE_FILES) break;\r
380       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);\r
381       CloseHandle(snapshot);\r
382       return;\r
383     }\r
384 \r
385     if (! check_parent(k, &pe, pid)) {\r
386       k->pid = pe.th32ProcessID;\r
387       walk_process_tree(service, fn, k, ppid);\r
388     }\r
389     k->pid = pid;\r
390   }\r
391 \r
392   CloseHandle(snapshot);\r
393 }\r
394 \r
395 void kill_process_tree(kill_t *k, unsigned long ppid) {\r
396   return walk_process_tree(NULL, kill_process, k, ppid);\r
397 }\r