]> granicus.if.org Git - apache/blob - server/mpm/winnt/service.c
this security API needs some loving, a warning at least for SYSTEM/LocalSystem
[apache] / server / mpm / winnt / service.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /* This module ALONE requires the window message API from user.h
18  * and the default APR include of windows.h will omit it, so
19  * preload the API symbols now...
20  */
21
22 #define _WINUSER_
23
24 #include "httpd.h"
25 #include "http_log.h"
26 #include "mpm_winnt.h"
27 #include "apr_strings.h"
28 #include "apr_lib.h"
29 #include "ap_regkey.h"
30
31 #ifdef NOUSER
32 #undef NOUSER
33 #endif
34 #undef _WINUSER_
35 #include <winuser.h>
36
37 static char *mpm_service_name = NULL;
38 static char *mpm_display_name = NULL;
39
40 static struct
41 {
42     HANDLE mpm_thread;       /* primary thread handle of the apache server */
43     HANDLE service_thread;   /* thread service/monitor handle */
44     DWORD  service_thread_id;/* thread service/monitor ID */
45     HANDLE service_init;     /* controller thread init mutex */
46     HANDLE service_term;     /* NT service thread kill signal */
47     SERVICE_STATUS ssStatus;
48     SERVICE_STATUS_HANDLE hServiceStatus;
49 } globdat;
50
51 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint);
52
53
54 #define PRODREGKEY "SOFTWARE\\" AP_SERVER_BASEVENDOR "\\" \
55                    AP_SERVER_BASEPRODUCT "\\" AP_SERVER_BASEREVISION
56
57 /*
58  * Get the server root from the registry into 'dir' which is
59  * size bytes long. Returns 0 if the server root was found
60  * or if the serverroot key does not exist (in which case
61  * dir will contain an empty string), or -1 if there was
62  * an error getting the key.
63  */
64 apr_status_t ap_registry_get_server_root(apr_pool_t *p, char **buf)
65 {
66     apr_status_t rv;
67     ap_regkey_t *key;
68
69     if ((rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, PRODREGKEY,
70                              APR_READ, p)) == APR_SUCCESS) {
71         rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
72         ap_regkey_close(key);
73         if (rv == APR_SUCCESS)
74             return rv;
75     }
76
77     if ((rv = ap_regkey_open(&key, AP_REGKEY_CURRENT_USER, PRODREGKEY,
78                              APR_READ, p)) == APR_SUCCESS) {
79         rv = ap_regkey_value_get(buf, key, "ServerRoot", p);
80         ap_regkey_close(key);
81         if (rv == APR_SUCCESS)
82             return rv;
83     }
84
85     *buf = NULL;
86     return rv;
87 }
88
89
90 /* The service configuration's is stored under the following trees:
91  *
92  * HKLM\System\CurrentControlSet\Services\[service name]
93  *
94  *     \DisplayName
95  *     \ImagePath
96  *     \Parameters\ConfigArgs
97  */
98
99
100 /* exit() for Win32 is macro mapped (horrible, we agree) that allows us
101  * to catch the non-zero conditions and inform the console process that
102  * the application died, and hang on to the console a bit longer.
103  *
104  * The macro only maps for http_main.c and other sources that include
105  * the service.h header, so we best assume it's an error to exit from
106  * _any_ other module.
107  *
108  * If ap_real_exit_code is reset to 0, it will not be set or trigger this
109  * behavior on exit.  All service and child processes are expected to
110  * reset this flag to zero to avoid undesireable side effects.
111  */
112 AP_DECLARE_DATA int ap_real_exit_code = 1;
113
114 void hold_console_open_on_error(void)
115 {
116     HANDLE hConIn;
117     HANDLE hConErr;
118     DWORD result;
119     time_t start;
120     time_t remains;
121     char *msg = "Note the errors or messages above, "
122                 "and press the <ESC> key to exit.  ";
123     CONSOLE_SCREEN_BUFFER_INFO coninfo;
124     INPUT_RECORD in;
125     char count[16];
126
127     if (!ap_real_exit_code)
128         return;
129     hConIn = GetStdHandle(STD_INPUT_HANDLE);
130     hConErr = GetStdHandle(STD_ERROR_HANDLE);
131     if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE))
132         return;
133     if (!WriteConsole(hConErr, msg, (DWORD)strlen(msg), &result, NULL) || !result)
134         return;
135     if (!GetConsoleScreenBufferInfo(hConErr, &coninfo))
136         return;
137     if (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80))
138         return;
139
140     start = time(NULL);
141     do
142     {
143         while (PeekConsoleInput(hConIn, &in, 1, &result) && result)
144         {
145             if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result)
146                 return;
147             if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown
148                     && (in.Event.KeyEvent.uChar.AsciiChar == 27))
149                 return;
150             if (in.EventType == MOUSE_EVENT
151                     && (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
152                 return;
153         }
154         remains = ((start + 30) - time(NULL));
155         sprintf (count, "%d...", remains);
156         if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition))
157             return;
158         if (!WriteConsole(hConErr, count, (DWORD)strlen(count), &result, NULL)
159                 || !result)
160             return;
161     }
162     while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED);
163 }
164
165 static BOOL  die_on_logoff = FALSE;
166
167 static BOOL CALLBACK console_control_handler(DWORD ctrl_type)
168 {
169     switch (ctrl_type)
170     {
171         case CTRL_BREAK_EVENT:
172             fprintf(stderr, "Apache server restarting...\n");
173             ap_signal_parent(SIGNAL_PARENT_RESTART);
174             return TRUE;
175         case CTRL_C_EVENT:
176             fprintf(stderr, "Apache server interrupted...\n");
177             /* for Interrupt signals, shut down the server.
178              * Tell the system we have dealt with the signal
179              * without waiting for Apache to terminate.
180              */
181             ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
182             return TRUE;
183
184         case CTRL_CLOSE_EVENT:
185         case CTRL_LOGOFF_EVENT:
186         case CTRL_SHUTDOWN_EVENT:
187             /* for Terminate signals, shut down the server.
188              * Wait for Apache to terminate, but respond
189              * after a reasonable time to tell the system
190              * that we did attempt to shut ourself down.
191              */
192             fprintf(stderr, "Apache server shutdown initiated...\n");
193             ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
194             Sleep(30000);
195             return TRUE;
196     }
197
198     /* We should never get here, but this is (mostly) harmless */
199     return FALSE;
200 }
201
202
203 static void stop_console_handler(void)
204 {
205     SetConsoleCtrlHandler(console_control_handler, FALSE);
206 }
207
208
209 void mpm_start_console_handler(void)
210 {
211     SetConsoleCtrlHandler(console_control_handler, TRUE);
212     atexit(stop_console_handler);
213 }
214
215
216 /* Special situation - children of services need to mind their
217  * P's & Q's and wait quietly, ignoring the mean OS signaling
218  * shutdown and other horrors, to kill them gracefully...
219  */
220
221 static BOOL CALLBACK child_control_handler(DWORD ctrl_type)
222 {
223     switch (ctrl_type)
224     {
225         case CTRL_C_EVENT:
226         case CTRL_BREAK_EVENT:
227             /* for Interrupt signals, ignore them.
228              * The system will also signal the parent process,
229              * which will terminate Apache.
230              */
231             return TRUE;
232
233         case CTRL_CLOSE_EVENT:
234         case CTRL_LOGOFF_EVENT:
235         case CTRL_SHUTDOWN_EVENT:
236             /* for Shutdown signals, ignore them, but...             .
237              * The system will also signal the parent process,
238              * which will terminate Apache, so we need to wait.
239              */
240             Sleep(30000);
241             return TRUE;
242     }
243
244     /* We should never get here, but this is (mostly) harmless */
245     return FALSE;
246 }
247
248
249 void mpm_start_child_console_handler(void)
250 {
251     FreeConsole();
252 }
253
254
255 /**********************************
256   WinNT service control management
257  **********************************/
258
259 static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint)
260 {
261     static int checkPoint = 1;
262     int rv = APR_SUCCESS;
263
264     if (globdat.hServiceStatus)
265     {
266         if (currentState == SERVICE_RUNNING) {
267             globdat.ssStatus.dwWaitHint = 0;
268             globdat.ssStatus.dwCheckPoint = 0;
269             globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
270         }
271         else if (currentState == SERVICE_STOPPED) {
272             globdat.ssStatus.dwWaitHint = 0;
273             globdat.ssStatus.dwCheckPoint = 0;
274             if (!exitCode && globdat.ssStatus.dwCurrentState
275                                            != SERVICE_STOP_PENDING) {
276                 /* An unexpected exit?  Better to error! */
277                 exitCode = 1;
278             }
279             if (exitCode) {
280                 globdat.ssStatus.dwWin32ExitCode =ERROR_SERVICE_SPECIFIC_ERROR;
281                 globdat.ssStatus.dwServiceSpecificExitCode = exitCode;
282             }
283         }
284         else {
285             globdat.ssStatus.dwCheckPoint = ++checkPoint;
286             globdat.ssStatus.dwControlsAccepted = 0;
287             if(waitHint)
288                 globdat.ssStatus.dwWaitHint = waitHint;
289         }
290
291         globdat.ssStatus.dwCurrentState = currentState;
292
293         rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus);
294     }
295     return(rv);
296 }
297
298 /* Note this works on Win2000 and later due to ChangeServiceConfig2
299  * Continue to test it's existence, but at least drop the feature
300  * of revising service description tags prior to Win2000.
301  */
302
303 /* borrowed from mpm_winnt.c */
304 extern apr_pool_t *pconf;
305
306 static void set_service_description(void)
307 {
308     const char *full_description;
309     SC_HANDLE schSCManager;
310     BOOL ret = 0;
311
312     /* Nothing to do if we are a console
313      */
314     if (!mpm_service_name)
315         return;
316
317     /* Time to fix up the description, upon each successful restart
318      */
319     full_description = ap_get_server_description();
320
321     if ((ChangeServiceConfig2) &&
322         (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)))
323     {
324         SC_HANDLE schService = OpenService(schSCManager, mpm_service_name,
325                                            SERVICE_CHANGE_CONFIG);
326         if (schService) {
327             /* Cast is necessary, ChangeServiceConfig2 handles multiple
328              * object types, some volatile, some not.
329              */
330             /* ###: utf-ize */
331             ChangeServiceConfig2(schService,
332                                  1 /* SERVICE_CONFIG_DESCRIPTION */,
333                                  (LPVOID) &full_description);
334             CloseServiceHandle(schService);
335         }
336         CloseServiceHandle(schSCManager);
337     }
338 }
339
340 /* handle the SCM's ControlService() callbacks to our service */
341
342 static VOID WINAPI service_nt_ctrl(DWORD dwCtrlCode)
343 {
344     if (dwCtrlCode == SERVICE_CONTROL_STOP)
345     {
346         ap_signal_parent(SIGNAL_PARENT_SHUTDOWN);
347         ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 30000);
348         return;
349     }
350     if (dwCtrlCode == SERVICE_APACHE_RESTART)
351     {
352         ap_signal_parent(SIGNAL_PARENT_RESTART);
353         ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
354         return;
355     }
356
357     ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0);
358 }
359
360
361 /* service_nt_main_fn is outside of the call stack and outside of the
362  * primary server thread... so now we _really_ need a placeholder!
363  * The winnt_rewrite_args has created and shared mpm_new_argv with us.
364  */
365 extern apr_array_header_t *mpm_new_argv;
366
367 /* ###: utf-ize */
368 static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv)
369 {
370     const char *ignored;
371
372     /* args and service names live in the same pool */
373     mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]);
374
375     memset(&globdat.ssStatus, 0, sizeof(globdat.ssStatus));
376     globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
377     globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING;
378     globdat.ssStatus.dwCheckPoint = 1;
379
380     /* ###: utf-ize */
381     if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl)))
382     {
383         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(),
384                      NULL, "Failure registering service handler");
385         return;
386     }
387
388     /* Report status, no errors, and buy 3 more seconds */
389     ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000);
390
391     /* We need to append all the command arguments passed via StartService()
392      * to our running service... which just got here via the SCM...
393      * but we hvae no interest in argv[0] for the mpm_new_argv list.
394      */
395     if (argc > 1)
396     {
397         char **cmb_data;
398
399         mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1;
400         cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *));
401
402         /* mpm_new_argv remains first (of lower significance) */
403         memcpy (cmb_data, mpm_new_argv->elts,
404                 mpm_new_argv->elt_size * mpm_new_argv->nelts);
405
406         /* Service args follow from StartService() invocation */
407         memcpy (cmb_data + mpm_new_argv->nelts, argv + 1,
408                 mpm_new_argv->elt_size * (argc - 1));
409
410         /* The replacement arg list is complete */
411         mpm_new_argv->elts = (char *)cmb_data;
412         mpm_new_argv->nelts = mpm_new_argv->nalloc;
413     }
414
415     /* Let the main thread continue now... but hang on to the
416      * signal_monitor event so we can take further action
417      */
418     SetEvent(globdat.service_init);
419
420     WaitForSingleObject(globdat.service_term, INFINITE);
421 }
422
423
424 DWORD WINAPI service_nt_dispatch_thread(LPVOID nada)
425 {
426     apr_status_t rv = APR_SUCCESS;
427
428     SERVICE_TABLE_ENTRY dispatchTable[] =
429     {
430         { "", service_nt_main_fn },
431         { NULL, NULL }
432     };
433
434     /* ###: utf-ize */
435     if (!StartServiceCtrlDispatcher(dispatchTable))
436     {
437         /* This is a genuine failure of the SCM. */
438         rv = apr_get_os_error();
439         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
440                      "Error starting service control dispatcher");
441     }
442
443     return (rv);
444 }
445
446
447 apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name,
448                                   const char *set_name)
449 {
450     char key_name[MAX_PATH];
451     ap_regkey_t *key;
452     apr_status_t rv;
453
454     /* ### Needs improvement, on Win2K the user can _easily_
455      * change the display name to a string that doesn't reflect
456      * the internal service name + whitespace!
457      */
458     mpm_service_name = apr_palloc(p, strlen(set_name) + 1);
459     apr_collapse_spaces((char*) mpm_service_name, set_name);
460     apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name);
461     rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, APR_READ, pconf);
462     if (rv == APR_SUCCESS) {
463         rv = ap_regkey_value_get(&mpm_display_name, key, "DisplayName", pconf);
464         ap_regkey_close(key);
465     }
466     if (rv != APR_SUCCESS) {
467         /* Take the given literal name if there is no service entry */
468         mpm_display_name = apr_pstrdup(p, set_name);
469     }
470     *display_name = mpm_display_name;
471     return rv;
472 }
473
474
475 apr_status_t mpm_merge_service_args(apr_pool_t *p,
476                                    apr_array_header_t *args,
477                                    int fixed_args)
478 {
479     apr_array_header_t *svc_args = NULL;
480     char conf_key[MAX_PATH];
481     char **cmb_data;
482     apr_status_t rv;
483     ap_regkey_t *key;
484
485     apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name);
486     rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, conf_key, APR_READ, p);
487     if (rv == APR_SUCCESS) {
488         rv = ap_regkey_value_array_get(&svc_args, key, "ConfigArgs", p);
489         ap_regkey_close(key);
490     }
491     if (rv != APR_SUCCESS) {
492         if (rv == ERROR_FILE_NOT_FOUND) {
493             ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL,
494                          "No ConfigArgs registered for %s, perhaps "
495                          "this service is not installed?",
496                          mpm_service_name);
497             return APR_SUCCESS;
498         }
499         else
500             return (rv);
501     }
502
503     if (!svc_args || svc_args->nelts == 0) {
504         return (APR_SUCCESS);
505     }
506
507     /* Now we have the mpm_service_name arg, and the mpm_runservice_nt()
508      * call appended the arguments passed by StartService(), so it's
509      * time to _prepend_ the default arguments for the server from
510      * the service's default arguments (all others override them)...
511      */
512     args->nalloc = args->nelts + svc_args->nelts;
513     cmb_data = malloc(args->nalloc * sizeof(const char *));
514
515     /* First three args (argv[0], -f, path) remain first */
516     memcpy(cmb_data, args->elts, args->elt_size * fixed_args);
517
518     /* Service args follow from service registry array */
519     memcpy(cmb_data + fixed_args, svc_args->elts,
520            svc_args->elt_size * svc_args->nelts);
521
522     /* Remaining new args follow  */
523     memcpy(cmb_data + fixed_args + svc_args->nelts,
524            (const char **)args->elts + fixed_args,
525            args->elt_size * (args->nelts - fixed_args));
526
527     args->elts = (char *)cmb_data;
528     args->nelts = args->nalloc;
529
530     return APR_SUCCESS;
531 }
532
533
534 void service_stopped(void)
535 {
536     /* Still have a thread & window to clean up, so signal now */
537     if (globdat.service_thread)
538     {
539         /* Stop logging to the event log */
540         mpm_nt_eventlog_stderr_flush();
541
542         /* Cause the service_nt_main_fn to complete */
543         ReleaseMutex(globdat.service_term);
544
545         ReportStatusToSCMgr(SERVICE_STOPPED, // service state
546                             NO_ERROR,        // exit code
547                             0);              // wait hint
548
549         WaitForSingleObject(globdat.service_thread, 5000);
550         CloseHandle(globdat.service_thread);
551     }
552 }
553
554
555 apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p)
556 {
557     HANDLE hProc = GetCurrentProcess();
558     HANDLE hThread = GetCurrentThread();
559     HANDLE waitfor[2];
560
561     /* Prevent holding open the (hidden) console */
562     ap_real_exit_code = 0;
563
564      /* GetCurrentThread returns a psuedo-handle, we need
565       * a real handle for another thread to wait upon.
566       */
567     if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread),
568                          0, FALSE, DUPLICATE_SAME_ACCESS)) {
569         return APR_ENOTHREAD;
570     }
571
572     globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL);
573     globdat.service_term = CreateMutex(NULL, TRUE, NULL);
574     if (!globdat.service_init || !globdat.service_term) {
575          return APR_EGENERAL;
576     }
577
578     globdat.service_thread = CreateThread(NULL, 65536,
579                                           service_nt_dispatch_thread,
580                                           NULL, stack_res_flag,
581                                           &globdat.service_thread_id);
582
583     if (!globdat.service_thread) {
584         return APR_ENOTHREAD;
585     }
586
587     waitfor[0] = globdat.service_init;
588     waitfor[1] = globdat.service_thread;
589
590     /* Wait for controlling thread init or termination */
591     if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) {
592         return APR_ENOTHREAD;
593     }
594
595     atexit(service_stopped);
596     *display_name = mpm_display_name;
597     return APR_SUCCESS;
598 }
599
600
601 apr_status_t mpm_service_started(void)
602 {
603     set_service_description();
604     ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0);
605     return APR_SUCCESS;
606 }
607
608
609 void mpm_service_stopping(void)
610 {
611     ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 30000);
612 }
613
614
615 apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc,
616                                  const char * const * argv, int reconfig)
617 {
618     char key_name[MAX_PATH];
619     char exe_path[MAX_PATH];
620     char *launch_cmd;
621     ap_regkey_t *key;
622     apr_status_t rv;
623     SC_HANDLE   schService;
624     SC_HANDLE   schSCManager;
625
626     fprintf(stderr,reconfig ? "Reconfiguring the %s service\n"
627                    : "Installing the %s service\n", mpm_display_name);
628
629     /* ###: utf-ize */
630     if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0)
631     {
632         apr_status_t rv = apr_get_os_error();
633         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
634                      "GetModuleFileName failed");
635         return rv;
636     }
637
638     schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
639                                  SC_MANAGER_CREATE_SERVICE);
640     if (!schSCManager) {
641         rv = apr_get_os_error();
642         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
643                      "Failed to open the WinNT service manager, perhaps "
644                      "you forgot to log in as Adminstrator?");
645         return (rv);
646     }
647
648     launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path);
649
650     if (reconfig) {
651         /* ###: utf-ize */
652         schService = OpenService(schSCManager, mpm_service_name,
653                                  SERVICE_CHANGE_CONFIG);
654         if (!schService) {
655             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
656                          apr_get_os_error(), NULL,
657                          "OpenService failed");
658         }
659         /* ###: utf-ize */
660         else if (!ChangeServiceConfig(schService,
661                                       SERVICE_WIN32_OWN_PROCESS,
662                                       SERVICE_AUTO_START,
663                                       SERVICE_ERROR_NORMAL,
664                                       launch_cmd, NULL, NULL,
665                                       "Tcpip\0Afd\0", NULL, NULL,
666                                       mpm_display_name)) {
667             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR,
668                          apr_get_os_error(), NULL,
669                          "ChangeServiceConfig failed");
670
671             /* !schService aborts configuration below */
672             CloseServiceHandle(schService);
673             schService = NULL;
674             }
675         }
676     else {
677         /* RPCSS is the Remote Procedure Call (RPC) Locator required
678          * for DCOM communication pipes.  I am far from convinced we
679          * should add this to the default service dependencies, but
680          * be warned that future apache modules or ISAPI dll's may
681          * depend on it.
682          */
683         /* ###: utf-ize */
684         schService = CreateService(schSCManager,         // SCManager database
685                                    mpm_service_name,     // name of service
686                                    mpm_display_name,     // name to display
687                                    SERVICE_ALL_ACCESS,   // access required
688                                    SERVICE_WIN32_OWN_PROCESS,  // service type
689                                    SERVICE_AUTO_START,   // start type
690                                    SERVICE_ERROR_NORMAL, // error control type
691                                    launch_cmd,           // service's binary
692                                    NULL,                 // no load svc group
693                                    NULL,                 // no tag identifier
694                                    "Tcpip\0Afd\0",       // dependencies
695                                    NULL,                 // use SYSTEM account
696                                    NULL);                // no password
697
698         if (!schService)
699         {
700             rv = apr_get_os_error();
701             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
702                          "Failed to create WinNT Service Profile");
703             CloseServiceHandle(schSCManager);
704             return (rv);
705         }
706     }
707
708     CloseServiceHandle(schService);
709     CloseServiceHandle(schSCManager);
710
711     set_service_description();
712
713     /* Store the service ConfigArgs in the registry...
714      */
715     apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name);
716     rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name,
717                         APR_READ | APR_WRITE | APR_CREATE, pconf);
718     if (rv == APR_SUCCESS) {
719         rv = ap_regkey_value_array_set(key, "ConfigArgs", argc, argv, pconf);
720         ap_regkey_close(key);
721     }
722     if (rv != APR_SUCCESS) {
723         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
724                      "%s: Failed to store the ConfigArgs in the registry.",
725                      mpm_display_name);
726         return (rv);
727     }
728     fprintf(stderr,"The %s service is successfully installed.\n", mpm_display_name);
729     return APR_SUCCESS;
730 }
731
732
733 apr_status_t mpm_service_uninstall(void)
734 {
735     apr_status_t rv;
736     SC_HANDLE schService;
737     SC_HANDLE schSCManager;
738
739     fprintf(stderr,"Removing the %s service\n", mpm_display_name);
740
741     schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
742                                  SC_MANAGER_CONNECT);
743     if (!schSCManager) {
744         rv = apr_get_os_error();
745         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
746                      "Failed to open the WinNT service manager.");
747         return (rv);
748     }
749
750     /* ###: utf-ize */
751     schService = OpenService(schSCManager, mpm_service_name, DELETE);
752
753     if (!schService) {
754         rv = apr_get_os_error();
755         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
756                         "%s: OpenService failed", mpm_display_name);
757         return (rv);
758     }
759
760     /* assure the service is stopped before continuing
761      *
762      * This may be out of order... we might not be able to be
763      * granted all access if the service is running anyway.
764      *
765      * And do we want to make it *this easy* for them
766      * to uninstall their service unintentionally?
767      */
768     /* ap_stop_service(schService);
769      */
770
771     if (DeleteService(schService) == 0) {
772         rv = apr_get_os_error();
773         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
774                      "%s: Failed to delete the service.", mpm_display_name);
775         return (rv);
776     }
777
778     CloseServiceHandle(schService);
779     CloseServiceHandle(schSCManager);
780
781     fprintf(stderr,"The %s service has been removed successfully.\n", mpm_display_name);
782     return APR_SUCCESS;
783 }
784
785
786 /* signal_service_transition is a simple thunk to signal the service
787  * and monitor its successful transition.  If the signal passed is 0,
788  * then the caller is assumed to already have performed some service
789  * operation to be monitored (such as StartService), and no actual
790  * ControlService signal is sent.
791  */
792
793 static int signal_service_transition(SC_HANDLE schService, DWORD signal, DWORD pending, DWORD complete)
794 {
795     if (signal && !ControlService(schService, signal, &globdat.ssStatus))
796         return FALSE;
797
798     do {
799         Sleep(1000);
800         if (!QueryServiceStatus(schService, &globdat.ssStatus))
801             return FALSE;
802     } while (globdat.ssStatus.dwCurrentState == pending);
803
804     return (globdat.ssStatus.dwCurrentState == complete);
805 }
806
807
808 apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc,
809                                const char * const * argv)
810 {
811     apr_status_t rv;
812     char **start_argv;
813     SC_HANDLE   schService;
814     SC_HANDLE   schSCManager;
815
816     fprintf(stderr,"Starting the %s service\n", mpm_display_name);
817
818     schSCManager = OpenSCManager(NULL, NULL, /* local, default database */
819                                  SC_MANAGER_CONNECT);
820     if (!schSCManager) {
821         rv = apr_get_os_error();
822         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
823                      "Failed to open the WinNT service manager");
824         return (rv);
825     }
826
827     /* ###: utf-ize */
828     schService = OpenService(schSCManager, mpm_service_name,
829                              SERVICE_START | SERVICE_QUERY_STATUS);
830     if (!schService) {
831         rv = apr_get_os_error();
832         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL,
833                      "%s: Failed to open the service.", mpm_display_name);
834         CloseServiceHandle(schSCManager);
835         return (rv);
836     }
837
838     if (QueryServiceStatus(schService, &globdat.ssStatus)
839         && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) {
840         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL,
841                      "Service %s is already started!", mpm_display_name);
842         CloseServiceHandle(schService);
843         CloseServiceHandle(schSCManager);
844         return 0;
845     }
846
847     start_argv = malloc((argc + 1) * sizeof(const char **));
848     memcpy(start_argv, argv, argc * sizeof(const char **));
849     start_argv[argc] = NULL;
850
851     rv = APR_EINIT;
852     /* ###: utf-ize */
853     if (StartService(schService, argc, start_argv)
854         && signal_service_transition(schService, 0, /* test only */
855                                      SERVICE_START_PENDING,
856                                      SERVICE_RUNNING))
857         rv = APR_SUCCESS;
858     if (rv != APR_SUCCESS)
859         rv = apr_get_os_error();
860
861     CloseServiceHandle(schService);
862     CloseServiceHandle(schSCManager);
863
864     if (rv == APR_SUCCESS)
865         fprintf(stderr,"The %s service is running.\n", mpm_display_name);
866     else
867         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL,
868                      "%s: Failed to start the service process.",
869                      mpm_display_name);
870
871     return rv;
872 }
873
874
875 /* signal is zero to stop, non-zero for restart */
876
877 void mpm_signal_service(apr_pool_t *ptemp, int signal)
878 {
879     int success = FALSE;
880     SC_HANDLE   schService;
881     SC_HANDLE   schSCManager;
882
883     schSCManager = OpenSCManager(NULL, NULL, // default machine & database
884                                  SC_MANAGER_CONNECT);
885
886     if (!schSCManager) {
887         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
888                      "Failed to open the NT Service Manager");
889         return;
890     }
891
892     /* ###: utf-ize */
893     schService = OpenService(schSCManager, mpm_service_name,
894                              SERVICE_INTERROGATE | SERVICE_QUERY_STATUS |
895                              SERVICE_USER_DEFINED_CONTROL |
896                              SERVICE_START | SERVICE_STOP);
897
898     if (schService == NULL) {
899         /* Could not open the service */
900         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
901                      "Failed to open the %s Service", mpm_display_name);
902         CloseServiceHandle(schSCManager);
903         return;
904     }
905
906     if (!QueryServiceStatus(schService, &globdat.ssStatus)) {
907         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL,
908                      "Query of Service %s failed", mpm_display_name);
909         CloseServiceHandle(schService);
910         CloseServiceHandle(schSCManager);
911         return;
912     }
913
914     if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) {
915         fprintf(stderr,"The %s service is not started.\n", mpm_display_name);
916         CloseServiceHandle(schService);
917         CloseServiceHandle(schSCManager);
918         return;
919     }
920
921     fprintf(stderr,"The %s service is %s.\n", mpm_display_name,
922             signal ? "restarting" : "stopping");
923
924     if (!signal)
925         success = signal_service_transition(schService,
926                                             SERVICE_CONTROL_STOP,
927                                             SERVICE_STOP_PENDING,
928                                             SERVICE_STOPPED);
929     else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) {
930         mpm_service_start(ptemp, 0, NULL);
931         CloseServiceHandle(schService);
932         CloseServiceHandle(schSCManager);
933         return;
934     }
935     else
936         success = signal_service_transition(schService,
937                                             SERVICE_APACHE_RESTART,
938                                             SERVICE_START_PENDING,
939                                             SERVICE_RUNNING);
940
941     CloseServiceHandle(schService);
942     CloseServiceHandle(schSCManager);
943
944     if (success)
945         fprintf(stderr,"The %s service has %s.\n", mpm_display_name,
946                signal ? "restarted" : "stopped");
947     else
948         fprintf(stderr,"Failed to %s the %s service.\n",
949                signal ? "restart" : "stop", mpm_display_name);
950 }