Grant SeServiceLogonRight.
[nssm.git] / service.cpp
1 #include "nssm.h"\r
2 \r
3 /* This is explicitly a wide string. */\r
4 #define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"\r
5 \r
6 extern const TCHAR *exit_action_strings[];\r
7 \r
8 bool is_admin;\r
9 bool use_critical_section;\r
10 \r
11 extern imports_t imports;\r
12 \r
13 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };\r
14 \r
15 static inline int throttle_milliseconds(unsigned long throttle) {\r
16   /* pow() operates on doubles. */\r
17   int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
18   return ret * 1000;\r
19 }\r
20 \r
21 /*\r
22   Wrapper to be called in a new thread so that we can acknowledge a STOP\r
23   control immediately.\r
24 */\r
25 static unsigned long WINAPI shutdown_service(void *arg) {\r
26   return stop_service((nssm_service_t *) arg, 0, true, true);\r
27 }\r
28 \r
29 /* Connect to the service manager */\r
30 SC_HANDLE open_service_manager() {\r
31   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);\r
32   if (! ret) {\r
33     if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
34     return 0;\r
35   }\r
36 \r
37   return ret;\r
38 }\r
39 \r
40 static int grant_logon_as_service(const TCHAR *username) {\r
41   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
42 \r
43   /* Open Policy object. */\r
44   LSA_OBJECT_ATTRIBUTES attributes;\r
45   ZeroMemory(&attributes, sizeof(attributes));\r
46 \r
47   LSA_HANDLE policy;\r
48 \r
49   NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);\r
50   if (status) {\r
51     print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));\r
52     return 1;\r
53   }\r
54 \r
55   /* Look up SID for the account. */\r
56   LSA_UNICODE_STRING lsa_username;\r
57 #ifdef UNICODE\r
58   lsa_username.Buffer = (wchar_t *) username;\r
59   lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);\r
60   lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);\r
61 #else\r
62   size_t buflen;\r
63   mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);\r
64   lsa_username.MaximumLength = buflen * sizeof(wchar_t);\r
65   lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);\r
66   lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);\r
67   if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);\r
68   else {\r
69     LsaClose(policy);\r
70     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));\r
71     return 2;\r
72   }\r
73 #endif\r
74 \r
75   LSA_REFERENCED_DOMAIN_LIST *translated_domains;\r
76   LSA_TRANSLATED_SID *translated_sid;\r
77   status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);\r
78 #ifndef UNICODE\r
79   HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);\r
80 #endif\r
81   if (status) {\r
82     LsaFreeMemory(translated_domains);\r
83     LsaFreeMemory(translated_sid);\r
84     LsaClose(policy);\r
85     print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));\r
86     return 3;\r
87   }\r
88 \r
89   if (translated_sid->Use != SidTypeUser) {\r
90     LsaFreeMemory(translated_domains);\r
91     LsaFreeMemory(translated_sid);\r
92     LsaClose(policy);\r
93     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
94     return 4;\r
95   }\r
96 \r
97   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];\r
98   if (! trust || ! IsValidSid(trust->Sid)) {\r
99     LsaFreeMemory(translated_domains);\r
100     LsaFreeMemory(translated_sid);\r
101     LsaClose(policy);\r
102     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
103     return 4;\r
104   }\r
105 \r
106   /* GetSidSubAuthority*() return pointers! */\r
107   unsigned char *n = GetSidSubAuthorityCount(trust->Sid);\r
108 \r
109   /* Convert translated SID to SID. */\r
110   SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));\r
111   if (! sid) {\r
112     LsaFreeMemory(translated_domains);\r
113     LsaFreeMemory(translated_sid);\r
114     LsaClose(policy);\r
115     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));\r
116     return 4;\r
117   }\r
118 \r
119   unsigned long error;\r
120   if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {\r
121     error = GetLastError();\r
122     HeapFree(GetProcessHeap(), 0, sid);\r
123     LsaFreeMemory(translated_domains);\r
124     LsaFreeMemory(translated_sid);\r
125     LsaClose(policy);\r
126     print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));\r
127     return 5;\r
128   }\r
129 \r
130   for (unsigned char i = 0; i <= *n; i++) {\r
131     unsigned long *sub = GetSidSubAuthority(sid, i);\r
132     if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);\r
133     else *sub = translated_sid->RelativeId;\r
134   }\r
135 \r
136   LsaFreeMemory(translated_domains);\r
137   LsaFreeMemory(translated_sid);\r
138 \r
139   /* Check if the SID has the "Log on as a service" right. */\r
140   LSA_UNICODE_STRING lsa_right;\r
141   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;\r
142   lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);\r
143   lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);\r
144 \r
145   LSA_UNICODE_STRING *rights;\r
146   unsigned long count = ~0;\r
147   status = LsaEnumerateAccountRights(policy, sid, &rights, &count);\r
148   if (status) {\r
149     /*\r
150       If the account has no rights set LsaEnumerateAccountRights() will return\r
151       STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.\r
152     */\r
153     error = LsaNtStatusToWinError(status);\r
154     if (error != ERROR_FILE_NOT_FOUND) {\r
155       HeapFree(GetProcessHeap(), 0, sid);\r
156       LsaClose(policy);\r
157       print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));\r
158       return 4;\r
159     }\r
160   }\r
161 \r
162   for (unsigned long i = 0; i < count; i++) {\r
163     if (rights[i].Length != lsa_right.Length) continue;\r
164     if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;\r
165     /* The SID has the right. */\r
166     HeapFree(GetProcessHeap(), 0, sid);\r
167     LsaFreeMemory(rights);\r
168     LsaClose(policy);\r
169     return 0;\r
170   }\r
171   LsaFreeMemory(rights);\r
172 \r
173   /* Add the right. */\r
174   status = LsaAddAccountRights(policy, sid, &lsa_right, 1);\r
175   HeapFree(GetProcessHeap(), 0, sid);\r
176   LsaClose(policy);\r
177   if (status) {\r
178     print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
179     return 5;\r
180   }\r
181 \r
182   print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);\r
183   return 0;\r
184 }\r
185 \r
186 /* Set default values which aren't zero. */\r
187 void set_nssm_service_defaults(nssm_service_t *service) {\r
188   if (! service) return;\r
189 \r
190   service->type = SERVICE_WIN32_OWN_PROCESS;\r
191   service->stdin_sharing = NSSM_STDIN_SHARING;\r
192   service->stdin_disposition = NSSM_STDIN_DISPOSITION;\r
193   service->stdin_flags = NSSM_STDIN_FLAGS;\r
194   service->stdout_sharing = NSSM_STDOUT_SHARING;\r
195   service->stdout_disposition = NSSM_STDOUT_DISPOSITION;\r
196   service->stdout_flags = NSSM_STDOUT_FLAGS;\r
197   service->stderr_sharing = NSSM_STDERR_SHARING;\r
198   service->stderr_disposition = NSSM_STDERR_DISPOSITION;\r
199   service->stderr_flags = NSSM_STDERR_FLAGS;\r
200   service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;\r
201   service->stop_method = ~0;\r
202   service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
203   service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
204   service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
205 }\r
206 \r
207 /* Allocate and zero memory for a service. */\r
208 nssm_service_t *alloc_nssm_service() {\r
209   nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));\r
210   if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);\r
211   return service;\r
212 }\r
213 \r
214 /* Free memory for a service. */\r
215 void cleanup_nssm_service(nssm_service_t *service) {\r
216   if (! service) return;\r
217   if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
218   if (service->password) {\r
219     SecureZeroMemory(service->password, service->passwordlen);\r
220     HeapFree(GetProcessHeap(), 0, service->password);\r
221   }\r
222   if (service->env) HeapFree(GetProcessHeap(), 0, service->env);\r
223   if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);\r
224   if (service->handle) CloseServiceHandle(service->handle);\r
225   if (service->process_handle) CloseHandle(service->process_handle);\r
226   if (service->wait_handle) UnregisterWait(service->process_handle);\r
227   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
228   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
229   HeapFree(GetProcessHeap(), 0, service);\r
230 }\r
231 \r
232 /* About to install the service */\r
233 int pre_install_service(int argc, TCHAR **argv) {\r
234   nssm_service_t *service = alloc_nssm_service();\r
235   set_nssm_service_defaults(service);\r
236   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
237 \r
238   /* Show the dialogue box if we didn't give the service name and path */\r
239   if (argc < 2) return nssm_gui(IDD_INSTALL, service);\r
240 \r
241   if (! service) {\r
242     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));\r
243     return 1;\r
244   }\r
245   _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);\r
246 \r
247   /* Arguments are optional */\r
248   size_t flagslen = 0;\r
249   size_t s = 0;\r
250   int i;\r
251   for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;\r
252   if (! flagslen) flagslen = 1;\r
253   if (flagslen > _countof(service->flags)) {\r
254     print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);\r
255     return 2;\r
256   }\r
257 \r
258   for (i = 2; i < argc; i++) {\r
259     size_t len = _tcslen(argv[i]);\r
260     memmove(service->flags + s, argv[i], len * sizeof(TCHAR));\r
261     s += len;\r
262     if (i < argc - 1) service->flags[s++] = _T(' ');\r
263   }\r
264 \r
265   /* Work out directory name */\r
266   _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
267   strip_basename(service->dir);\r
268 \r
269   int ret = install_service(service);\r
270   cleanup_nssm_service(service);\r
271   return ret;\r
272 }\r
273 \r
274 /* About to edit the service. */\r
275 int pre_edit_service(int argc, TCHAR **argv) {\r
276   /* Require service name. */\r
277   if (argc < 1) return usage(1);\r
278 \r
279   nssm_service_t *service = alloc_nssm_service();\r
280   _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
281 \r
282   /* Open service manager */\r
283   SC_HANDLE services = open_service_manager();\r
284   if (! services) {\r
285     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
286     return 2;\r
287   }\r
288 \r
289   /* Try to open the service */\r
290   service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
291   if (! service->handle) {\r
292     CloseServiceHandle(services);\r
293     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
294     return 3;\r
295   }\r
296 \r
297   /* Get system details. */\r
298   unsigned long bufsize;\r
299   unsigned long error;\r
300   QUERY_SERVICE_CONFIG *qsc;\r
301 \r
302   QueryServiceConfig(service->handle, 0, 0, &bufsize);\r
303   error = GetLastError();\r
304   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
305     qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);\r
306     if (! qsc) {\r
307       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("pre_edit_service()"), 0);\r
308       return 4;\r
309     }\r
310   }\r
311   else {\r
312     CloseHandle(service->handle);\r
313     CloseServiceHandle(services);\r
314     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(error), 0);\r
315     return 4;\r
316   }\r
317 \r
318   if (! QueryServiceConfig(service->handle, qsc, bufsize, &bufsize)) {\r
319     HeapFree(GetProcessHeap(), 0, qsc);\r
320     CloseHandle(service->handle);\r
321     CloseServiceHandle(services);\r
322     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(GetLastError()), 0);\r
323     return 4;\r
324   }\r
325 \r
326   service->type = qsc->dwServiceType;\r
327   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
328     HeapFree(GetProcessHeap(), 0, qsc);\r
329     CloseHandle(service->handle);\r
330     CloseServiceHandle(services);\r
331     print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);\r
332     return 3;\r
333   }\r
334 \r
335   switch (qsc->dwStartType) {\r
336     case SERVICE_DEMAND_START: service->startup = NSSM_STARTUP_MANUAL; break;\r
337     case SERVICE_DISABLED: service->startup = NSSM_STARTUP_DISABLED; break;\r
338     default: service->startup = NSSM_STARTUP_AUTOMATIC;\r
339   }\r
340   if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {\r
341     size_t len = _tcslen(qsc->lpServiceStartName);\r
342     service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));\r
343     if (service->username) {\r
344       memmove(service->username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));\r
345       service->usernamelen = (unsigned long) len;\r
346     }\r
347     else {\r
348       HeapFree(GetProcessHeap(), 0, qsc);\r
349       CloseHandle(service->handle);\r
350       CloseServiceHandle(services);\r
351       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("pre_edit_service()"));\r
352       return 4;\r
353     }\r
354   }\r
355   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
356 \r
357   /* Get the canonical service name. We open it case insensitively. */\r
358   bufsize = _countof(service->name);\r
359   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
360 \r
361   /* Remember the executable in case it isn't NSSM. */\r
362   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
363   HeapFree(GetProcessHeap(), 0, qsc);\r
364 \r
365   /* Get extended system details. */\r
366   if (service->startup == NSSM_STARTUP_AUTOMATIC) {\r
367     QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);\r
368     error = GetLastError();\r
369     if (error == ERROR_INSUFFICIENT_BUFFER) {\r
370       SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
371       if (! info) {\r
372         CloseHandle(service->handle);\r
373         CloseServiceHandle(services);\r
374         print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("pre_edit_service()"));\r
375         return 5;\r
376       }\r
377 \r
378       if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {\r
379         if (info->fDelayedAutostart) service->startup = NSSM_STARTUP_DELAYED;\r
380       }\r
381       else {\r
382         error = GetLastError();\r
383         if (error != ERROR_INVALID_LEVEL) {\r
384           CloseHandle(service->handle);\r
385           CloseServiceHandle(services);\r
386           print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
387           return 5;\r
388         }\r
389       }\r
390     }\r
391     else if (error != ERROR_INVALID_LEVEL) {\r
392       CloseHandle(service->handle);\r
393       CloseServiceHandle(services);\r
394       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));\r
395       return 5;\r
396     }\r
397   }\r
398 \r
399   QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);\r
400   error = GetLastError();\r
401   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
402     SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
403     if (! description) {\r
404       CloseHandle(service->handle);\r
405       CloseServiceHandle(services);\r
406       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("pre_edit_service()"));\r
407       return 6;\r
408     }\r
409 \r
410     if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {\r
411      if (description->lpDescription) _sntprintf_s(service->description, _countof(service->description), _TRUNCATE, _T("%s"), description->lpDescription);\r
412       HeapFree(GetProcessHeap(), 0, description);\r
413     }\r
414     else {\r
415       HeapFree(GetProcessHeap(), 0, description);\r
416       CloseHandle(service->handle);\r
417       CloseServiceHandle(services);\r
418       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
419       return 6;\r
420     }\r
421   }\r
422   else {\r
423     CloseHandle(service->handle);\r
424     CloseServiceHandle(services);\r
425     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
426     return 6;\r
427   }\r
428 \r
429   /* Get NSSM details. */\r
430   get_parameters(service, 0);\r
431 \r
432   CloseServiceHandle(services);\r
433 \r
434   if (! service->exe[0]) {\r
435     print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
436     service->native = true;\r
437   }\r
438 \r
439   nssm_gui(IDD_EDIT, service);\r
440 \r
441   return 0;\r
442 }\r
443 \r
444 /* About to remove the service */\r
445 int pre_remove_service(int argc, TCHAR **argv) {\r
446   nssm_service_t *service = alloc_nssm_service();\r
447   set_nssm_service_defaults(service);\r
448   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
449 \r
450   /* Show dialogue box if we didn't pass service name and "confirm" */\r
451   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
452   if (str_equiv(argv[1], _T("confirm"))) {\r
453     int ret = remove_service(service);\r
454     cleanup_nssm_service(service);\r
455     return ret;\r
456   }\r
457   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
458   return 100;\r
459 }\r
460 \r
461 /* Install the service */\r
462 int install_service(nssm_service_t *service) {\r
463   if (! service) return 1;\r
464 \r
465   /* Open service manager */\r
466   SC_HANDLE services = open_service_manager();\r
467   if (! services) {\r
468     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
469     cleanup_nssm_service(service);\r
470     return 2;\r
471   }\r
472 \r
473   /* Get path of this program */\r
474   GetModuleFileName(0, service->image, _countof(service->image));\r
475 \r
476   /* Create the service - settings will be changed in edit_service() */\r
477   service->handle = CreateService(services, service->name, service->name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);\r
478   if (! service->handle) {\r
479     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);\r
480     CloseServiceHandle(services);\r
481     return 5;\r
482   }\r
483 \r
484   if (edit_service(service, false)) {\r
485     DeleteService(service->handle);\r
486     CloseServiceHandle(services);\r
487     return 6;\r
488   }\r
489 \r
490   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
491 \r
492   /* Cleanup */\r
493   CloseServiceHandle(services);\r
494 \r
495   return 0;\r
496 }\r
497 \r
498 /* Edit the service. */\r
499 int edit_service(nssm_service_t *service, bool editing) {\r
500   if (! service) return 1;\r
501 \r
502   /*\r
503     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
504     and SERVICE_INTERACTIVE_PROCESS.\r
505   */\r
506   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
507   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
508 \r
509   /* Startup type. */\r
510   unsigned long startup;\r
511   switch (service->startup) {\r
512     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
513     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
514     default: startup = SERVICE_AUTO_START;\r
515   }\r
516 \r
517   /* Display name. */\r
518   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
519 \r
520   /*\r
521     Username must be NULL if we aren't changing or an account name.\r
522     We must explicitly user LOCALSYSTEM to change it when we are editing.\r
523     Password must be NULL if we aren't changing, a password or "".\r
524     Empty passwords are valid but we won't allow them in the GUI.\r
525   */\r
526   TCHAR *username = 0;\r
527   TCHAR *password = 0;\r
528   if (service->usernamelen) {\r
529     username = service->username;\r
530     if (service->passwordlen) password = service->password;\r
531     else password = _T("");\r
532   }\r
533   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
534 \r
535   if (grant_logon_as_service(username)) {\r
536     print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
537     return 5;\r
538   }\r
539 \r
540   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {\r
541     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
542     return 5;\r
543   }\r
544 \r
545   if (service->description[0] || editing) {\r
546     SERVICE_DESCRIPTION description;\r
547     ZeroMemory(&description, sizeof(description));\r
548     if (service->description[0]) description.lpDescription = service->description;\r
549     else description.lpDescription = 0;\r
550     if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {\r
551       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);\r
552     }\r
553   }\r
554 \r
555   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
556   ZeroMemory(&delayed, sizeof(delayed));\r
557   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
558   else delayed.fDelayedAutostart = 0;\r
559   /* Delayed startup isn't supported until Vista. */\r
560   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
561     unsigned long error = GetLastError();\r
562     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
563     if (error != ERROR_INVALID_LEVEL) {\r
564       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
565     }\r
566   }\r
567 \r
568   /* Don't mess with parameters which aren't ours. */\r
569   if (! service->native) {\r
570     /* Now we need to put the parameters into the registry */\r
571     if (create_parameters(service, editing)) {\r
572       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
573       return 6;\r
574     }\r
575 \r
576     set_service_recovery(service);\r
577   }\r
578 \r
579   return 0;\r
580 }\r
581 \r
582 /* Remove the service */\r
583 int remove_service(nssm_service_t *service) {\r
584   if (! service) return 1;\r
585 \r
586   /* Open service manager */\r
587   SC_HANDLE services = open_service_manager();\r
588   if (! services) {\r
589     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
590     return 2;\r
591   }\r
592 \r
593   /* Try to open the service */\r
594   service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
595   if (! service->handle) {\r
596     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
597     CloseServiceHandle(services);\r
598     return 3;\r
599   }\r
600 \r
601   /* Get the canonical service name. We open it case insensitively. */\r
602   unsigned long bufsize = _countof(service->displayname);\r
603   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
604   bufsize = _countof(service->name);\r
605   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
606 \r
607   /* Try to delete the service */\r
608   if (! DeleteService(service->handle)) {\r
609     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
610     CloseServiceHandle(services);\r
611     return 4;\r
612   }\r
613 \r
614   /* Cleanup */\r
615   CloseServiceHandle(services);\r
616 \r
617   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
618   return 0;\r
619 }\r
620 \r
621 /* Service initialisation */\r
622 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
623   nssm_service_t *service = alloc_nssm_service();\r
624   if (! service) return;\r
625 \r
626   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
627     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
628     return;\r
629   }\r
630 \r
631   /* We can use a condition variable in a critical section on Vista or later. */\r
632   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
633   else use_critical_section = false;\r
634 \r
635   /* Initialise status */\r
636   ZeroMemory(&service->status, sizeof(service->status));\r
637   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
638   service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
639   service->status.dwWin32ExitCode = NO_ERROR;\r
640   service->status.dwServiceSpecificExitCode = 0;\r
641   service->status.dwCheckPoint = 0;\r
642   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
643 \r
644   /* Signal we AREN'T running the server */\r
645   service->process_handle = 0;\r
646   service->pid = 0;\r
647 \r
648   /* Register control handler */\r
649   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
650   if (! service->status_handle) {\r
651     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
652     return;\r
653   }\r
654 \r
655   log_service_control(service->name, 0, true);\r
656 \r
657   service->status.dwCurrentState = SERVICE_START_PENDING;\r
658   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
659   SetServiceStatus(service->status_handle, &service->status);\r
660 \r
661   if (is_admin) {\r
662     /* Try to create the exit action parameters; we don't care if it fails */\r
663     create_exit_action(service->name, exit_action_strings[0], false);\r
664 \r
665     SC_HANDLE services = open_service_manager();\r
666     if (services) {\r
667       service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
668       set_service_recovery(service);\r
669       CloseServiceHandle(services);\r
670     }\r
671   }\r
672 \r
673   /* Used for signalling a resume if the service pauses when throttled. */\r
674   if (use_critical_section) {\r
675     InitializeCriticalSection(&service->throttle_section);\r
676     service->throttle_section_initialised = true;\r
677   }\r
678   else {\r
679     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
680     if (! service->throttle_timer) {\r
681       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
682     }\r
683   }\r
684 \r
685   monitor_service(service);\r
686 }\r
687 \r
688 /* Make sure service recovery actions are taken where necessary */\r
689 void set_service_recovery(nssm_service_t *service) {\r
690   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
691   ZeroMemory(&flag, sizeof(flag));\r
692   flag.fFailureActionsOnNonCrashFailures = true;\r
693 \r
694   /* This functionality was added in Vista so the call may fail */\r
695   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
696     unsigned long error = GetLastError();\r
697     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
698     if (error != ERROR_INVALID_LEVEL) {\r
699       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
700     }\r
701   }\r
702 }\r
703 \r
704 int monitor_service(nssm_service_t *service) {\r
705   /* Set service status to started */\r
706   int ret = start_service(service);\r
707   if (ret) {\r
708     TCHAR code[16];\r
709     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
710     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
711     return ret;\r
712   }\r
713   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
714 \r
715   /* Monitor service */\r
716   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
717     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
718   }\r
719 \r
720   return 0;\r
721 }\r
722 \r
723 TCHAR *service_control_text(unsigned long control) {\r
724   switch (control) {\r
725     /* HACK: there is no SERVICE_CONTROL_START constant */\r
726     case 0: return _T("START");\r
727     case SERVICE_CONTROL_STOP: return _T("STOP");\r
728     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
729     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
730     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
731     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
732     default: return 0;\r
733   }\r
734 }\r
735 \r
736 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
737   TCHAR *text = service_control_text(control);\r
738   unsigned long event;\r
739 \r
740   if (! text) {\r
741     /* "0x" + 8 x hex + NULL */\r
742     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
743     if (! text) {\r
744       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
745       return;\r
746     }\r
747     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
748       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
749       HeapFree(GetProcessHeap(), 0, text);\r
750       return;\r
751     }\r
752 \r
753     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
754   }\r
755   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
756   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
757 \r
758   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
759 \r
760   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
761     HeapFree(GetProcessHeap(), 0, text);\r
762   }\r
763 }\r
764 \r
765 /* Service control handler */\r
766 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
767   nssm_service_t *service = (nssm_service_t *) context;\r
768 \r
769   switch (control) {\r
770     case SERVICE_CONTROL_INTERROGATE:\r
771       /* We always keep the service status up-to-date so this is a no-op. */\r
772       return NO_ERROR;\r
773 \r
774     case SERVICE_CONTROL_SHUTDOWN:\r
775     case SERVICE_CONTROL_STOP:\r
776       log_service_control(service->name, control, true);\r
777       /*\r
778         We MUST acknowledge the stop request promptly but we're committed to\r
779         waiting for the application to exit.  Spawn a new thread to wait\r
780         while we acknowledge the request.\r
781       */\r
782       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
783         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
784 \r
785         /*\r
786           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
787           to complete in time in this thread.\r
788         */\r
789         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
790         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
791         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
792 \r
793         stop_service(service, 0, true, true);\r
794       }\r
795       return NO_ERROR;\r
796 \r
797     case SERVICE_CONTROL_CONTINUE:\r
798       log_service_control(service->name, control, true);\r
799       service->throttle = 0;\r
800       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
801       else {\r
802         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
803         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
804         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
805       }\r
806       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
807       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
808       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
809       SetServiceStatus(service->status_handle, &service->status);\r
810       return NO_ERROR;\r
811 \r
812     case SERVICE_CONTROL_PAUSE:\r
813       /*\r
814         We don't accept pause messages but it isn't possible to register\r
815         only for continue messages so we have to handle this case.\r
816       */\r
817       log_service_control(service->name, control, false);\r
818       return ERROR_CALL_NOT_IMPLEMENTED;\r
819   }\r
820 \r
821   /* Unknown control */\r
822   log_service_control(service->name, control, false);\r
823   return ERROR_CALL_NOT_IMPLEMENTED;\r
824 }\r
825 \r
826 /* Start the service */\r
827 int start_service(nssm_service_t *service) {\r
828   service->stopping = false;\r
829   service->allow_restart = true;\r
830 \r
831   if (service->process_handle) return 0;\r
832 \r
833   /* Allocate a STARTUPINFO structure for a new process */\r
834   STARTUPINFO si;\r
835   ZeroMemory(&si, sizeof(si));\r
836   si.cb = sizeof(si);\r
837 \r
838   /* Allocate a PROCESSINFO structure for the process */\r
839   PROCESS_INFORMATION pi;\r
840   ZeroMemory(&pi, sizeof(pi));\r
841 \r
842   /* Get startup parameters */\r
843   int ret = get_parameters(service, &si);\r
844   if (ret) {\r
845     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
846     return stop_service(service, 2, true, true);\r
847   }\r
848 \r
849   /* Launch executable with arguments */\r
850   TCHAR cmd[CMD_LENGTH];\r
851   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
852     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
853     close_output_handles(&si);\r
854     return stop_service(service, 2, true, true);\r
855   }\r
856 \r
857   throttle_restart(service);\r
858 \r
859   bool inherit_handles = false;\r
860   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
861   unsigned long flags = 0;\r
862 #ifdef UNICODE\r
863   flags |= CREATE_UNICODE_ENVIRONMENT;\r
864 #endif\r
865   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
866     unsigned long exitcode = 3;\r
867     unsigned long error = GetLastError();\r
868     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
869       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
870       if (test_environment(service->env)) exitcode = 4;\r
871     }\r
872     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
873     close_output_handles(&si);\r
874     return stop_service(service, exitcode, true, true);\r
875   }\r
876   service->process_handle = pi.hProcess;\r
877   service->pid = pi.dwProcessId;\r
878 \r
879   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
880 \r
881   close_output_handles(&si);\r
882 \r
883   /*\r
884     Wait for a clean startup before changing the service status to RUNNING\r
885     but be mindful of the fact that we are blocking the service control manager\r
886     so abandon the wait before too much time has elapsed.\r
887   */\r
888   unsigned long delay = service->throttle_delay;\r
889   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
890     TCHAR delay_milliseconds[16];\r
891     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
892     TCHAR deadline_milliseconds[16];\r
893     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
894     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
895     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
896   }\r
897   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
898 \r
899   /* Signal successful start */\r
900   service->status.dwCurrentState = SERVICE_RUNNING;\r
901   SetServiceStatus(service->status_handle, &service->status);\r
902 \r
903   /* Continue waiting for a clean startup. */\r
904   if (deadline == WAIT_TIMEOUT) {\r
905     if (service->throttle_delay > delay) {\r
906       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
907     }\r
908     else service->throttle = 0;\r
909   }\r
910 \r
911   return 0;\r
912 }\r
913 \r
914 /* Stop the service */\r
915 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
916   service->allow_restart = false;\r
917   if (service->wait_handle) {\r
918     UnregisterWait(service->wait_handle);\r
919     service->wait_handle = 0;\r
920   }\r
921 \r
922   if (default_action && ! exitcode && ! graceful) {\r
923     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service->name, service->exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY], 0);\r
924     graceful = true;\r
925   }\r
926 \r
927   /* Signal we are stopping */\r
928   if (graceful) {\r
929     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
930     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
931     SetServiceStatus(service->status_handle, &service->status);\r
932   }\r
933 \r
934   /* Nothing to do if service isn't running */\r
935   if (service->pid) {\r
936     /* Shut down service */\r
937     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
938     kill_process(service, service->process_handle, service->pid, 0);\r
939   }\r
940   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
941 \r
942   end_service((void *) service, true);\r
943 \r
944   /* Signal we stopped */\r
945   if (graceful) {\r
946     service->status.dwCurrentState = SERVICE_STOPPED;\r
947     if (exitcode) {\r
948       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
949       service->status.dwServiceSpecificExitCode = exitcode;\r
950     }\r
951     else {\r
952       service->status.dwWin32ExitCode = NO_ERROR;\r
953       service->status.dwServiceSpecificExitCode = 0;\r
954     }\r
955     SetServiceStatus(service->status_handle, &service->status);\r
956   }\r
957 \r
958   return exitcode;\r
959 }\r
960 \r
961 /* Callback function triggered when the server exits */\r
962 void CALLBACK end_service(void *arg, unsigned char why) {\r
963   nssm_service_t *service = (nssm_service_t *) arg;\r
964 \r
965   if (service->stopping) return;\r
966 \r
967   service->stopping = true;\r
968 \r
969   /* Check exit code */\r
970   unsigned long exitcode = 0;\r
971   TCHAR code[16];\r
972   if (service->process_handle) {\r
973     GetExitCodeProcess(service->process_handle, &exitcode);\r
974     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
975     CloseHandle(service->process_handle);\r
976   }\r
977   else GetSystemTimeAsFileTime(&service->exit_time);\r
978 \r
979   service->process_handle = 0;\r
980 \r
981   /*\r
982     Log that the service ended BEFORE logging about killing the process\r
983     tree.  See below for the possible values of the why argument.\r
984   */\r
985   if (! why) {\r
986     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
987     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
988   }\r
989 \r
990   /* Clean up. */\r
991   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
992   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
993   service->pid = 0;\r
994 \r
995   /*\r
996     The why argument is true if our wait timed out or false otherwise.\r
997     Our wait is infinite so why will never be true when called by the system.\r
998     If it is indeed true, assume we were called from stop_service() because\r
999     this is a controlled shutdown, and don't take any restart action.\r
1000   */\r
1001   if (why) return;\r
1002   if (! service->allow_restart) return;\r
1003 \r
1004   /* What action should we take? */\r
1005   int action = NSSM_EXIT_RESTART;\r
1006   TCHAR action_string[ACTION_LEN];\r
1007   bool default_action;\r
1008   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
1009     for (int i = 0; exit_action_strings[i]; i++) {\r
1010       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
1011         action = i;\r
1012         break;\r
1013       }\r
1014     }\r
1015   }\r
1016 \r
1017   switch (action) {\r
1018     /* Try to restart the service or return failure code to service manager */\r
1019     case NSSM_EXIT_RESTART:\r
1020       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
1021       while (monitor_service(service)) {\r
1022         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
1023         Sleep(30000);\r
1024       }\r
1025     break;\r
1026 \r
1027     /* Do nothing, just like srvany would */\r
1028     case NSSM_EXIT_IGNORE:\r
1029       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
1030       Sleep(INFINITE);\r
1031     break;\r
1032 \r
1033     /* Tell the service manager we are finished */\r
1034     case NSSM_EXIT_REALLY:\r
1035       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
1036       stop_service(service, exitcode, true, default_action);\r
1037     break;\r
1038 \r
1039     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
1040     case NSSM_EXIT_UNCLEAN:\r
1041       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
1042       stop_service(service, exitcode, false, default_action);\r
1043       free_imports();\r
1044       exit(exitcode);\r
1045     break;\r
1046   }\r
1047 }\r
1048 \r
1049 void throttle_restart(nssm_service_t *service) {\r
1050   /* This can't be a restart if the service is already running. */\r
1051   if (! service->throttle++) return;\r
1052 \r
1053   int ms = throttle_milliseconds(service->throttle);\r
1054 \r
1055   if (service->throttle > 7) service->throttle = 8;\r
1056 \r
1057   TCHAR threshold[8], milliseconds[8];\r
1058   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
1059   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
1060   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
1061 \r
1062   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
1063   else if (service->throttle_timer) {\r
1064     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1065     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
1066     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1067   }\r
1068 \r
1069   service->status.dwCurrentState = SERVICE_PAUSED;\r
1070   SetServiceStatus(service->status_handle, &service->status);\r
1071 \r
1072   if (use_critical_section) {\r
1073     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
1074     LeaveCriticalSection(&service->throttle_section);\r
1075   }\r
1076   else {\r
1077     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
1078     else Sleep(ms);\r
1079   }\r
1080 }\r
1081 \r
1082 /*\r
1083   When responding to a stop (or any other) request we need to set dwWaitHint to\r
1084   the number of milliseconds we expect the operation to take, and optionally\r
1085   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
1086   operation completing or dwCheckPoint increasing, the system will consider the\r
1087   service to be hung.\r
1088 \r
1089   However the system will consider the service to be hung after 30000\r
1090   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
1091   changed.  Therefore if we want to wait longer than that we must periodically\r
1092   increase dwCheckPoint.\r
1093 \r
1094   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
1095   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
1096   time dwCheckPoint is also increased.\r
1097 \r
1098   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
1099   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
1100   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
1101   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
1102   dwWaitHint.\r
1103 \r
1104   Only doing both these things will prevent the system from killing the service.\r
1105 \r
1106   Returns: 1 if the wait timed out.\r
1107            0 if the wait completed.\r
1108           -1 on error.\r
1109 */\r
1110 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
1111   unsigned long interval;\r
1112   unsigned long waithint;\r
1113   unsigned long ret;\r
1114   unsigned long waited;\r
1115   TCHAR interval_milliseconds[16];\r
1116   TCHAR timeout_milliseconds[16];\r
1117   TCHAR waited_milliseconds[16];\r
1118   TCHAR *function = function_name;\r
1119 \r
1120   /* Add brackets to function name. */\r
1121   size_t funclen = _tcslen(function_name) + 3;\r
1122   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
1123   if (func) {\r
1124     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
1125   }\r
1126 \r
1127   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
1128 \r
1129   waithint = service->status.dwWaitHint;\r
1130   waited = 0;\r
1131   while (waited < timeout) {\r
1132     interval = timeout - waited;\r
1133     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
1134 \r
1135     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1136     service->status.dwWaitHint += interval;\r
1137     service->status.dwCheckPoint++;\r
1138     SetServiceStatus(service->status_handle, &service->status);\r
1139 \r
1140     if (waited) {\r
1141       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
1142       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
1143       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
1144     }\r
1145 \r
1146     switch (WaitForSingleObject(service->process_handle, interval)) {\r
1147       case WAIT_OBJECT_0:\r
1148         ret = 0;\r
1149         goto awaited;\r
1150 \r
1151       case WAIT_TIMEOUT:\r
1152         ret = 1;\r
1153       break;\r
1154 \r
1155       default:\r
1156         ret = -1;\r
1157         goto awaited;\r
1158     }\r
1159 \r
1160     waited += interval;\r
1161   }\r
1162 \r
1163 awaited:\r
1164   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1165 \r
1166   return ret;\r
1167 }\r