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