]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - winrc/win_svc.c
import unbound 1.4.17
[FreeBSD/FreeBSD.git] / winrc / win_svc.c
1 /*
2  * winrc/win_svc.c - windows services API implementation for unbound
3  *
4  * Copyright (c) 2009, NLnet Labs. All rights reserved.
5  *
6  * This software is open source.
7  * 
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 
12  * Redistributions of source code must retain the above copyright notice,
13  * this list of conditions and the following disclaimer.
14  * 
15  * Redistributions in binary form must reproduce the above copyright notice,
16  * this list of conditions and the following disclaimer in the documentation
17  * and/or other materials provided with the distribution.
18  * 
19  * Neither the name of the NLNET LABS nor the names of its contributors may
20  * be used to endorse or promote products derived from this software without
21  * specific prior written permission.
22  * 
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
25  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
27  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * POSSIBILITY OF SUCH DAMAGE.
34  */
35
36 /**
37  * \file
38  *
39  * This file contains functions to integrate with the windows services API.
40  * This means it handles the commandline switches to install and remove
41  * the service (via CreateService and DeleteService), it handles
42  * the ServiceMain() main service entry point when started as a service,
43  * and it handles the Handler[_ex]() to process requests to the service
44  * (such as start and stop and status).
45  */
46 #include "config.h"
47 #include "winrc/win_svc.h"
48 #include "winrc/w_inst.h"
49 #include "daemon/daemon.h"
50 #include "daemon/worker.h"
51 #include "daemon/remote.h"
52 #include "util/config_file.h"
53 #include "util/netevent.h"
54 #include "util/winsock_event.h"
55
56 /** global service status */
57 static SERVICE_STATUS   service_status;
58 /** global service status handle */
59 static SERVICE_STATUS_HANDLE service_status_handle;
60 /** global service stop event */
61 static WSAEVENT service_stop_event = NULL;
62 /** event struct for stop callbacks */
63 static struct event service_stop_ev;
64 /** if stop even means shutdown or restart */
65 static int service_stop_shutdown = 0;
66 /** config file to open. global communication to service_main() */
67 static char* service_cfgfile = CONFIGFILE;
68 /** commandline verbosity. global communication to service_main() */
69 static int service_cmdline_verbose = 0;
70 /** the cron callback */
71 static struct comm_timer* service_cron = NULL;
72 /** the cron thread */
73 static ub_thread_t cron_thread = NULL;
74 /** if cron has already done its quick check */
75 static int cron_was_quick = 0;
76
77 /**
78  * Report current service status to service control manager
79  * @param state: current state
80  * @param exitcode: error code (when stopped)
81  * @param wait: pending operation estimated time in milliseconds.
82  */
83 static void report_status(DWORD state, DWORD exitcode, DWORD wait)
84 {
85         static DWORD checkpoint = 1;
86         service_status.dwCurrentState = state;
87         service_status.dwWin32ExitCode = exitcode;
88         service_status.dwWaitHint = wait;
89         if(state == SERVICE_START_PENDING)
90                 service_status.dwControlsAccepted = 0;
91         else    service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
92         if(state == SERVICE_RUNNING || state == SERVICE_STOPPED)
93                 service_status.dwCheckPoint = 0;
94         else    service_status.dwCheckPoint = checkpoint++;
95         SetServiceStatus(service_status_handle, &service_status);
96 }
97
98 /**
99  * Service control handler. Called by serviceControlManager when a control
100  * code is sent to the service (with ControlService).
101  * @param ctrl: control code
102  */
103 static void 
104 hdlr(DWORD ctrl)
105 {
106         if(ctrl == SERVICE_CONTROL_STOP) {
107                 report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
108                 service_stop_shutdown = 1;
109                 /* send signal to stop */
110                 if(!WSASetEvent(service_stop_event))
111                         log_err("Could not WSASetEvent: %s",
112                                 wsa_strerror(WSAGetLastError()));
113                 return;
114         } else {
115                 /* ctrl == SERVICE_CONTROL_INTERROGATE or whatever */
116                 /* update status */
117                 report_status(service_status.dwCurrentState, NO_ERROR, 0);
118         }
119 }
120
121 /**
122  * report event to system event log
123  * For use during startup and shutdown.
124  * @param str: the error
125  */
126 static void
127 reportev(const char* str)
128 {
129         char b[256];
130         char e[256];
131         HANDLE* s;
132         LPCTSTR msg = b;
133         /* print quickly to keep GetLastError value */
134         wsvc_err2str(e, sizeof(e), str, GetLastError());
135         snprintf(b, sizeof(b), "%s: %s", SERVICE_NAME, e);
136         s = RegisterEventSource(NULL, SERVICE_NAME);
137         if(!s) return;
138         ReportEvent(s, /* event log */
139                 EVENTLOG_ERROR_TYPE, /* event type */
140                 0, /* event category */
141                 MSG_GENERIC_ERR, /* event ID (from gen_msg.mc) */
142                 NULL, /* user security context */
143                 1, /* numstrings */
144                 0, /* binary size */
145                 &msg, /* strings */
146                 NULL); /* binary data */
147         DeregisterEventSource(s);
148 }
149
150 /**
151  * Obtain registry string (if it exists).
152  * @param key: key string
153  * @param name: name of value to fetch.
154  * @return malloced string with the result or NULL if it did not
155  * exist on an error (logged) was encountered.
156  */
157 static char*
158 lookup_reg_str(const char* key, const char* name)
159 {
160         HKEY hk = NULL;
161         DWORD type = 0;
162         BYTE buf[1024];
163         DWORD len = (DWORD)sizeof(buf);
164         LONG ret;
165         char* result = NULL;
166         ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hk);
167         if(ret == ERROR_FILE_NOT_FOUND)
168                 return NULL; /* key does not exist */
169         else if(ret != ERROR_SUCCESS) {
170                 reportev("RegOpenKeyEx failed");
171                 return NULL;
172         }
173         ret = RegQueryValueEx(hk, (LPCTSTR)name, 0, &type, buf, &len);
174         if(RegCloseKey(hk))
175                 reportev("RegCloseKey");
176         if(ret == ERROR_FILE_NOT_FOUND)
177                 return NULL; /* name does not exist */
178         else if(ret != ERROR_SUCCESS) {
179                 reportev("RegQueryValueEx failed");
180                 return NULL;
181         }
182         if(type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ) {
183                 buf[sizeof(buf)-1] = 0;
184                 buf[sizeof(buf)-2] = 0; /* for multi_sz */
185                 result = strdup((char*)buf);
186                 if(!result) reportev("out of memory");
187         }
188         return result;
189 }
190
191 /**
192  * Obtain registry integer (if it exists).
193  * @param key: key string
194  * @param name: name of value to fetch.
195  * @return integer value (if it exists), or 0 on error.
196  */
197 static int
198 lookup_reg_int(const char* key, const char* name)
199 {
200         HKEY hk = NULL;
201         DWORD type = 0;
202         BYTE buf[1024];
203         DWORD len = (DWORD)sizeof(buf);
204         LONG ret;
205         int result = 0;
206         ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hk);
207         if(ret == ERROR_FILE_NOT_FOUND)
208                 return 0; /* key does not exist */
209         else if(ret != ERROR_SUCCESS) {
210                 reportev("RegOpenKeyEx failed");
211                 return 0;
212         }
213         ret = RegQueryValueEx(hk, (LPCTSTR)name, 0, &type, buf, &len);
214         if(RegCloseKey(hk))
215                 reportev("RegCloseKey");
216         if(ret == ERROR_FILE_NOT_FOUND)
217                 return 0; /* name does not exist */
218         else if(ret != ERROR_SUCCESS) {
219                 reportev("RegQueryValueEx failed");
220                 return 0;
221         }
222         if(type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ) {
223                 buf[sizeof(buf)-1] = 0;
224                 buf[sizeof(buf)-2] = 0; /* for multi_sz */
225                 result = atoi((char*)buf);
226         } else if(type == REG_DWORD) {
227                 DWORD r;
228                 memmove(&r, buf, sizeof(r));
229                 result = r;
230         } 
231         return result;
232 }
233
234 /** wait for unbound-anchor process to finish */
235 static void
236 waitforubanchor(PROCESS_INFORMATION* pinfo)
237 {
238         /* we have 5 seconds scheduled for it, usually it will be very fast,
239          * with only a UDP message or two (100 msec or so), but the https
240          * connections could take some time */
241         DWORD count = 7900;
242         DWORD ret = WAIT_TIMEOUT;
243         /* decrease timer every 1/10 second, we are still starting up */
244         while(ret == WAIT_TIMEOUT) {
245                 ret = WaitForSingleObject(pinfo->hProcess, 100);
246                 if(count > 4000) count -= 100;
247                 else count--; /* go slow, it is taking long */
248                 if(count > 3000)
249                         report_status(SERVICE_START_PENDING, NO_ERROR, count);
250         }
251         verbose(VERB_ALGO, "unbound-anchor done");
252         if(ret != WAIT_OBJECT_0) {
253                 return; /* did not end successfully */
254         }
255         if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
256                 log_err("GetExitCodeProcess failed");
257                 return;
258         }
259         verbose(VERB_ALGO, "unbound-anchor exit code is %d", (int)ret);
260         if(ret != 0) {
261                 log_info("The root trust anchor has been updated.");
262         }
263 }
264
265
266 /**
267  * Perform root anchor update if so configured, by calling that process
268  */
269 static void
270 call_root_update(void)
271 {
272         char* rootanchor;
273         rootanchor = lookup_reg_str("Software\\Unbound", "RootAnchor");
274         if(rootanchor && strlen(rootanchor)>0) {
275                 STARTUPINFO sinfo;
276                 PROCESS_INFORMATION pinfo;
277                 memset(&pinfo, 0, sizeof(pinfo));
278                 memset(&sinfo, 0, sizeof(sinfo));
279                 sinfo.cb = sizeof(sinfo);
280                 verbose(VERB_ALGO, "rootanchor: %s", rootanchor);
281                 report_status(SERVICE_START_PENDING, NO_ERROR, 8000);
282                 if(!CreateProcess(NULL, rootanchor, NULL, NULL, 0, 
283                         CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
284                         log_err("CreateProcess error for unbound-anchor.exe");
285                 else {
286                         waitforubanchor(&pinfo);
287                         CloseHandle(pinfo.hProcess);
288                         CloseHandle(pinfo.hThread);
289                 }
290         }
291         free(rootanchor);
292 }
293
294 /**
295  * Init service. Keeps calling status pending to tell service control
296  * manager that this process is not hanging.
297  * @param r: restart, true on restart
298  * @param d: daemon returned here.
299  * @param c: config file returned here.
300  * @return false if failed.
301  */
302 static int
303 service_init(int r, struct daemon** d, struct config_file** c)
304 {
305         struct config_file* cfg = NULL;
306         struct daemon* daemon = NULL;
307
308         if(!service_cfgfile) {
309                 char* newf = lookup_reg_str("Software\\Unbound", "ConfigFile");
310                 if(newf) service_cfgfile = newf;
311                 else    service_cfgfile = strdup(CONFIGFILE);
312                 if(!service_cfgfile) fatal_exit("out of memory");
313         }
314
315         /* create daemon */
316         if(r)   daemon = *d;
317         else    daemon = daemon_init();
318         if(!daemon) return 0;
319         if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2800);
320
321         /* read config */
322         cfg = config_create();
323         if(!cfg) return 0;
324         if(!config_read(cfg, service_cfgfile, daemon->chroot)) {
325                 if(errno != ENOENT) {
326                         log_err("error in config file");
327                         return 0;
328                 }
329                 log_warn("could not open config file, using defaults");
330         }
331         if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2600);
332
333         verbose(VERB_QUERY, "winservice - apply settings");
334         /* apply settings and init */
335         verbosity = cfg->verbosity + service_cmdline_verbose;
336         if(cfg->directory && cfg->directory[0]) {
337                 if(chdir(cfg->directory)) {
338                         log_err("could not chdir to %s: %s", 
339                                 cfg->directory, strerror(errno));
340                         if(errno != ENOENT)
341                                 return 0;
342                         log_warn("could not change directory - continuing");
343                 } else
344                         verbose(VERB_QUERY, "chdir to %s", cfg->directory);
345         }
346         log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
347         if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2400);
348         verbose(VERB_QUERY, "winservice - apply cfg");
349         daemon_apply_cfg(daemon, cfg);
350
351         if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2300);
352         if(!(daemon->rc = daemon_remote_create(cfg))) {
353                 log_err("could not set up remote-control");
354                 daemon_delete(daemon);
355                 config_delete(cfg);
356                 return 0;
357         }
358
359         /* open ports */
360         /* keep reporting that we are busy starting */
361         if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2200);
362         verbose(VERB_QUERY, "winservice - open ports");
363         if(!daemon_open_shared_ports(daemon)) return 0;
364         verbose(VERB_QUERY, "winservice - ports opened");
365         if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2000);
366
367         *d = daemon;
368         *c = cfg;
369         return 1;
370 }
371
372 /**
373  * Deinit the service
374  */
375 static void
376 service_deinit(struct daemon* daemon, struct config_file* cfg)
377 {
378         daemon_cleanup(daemon);
379         config_delete(cfg);
380         daemon_delete(daemon);
381 }
382
383 /**
384  * The main function for the service.
385  * Called by the services API when starting unbound on windows in background.
386  * Arguments could have been present in the string 'path'.
387  * @param argc: nr args
388  * @param argv: arg text.
389  */
390 static void 
391 service_main(DWORD ATTR_UNUSED(argc), LPTSTR* ATTR_UNUSED(argv))
392 {
393         struct config_file* cfg = NULL;
394         struct daemon* daemon = NULL;
395
396         service_status_handle = RegisterServiceCtrlHandler(SERVICE_NAME, 
397                 (LPHANDLER_FUNCTION)hdlr);
398         if(!service_status_handle) {
399                 reportev("Could not RegisterServiceCtrlHandler");
400                 return;
401         }
402         
403         service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
404         service_status.dwServiceSpecificExitCode = 0;
405
406         /* see if we have root anchor update enabled */
407         call_root_update();
408
409         /* we are now starting up */
410         report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
411         if(!service_init(0, &daemon, &cfg)) {
412                 reportev("Could not service_init");
413                 report_status(SERVICE_STOPPED, NO_ERROR, 0);
414                 return;
415         }
416
417         /* event that gets signalled when we want to quit; it
418          * should get registered in the worker-0 waiting loop. */
419         service_stop_event = WSACreateEvent();
420         if(service_stop_event == WSA_INVALID_EVENT) {
421                 log_err("WSACreateEvent: %s", wsa_strerror(WSAGetLastError()));
422                 reportev("Could not WSACreateEvent");
423                 report_status(SERVICE_STOPPED, NO_ERROR, 0);
424                 return;
425         }
426         if(!WSAResetEvent(service_stop_event)) {
427                 log_err("WSAResetEvent: %s", wsa_strerror(WSAGetLastError()));
428         }
429
430         /* SetServiceStatus SERVICE_RUNNING;*/
431         report_status(SERVICE_RUNNING, NO_ERROR, 0);
432         verbose(VERB_QUERY, "winservice - init complete");
433
434         /* daemon performs work */
435         while(!service_stop_shutdown) {
436                 daemon_fork(daemon);
437                 if(!service_stop_shutdown) {
438                         daemon_cleanup(daemon);
439                         config_delete(cfg); cfg=NULL;
440                         if(!service_init(1, &daemon, &cfg)) {
441                                 reportev("Could not service_init");
442                                 report_status(SERVICE_STOPPED, NO_ERROR, 0);
443                                 return;
444                         }
445                 }
446         }
447
448         /* exit */
449         verbose(VERB_ALGO, "winservice - cleanup.");
450         report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
451         service_deinit(daemon, cfg);
452         free(service_cfgfile);
453         if(service_stop_event) (void)WSACloseEvent(service_stop_event);
454         verbose(VERB_QUERY, "winservice - full stop");
455         report_status(SERVICE_STOPPED, NO_ERROR, 0);
456 }
457
458 /** start the service */
459 static void 
460 service_start(const char* cfgfile, int v, int c)
461 {
462         SERVICE_TABLE_ENTRY myservices[2] = {
463                 {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main},
464                 {NULL, NULL} };
465         verbosity=v;
466         if(verbosity >= VERB_QUERY) {
467                 /* log to file about start sequence */
468                 fclose(fopen("C:\\unbound.log", "w"));
469                 log_init("C:\\unbound.log", 0, 0);
470                 verbose(VERB_QUERY, "open logfile");
471         } else log_init(0, 1, 0); /* otherwise, use Application log */
472         if(c) {
473                 service_cfgfile = strdup(cfgfile);
474                 if(!service_cfgfile) fatal_exit("out of memory");
475         } else  service_cfgfile = NULL;
476         service_cmdline_verbose = v;
477         /* this call returns when service has stopped. */
478         if(!StartServiceCtrlDispatcher(myservices)) {
479                 reportev("Could not StartServiceCtrlDispatcher");
480         }
481 }
482
483 void
484 wsvc_command_option(const char* wopt, const char* cfgfile, int v, int c)
485 {
486         if(strcmp(wopt, "install") == 0)
487                 wsvc_install(stdout, NULL);
488         else if(strcmp(wopt, "remove") == 0)
489                 wsvc_remove(stdout);
490         else if(strcmp(wopt, "service") == 0)
491                 service_start(cfgfile, v, c);
492         else if(strcmp(wopt, "start") == 0)
493                 wsvc_rc_start(stdout);
494         else if(strcmp(wopt, "stop") == 0)
495                 wsvc_rc_stop(stdout);
496         else fatal_exit("unknown option: %s", wopt);
497         exit(0);
498 }
499
500 void
501 worker_win_stop_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void* arg)
502 {
503         struct worker* worker = (struct worker*)arg;
504         verbose(VERB_QUERY, "caught stop signal (wsaevent)");
505         worker->need_to_exit = 1;
506         comm_base_exit(worker->base);
507 }
508
509 /** wait for cron process to finish */
510 static void
511 waitforit(PROCESS_INFORMATION* pinfo)
512 {
513         DWORD ret = WaitForSingleObject(pinfo->hProcess, INFINITE);
514         verbose(VERB_ALGO, "cronaction done");
515         if(ret != WAIT_OBJECT_0) {
516                 return; /* did not end successfully */
517         }
518         if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
519                 log_err("GetExitCodeProcess failed");
520                 return;
521         }
522         verbose(VERB_ALGO, "exit code is %d", (int)ret);
523         if(ret != 1) {
524                 if(!WSASetEvent(service_stop_event))
525                         log_err("Could not WSASetEvent: %s",
526                         wsa_strerror(WSAGetLastError()));
527         }
528 }
529
530 /** Do the cron action and wait for result exit value */
531 static void*
532 win_do_cron(void* ATTR_UNUSED(arg))
533 {
534         int mynum=65;
535         char* cronaction;
536         log_thread_set(&mynum);
537         cronaction = lookup_reg_str("Software\\Unbound", "CronAction");
538         if(cronaction && strlen(cronaction)>0) {
539                 STARTUPINFO sinfo;
540                 PROCESS_INFORMATION pinfo;
541                 memset(&pinfo, 0, sizeof(pinfo));
542                 memset(&sinfo, 0, sizeof(sinfo));
543                 sinfo.cb = sizeof(sinfo);
544                 verbose(VERB_ALGO, "cronaction: %s", cronaction);
545                 if(!CreateProcess(NULL, cronaction, NULL, NULL, 0, 
546                         CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
547                         log_err("CreateProcess error");
548                 else {
549                         waitforit(&pinfo);
550                         CloseHandle(pinfo.hProcess);
551                         CloseHandle(pinfo.hThread);
552                 }
553         }
554         free(cronaction);
555         /* stop self */
556         CloseHandle(cron_thread);
557         cron_thread = NULL;
558         return NULL;
559 }
560
561 /** Set the timer for cron for the next wake up */
562 static void
563 set_cron_timer()
564 {
565         struct timeval tv;
566         int crontime;
567         if(cron_was_quick == 0) {
568                 cron_was_quick = 1;
569                 crontime = 3600; /* first update some time after boot */
570         } else {
571                 crontime = lookup_reg_int("Software\\Unbound", "CronTime");
572                 if(crontime == 0) crontime = 60*60*24; /* 24 hours */
573         }
574         memset(&tv, 0, sizeof(tv));
575         tv.tv_sec = (time_t)crontime;
576         comm_timer_set(service_cron, &tv);
577 }
578
579 void
580 wsvc_cron_cb(void* arg)
581 {
582         struct worker* worker = (struct worker*)arg;
583         /* perform cronned operation */
584         verbose(VERB_ALGO, "cron timer callback");
585         if(cron_thread == NULL) {
586                 /* create new thread to do it */
587                 ub_thread_create(&cron_thread, win_do_cron, worker);
588         }
589         /* reschedule */
590         set_cron_timer();
591 }
592
593 void wsvc_setup_worker(struct worker* worker)
594 {
595         /* if not started with -w service, do nothing */
596         if(!service_stop_event)
597                 return;
598         if(!winsock_register_wsaevent(comm_base_internal(worker->base),
599                 &service_stop_ev, service_stop_event,
600                 &worker_win_stop_cb, worker)) {
601                 fatal_exit("could not register wsaevent");
602                 return;
603         }
604         if(!service_cron) {
605                 service_cron = comm_timer_create(worker->base, 
606                         wsvc_cron_cb, worker);
607                 if(!service_cron)
608                         fatal_exit("could not create cron timer");
609                 set_cron_timer();
610         }
611 }
612
613 void wsvc_desetup_worker(struct worker* ATTR_UNUSED(worker))
614 {
615         comm_timer_delete(service_cron);
616         service_cron = NULL;
617 }