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