]> git.saurik.com Git - apple/network_cmds.git/blob - unbound/winrc/win_svc.c
57a160d6ae73098d985ec0edf1543fd28f9162eb
[apple/network_cmds.git] / unbound / 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
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE 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 #ifdef DOXYGEN
384 #define ATTR_UNUSED(x) x
385 #endif
386 /**
387 * The main function for the service.
388 * Called by the services API when starting unbound on windows in background.
389 * Arguments could have been present in the string 'path'.
390 * @param argc: nr args
391 * @param argv: arg text.
392 */
393 static void
394 service_main(DWORD ATTR_UNUSED(argc), LPTSTR* ATTR_UNUSED(argv))
395 {
396 struct config_file* cfg = NULL;
397 struct daemon* daemon = NULL;
398
399 service_status_handle = RegisterServiceCtrlHandler(SERVICE_NAME,
400 (LPHANDLER_FUNCTION)hdlr);
401 if(!service_status_handle) {
402 reportev("Could not RegisterServiceCtrlHandler");
403 return;
404 }
405
406 service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
407 service_status.dwServiceSpecificExitCode = 0;
408
409 /* see if we have root anchor update enabled */
410 call_root_update();
411
412 /* we are now starting up */
413 report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
414 if(!service_init(0, &daemon, &cfg)) {
415 reportev("Could not service_init");
416 report_status(SERVICE_STOPPED, NO_ERROR, 0);
417 return;
418 }
419
420 /* event that gets signalled when we want to quit; it
421 * should get registered in the worker-0 waiting loop. */
422 service_stop_event = WSACreateEvent();
423 if(service_stop_event == WSA_INVALID_EVENT) {
424 log_err("WSACreateEvent: %s", wsa_strerror(WSAGetLastError()));
425 reportev("Could not WSACreateEvent");
426 report_status(SERVICE_STOPPED, NO_ERROR, 0);
427 return;
428 }
429 if(!WSAResetEvent(service_stop_event)) {
430 log_err("WSAResetEvent: %s", wsa_strerror(WSAGetLastError()));
431 }
432
433 /* SetServiceStatus SERVICE_RUNNING;*/
434 report_status(SERVICE_RUNNING, NO_ERROR, 0);
435 verbose(VERB_QUERY, "winservice - init complete");
436
437 /* daemon performs work */
438 while(!service_stop_shutdown) {
439 daemon_fork(daemon);
440 if(!service_stop_shutdown) {
441 daemon_cleanup(daemon);
442 config_delete(cfg); cfg=NULL;
443 if(!service_init(1, &daemon, &cfg)) {
444 reportev("Could not service_init");
445 report_status(SERVICE_STOPPED, NO_ERROR, 0);
446 return;
447 }
448 }
449 }
450
451 /* exit */
452 verbose(VERB_ALGO, "winservice - cleanup.");
453 report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
454 service_deinit(daemon, cfg);
455 free(service_cfgfile);
456 if(service_stop_event) (void)WSACloseEvent(service_stop_event);
457 verbose(VERB_QUERY, "winservice - full stop");
458 report_status(SERVICE_STOPPED, NO_ERROR, 0);
459 }
460
461 /** start the service */
462 static void
463 service_start(const char* cfgfile, int v, int c)
464 {
465 SERVICE_TABLE_ENTRY myservices[2] = {
466 {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main},
467 {NULL, NULL} };
468 verbosity=v;
469 if(verbosity >= VERB_QUERY) {
470 /* log to file about start sequence */
471 fclose(fopen("C:\\unbound.log", "w"));
472 log_init("C:\\unbound.log", 0, 0);
473 verbose(VERB_QUERY, "open logfile");
474 } else log_init(0, 1, 0); /* otherwise, use Application log */
475 if(c) {
476 service_cfgfile = strdup(cfgfile);
477 if(!service_cfgfile) fatal_exit("out of memory");
478 } else service_cfgfile = NULL;
479 service_cmdline_verbose = v;
480 /* this call returns when service has stopped. */
481 if(!StartServiceCtrlDispatcher(myservices)) {
482 reportev("Could not StartServiceCtrlDispatcher");
483 }
484 }
485
486 void
487 wsvc_command_option(const char* wopt, const char* cfgfile, int v, int c)
488 {
489 if(strcmp(wopt, "install") == 0)
490 wsvc_install(stdout, NULL);
491 else if(strcmp(wopt, "remove") == 0)
492 wsvc_remove(stdout);
493 else if(strcmp(wopt, "service") == 0)
494 service_start(cfgfile, v, c);
495 else if(strcmp(wopt, "start") == 0)
496 wsvc_rc_start(stdout);
497 else if(strcmp(wopt, "stop") == 0)
498 wsvc_rc_stop(stdout);
499 else fatal_exit("unknown option: %s", wopt);
500 exit(0);
501 }
502
503 void
504 worker_win_stop_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void* arg)
505 {
506 struct worker* worker = (struct worker*)arg;
507 verbose(VERB_QUERY, "caught stop signal (wsaevent)");
508 worker->need_to_exit = 1;
509 comm_base_exit(worker->base);
510 }
511
512 /** wait for cron process to finish */
513 static void
514 waitforit(PROCESS_INFORMATION* pinfo)
515 {
516 DWORD ret = WaitForSingleObject(pinfo->hProcess, INFINITE);
517 verbose(VERB_ALGO, "cronaction done");
518 if(ret != WAIT_OBJECT_0) {
519 return; /* did not end successfully */
520 }
521 if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
522 log_err("GetExitCodeProcess failed");
523 return;
524 }
525 verbose(VERB_ALGO, "exit code is %d", (int)ret);
526 if(ret != 1) {
527 if(!WSASetEvent(service_stop_event))
528 log_err("Could not WSASetEvent: %s",
529 wsa_strerror(WSAGetLastError()));
530 }
531 }
532
533 /** Do the cron action and wait for result exit value */
534 static void*
535 win_do_cron(void* ATTR_UNUSED(arg))
536 {
537 int mynum=65;
538 char* cronaction;
539 log_thread_set(&mynum);
540 cronaction = lookup_reg_str("Software\\Unbound", "CronAction");
541 if(cronaction && strlen(cronaction)>0) {
542 STARTUPINFO sinfo;
543 PROCESS_INFORMATION pinfo;
544 memset(&pinfo, 0, sizeof(pinfo));
545 memset(&sinfo, 0, sizeof(sinfo));
546 sinfo.cb = sizeof(sinfo);
547 verbose(VERB_ALGO, "cronaction: %s", cronaction);
548 if(!CreateProcess(NULL, cronaction, NULL, NULL, 0,
549 CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
550 log_err("CreateProcess error");
551 else {
552 waitforit(&pinfo);
553 CloseHandle(pinfo.hProcess);
554 CloseHandle(pinfo.hThread);
555 }
556 }
557 free(cronaction);
558 /* stop self */
559 CloseHandle(cron_thread);
560 cron_thread = NULL;
561 return NULL;
562 }
563
564 /** Set the timer for cron for the next wake up */
565 static void
566 set_cron_timer()
567 {
568 struct timeval tv;
569 int crontime;
570 if(cron_was_quick == 0) {
571 cron_was_quick = 1;
572 crontime = 3600; /* first update some time after boot */
573 } else {
574 crontime = lookup_reg_int("Software\\Unbound", "CronTime");
575 if(crontime == 0) crontime = 60*60*24; /* 24 hours */
576 }
577 memset(&tv, 0, sizeof(tv));
578 tv.tv_sec = (time_t)crontime;
579 comm_timer_set(service_cron, &tv);
580 }
581
582 void
583 wsvc_cron_cb(void* arg)
584 {
585 struct worker* worker = (struct worker*)arg;
586 /* perform cronned operation */
587 verbose(VERB_ALGO, "cron timer callback");
588 if(cron_thread == NULL) {
589 /* create new thread to do it */
590 ub_thread_create(&cron_thread, win_do_cron, worker);
591 }
592 /* reschedule */
593 set_cron_timer();
594 }
595
596 void wsvc_setup_worker(struct worker* worker)
597 {
598 /* if not started with -w service, do nothing */
599 if(!service_stop_event)
600 return;
601 if(!winsock_register_wsaevent(comm_base_internal(worker->base),
602 &service_stop_ev, service_stop_event,
603 &worker_win_stop_cb, worker)) {
604 fatal_exit("could not register wsaevent");
605 return;
606 }
607 if(!service_cron) {
608 service_cron = comm_timer_create(worker->base,
609 wsvc_cron_cb, worker);
610 if(!service_cron)
611 fatal_exit("could not create cron timer");
612 set_cron_timer();
613 }
614 }
615
616 void wsvc_desetup_worker(struct worker* ATTR_UNUSED(worker))
617 {
618 comm_timer_delete(service_cron);
619 service_cron = NULL;
620 }