xref: /freebsd/contrib/unbound/winrc/win_svc.c (revision b2efd602aea8b3cbc3fb215b9611946d04fceb10)
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/ub_event.h"
55 #include "util/net_help.h"
56 
57 /** global service status */
58 static SERVICE_STATUS	service_status;
59 /** global service status handle */
60 static SERVICE_STATUS_HANDLE service_status_handle;
61 /** global service stop event */
62 static WSAEVENT service_stop_event = NULL;
63 /** event struct for stop callbacks */
64 static struct ub_event* service_stop_ev = NULL;
65 /** if stop even means shutdown or restart */
66 static int service_stop_shutdown = 0;
67 /** config file to open. global communication to service_main() */
68 static char* service_cfgfile = CONFIGFILE;
69 /** commandline verbosity. global communication to service_main() */
70 static int service_cmdline_verbose = 0;
71 /** the cron callback */
72 static struct comm_timer* service_cron = NULL;
73 /** the cron thread */
74 static ub_thread_type cron_thread = NULL;
75 /** if cron has already done its quick check */
76 static int cron_was_quick = 0;
77 
78 /**
79  * Report current service status to service control manager
80  * @param state: current state
81  * @param exitcode: error code (when stopped)
82  * @param wait: pending operation estimated time in milliseconds.
83  */
report_status(DWORD state,DWORD exitcode,DWORD wait)84 static void report_status(DWORD state, DWORD exitcode, DWORD wait)
85 {
86 	static DWORD checkpoint = 1;
87 	service_status.dwCurrentState = state;
88 	service_status.dwWin32ExitCode = exitcode;
89 	service_status.dwWaitHint = wait;
90 	if(state == SERVICE_START_PENDING)
91 		service_status.dwControlsAccepted = 0;
92 	else 	service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
93 	if(state == SERVICE_RUNNING || state == SERVICE_STOPPED)
94 		service_status.dwCheckPoint = 0;
95 	else	service_status.dwCheckPoint = checkpoint++;
96 	SetServiceStatus(service_status_handle, &service_status);
97 }
98 
99 /**
100  * Service control handler. Called by serviceControlManager when a control
101  * code is sent to the service (with ControlService).
102  * @param ctrl: control code
103  */
104 static void
hdlr(DWORD ctrl)105 hdlr(DWORD ctrl)
106 {
107 	if(ctrl == SERVICE_CONTROL_STOP) {
108 		report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
109 		service_stop_shutdown = 1;
110 		/* send signal to stop */
111 		if(!WSASetEvent(service_stop_event))
112 			log_err("Could not WSASetEvent: %s",
113 				wsa_strerror(WSAGetLastError()));
114 		return;
115 	} else {
116 		/* ctrl == SERVICE_CONTROL_INTERROGATE or whatever */
117 		/* update status */
118 		report_status(service_status.dwCurrentState, NO_ERROR, 0);
119 	}
120 }
121 
122 /**
123  * report event to system event log
124  * For use during startup and shutdown.
125  * @param str: the error
126  */
127 static void
reportev(const char * str)128 reportev(const char* str)
129 {
130 	char b[256];
131 	char e[256];
132 	HANDLE* s;
133 	LPCTSTR msg = b;
134 	/* print quickly to keep GetLastError value */
135 	wsvc_err2str(e, sizeof(e), str, GetLastError());
136 	snprintf(b, sizeof(b), "%s: %s", SERVICE_NAME, e);
137 	s = RegisterEventSource(NULL, SERVICE_NAME);
138 	if(!s) return;
139 	ReportEvent(s, /* event log */
140 		EVENTLOG_ERROR_TYPE, /* event type */
141 		0, /* event category */
142 		MSG_GENERIC_ERR, /* event ID (from gen_msg.mc) */
143 		NULL, /* user security context */
144 		1, /* numstrings */
145 		0, /* binary size */
146 		&msg, /* strings */
147 		NULL); /* binary data */
148 	DeregisterEventSource(s);
149 }
150 
151 /**
152  * Obtain registry string (if it exists).
153  * @param key: key string
154  * @param name: name of value to fetch.
155  * @return malloced string with the result or NULL if it did not
156  * exist on an error (logged) was encountered.
157  */
158 static char*
lookup_reg_str(const char * key,const char * name)159 lookup_reg_str(const char* key, const char* name)
160 {
161 	HKEY hk = NULL;
162 	DWORD type = 0;
163 	BYTE buf[1024];
164 	DWORD len = (DWORD)sizeof(buf);
165 	LONG ret;
166 	char* result = NULL;
167 	ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hk);
168 	if(ret == ERROR_FILE_NOT_FOUND)
169 		return NULL; /* key does not exist */
170 	else if(ret != ERROR_SUCCESS) {
171 		reportev("RegOpenKeyEx failed");
172 		return NULL;
173 	}
174 	ret = RegQueryValueEx(hk, (LPCTSTR)name, 0, &type, buf, &len);
175 	if(RegCloseKey(hk))
176 		reportev("RegCloseKey");
177 	if(ret == ERROR_FILE_NOT_FOUND)
178 		return NULL; /* name does not exist */
179 	else if(ret != ERROR_SUCCESS) {
180 		reportev("RegQueryValueEx failed");
181 		return NULL;
182 	}
183 	if(type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ) {
184 		buf[sizeof(buf)-1] = 0;
185 		buf[sizeof(buf)-2] = 0; /* for multi_sz */
186 		result = strdup((char*)buf);
187 		if(!result) reportev("out of memory");
188 	}
189 	return result;
190 }
191 
192 /**
193  * Obtain registry integer (if it exists).
194  * @param key: key string
195  * @param name: name of value to fetch.
196  * @return integer value (if it exists), or 0 on error.
197  */
198 static int
lookup_reg_int(const char * key,const char * name)199 lookup_reg_int(const char* key, const char* name)
200 {
201 	HKEY hk = NULL;
202 	DWORD type = 0;
203 	BYTE buf[1024];
204 	DWORD len = (DWORD)sizeof(buf);
205 	LONG ret;
206 	int result = 0;
207 	ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hk);
208 	if(ret == ERROR_FILE_NOT_FOUND)
209 		return 0; /* key does not exist */
210 	else if(ret != ERROR_SUCCESS) {
211 		reportev("RegOpenKeyEx failed");
212 		return 0;
213 	}
214 	ret = RegQueryValueEx(hk, (LPCTSTR)name, 0, &type, buf, &len);
215 	if(RegCloseKey(hk))
216 		reportev("RegCloseKey");
217 	if(ret == ERROR_FILE_NOT_FOUND)
218 		return 0; /* name does not exist */
219 	else if(ret != ERROR_SUCCESS) {
220 		reportev("RegQueryValueEx failed");
221 		return 0;
222 	}
223 	if(type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ) {
224 		buf[sizeof(buf)-1] = 0;
225 		buf[sizeof(buf)-2] = 0; /* for multi_sz */
226 		result = atoi((char*)buf);
227 	} else if(type == REG_DWORD) {
228 		DWORD r;
229 		memmove(&r, buf, sizeof(r));
230 		result = r;
231 	}
232 	return result;
233 }
234 
235 /** wait for unbound-anchor process to finish */
236 static void
waitforubanchor(PROCESS_INFORMATION * pinfo)237 waitforubanchor(PROCESS_INFORMATION* pinfo)
238 {
239 	/* we have 5 seconds scheduled for it, usually it will be very fast,
240 	 * with only a UDP message or two (100 msec or so), but the https
241 	 * connections could take some time */
242 	DWORD count = 7900;
243 	DWORD ret = WAIT_TIMEOUT;
244 	/* decrease timer every 1/10 second, we are still starting up */
245 	while(ret == WAIT_TIMEOUT) {
246 		ret = WaitForSingleObject(pinfo->hProcess, 100);
247 		if(count > 4000) count -= 100;
248 		else count--; /* go slow, it is taking long */
249 		if(count > 3000)
250 			report_status(SERVICE_START_PENDING, NO_ERROR, count);
251 	}
252 	verbose(VERB_ALGO, "unbound-anchor done");
253 	if(ret != WAIT_OBJECT_0) {
254 		return; /* did not end successfully */
255 	}
256 	if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
257 		log_err("GetExitCodeProcess failed");
258 		return;
259 	}
260 	verbose(VERB_ALGO, "unbound-anchor exit code is %d", (int)ret);
261 	if(ret != 0) {
262 		log_info("The root trust anchor has been updated.");
263 	}
264 }
265 
266 
267 /**
268  * Perform root anchor update if so configured, by calling that process
269  */
270 static void
call_root_update(void)271 call_root_update(void)
272 {
273 	char* rootanchor;
274  	rootanchor = lookup_reg_str("Software\\Unbound", "RootAnchor");
275 	if(rootanchor && strlen(rootanchor)>0) {
276 		STARTUPINFO sinfo;
277 		PROCESS_INFORMATION pinfo;
278 		memset(&pinfo, 0, sizeof(pinfo));
279 		memset(&sinfo, 0, sizeof(sinfo));
280 		sinfo.cb = sizeof(sinfo);
281 		verbose(VERB_ALGO, "rootanchor: %s", rootanchor);
282 		report_status(SERVICE_START_PENDING, NO_ERROR, 8000);
283 		if(!CreateProcess(NULL, rootanchor, NULL, NULL, 0,
284 			CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
285 			log_err("CreateProcess error for unbound-anchor.exe");
286 		else {
287 			waitforubanchor(&pinfo);
288 			CloseHandle(pinfo.hProcess);
289 			CloseHandle(pinfo.hThread);
290 		}
291 	}
292 	free(rootanchor);
293 }
294 
295 /**
296  * Init service. Keeps calling status pending to tell service control
297  * manager that this process is not hanging.
298  * @param r: restart, true on restart
299  * @param d: daemon returned here.
300  * @param c: config file returned here.
301  * @return false if failed.
302  */
303 static int
service_init(int r,struct daemon ** d,struct config_file ** c)304 service_init(int r, struct daemon** d, struct config_file** c)
305 {
306 	struct config_file* cfg = NULL;
307 	struct daemon* daemon = NULL;
308 
309 	if(!service_cfgfile) {
310 		char* newf = lookup_reg_str("Software\\Unbound", "ConfigFile");
311 		if(newf) service_cfgfile = newf;
312 		else 	service_cfgfile = strdup(CONFIGFILE);
313 		if(!service_cfgfile) fatal_exit("out of memory");
314 	}
315 
316 	/* create daemon */
317 	if(r) 	daemon = *d;
318 	else 	daemon = daemon_init();
319 	if(!daemon) return 0;
320 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2800);
321 
322 	/* read config */
323 	cfg = config_create();
324 	if(!cfg) return 0;
325 	if(!config_read(cfg, service_cfgfile, daemon->chroot)) {
326 		if(errno != ENOENT) {
327 			log_err("error in config file");
328 			return 0;
329 		}
330 		log_warn("could not open config file, using defaults");
331 		config_auto_slab_values(cfg);
332 	}
333 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2600);
334 
335 	verbose(VERB_QUERY, "winservice - apply settings");
336 	/* apply settings and init */
337 	verbosity = cfg->verbosity + service_cmdline_verbose;
338 	w_config_adjust_directory(cfg);
339 	if(cfg->directory && cfg->directory[0]) {
340 		char* dir = cfg->directory;
341 		if(chdir(dir)) {
342 			log_err("could not chdir to %s: %s",
343 				dir, strerror(errno));
344 			if(errno != ENOENT)
345 				return 0;
346 			log_warn("could not change directory - continuing");
347 		} else
348 			verbose(VERB_QUERY, "chdir to %s", dir);
349 	}
350 	log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
351 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2400);
352 	verbose(VERB_QUERY, "winservice - apply cfg");
353 	daemon_apply_cfg(daemon, cfg);
354 
355 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2300);
356 	if(!r) {
357 		if(!daemon_privileged(daemon))
358 			fatal_exit("could not do privileged setup");
359 	}
360 	if(!(daemon->rc = daemon_remote_create(cfg))) {
361 		log_err("could not set up remote-control");
362 		daemon_delete(daemon);
363 		config_delete(cfg);
364 		return 0;
365 	}
366 	if(cfg->ssl_service_key && cfg->ssl_service_key[0]) {
367 		if(!(daemon->listen_dot_sslctx = listen_sslctx_create(
368 			cfg->ssl_service_key, cfg->ssl_service_pem, NULL,
369 			cfg->tls_ciphers, cfg->tls_ciphersuites,
370 			(cfg->tls_session_ticket_keys.first &&
371 			cfg->tls_session_ticket_keys.first->str[0] != 0),
372 			1, 0))) {
373 			fatal_exit("could not set up listen SSL_CTX");
374 		}
375 #ifdef HAVE_NGHTTP2_NGHTTP2_H
376 		if(cfg_has_https(cfg)) {
377 			if(!(daemon->listen_doh_sslctx = listen_sslctx_create(
378 				cfg->ssl_service_key, cfg->ssl_service_pem, NULL,
379 				cfg->tls_ciphers, cfg->tls_ciphersuites,
380 				(cfg->tls_session_ticket_keys.first &&
381 				cfg->tls_session_ticket_keys.first->str[0] != 0),
382 				0, 1))) {
383 				fatal_exit("could not set up listen doh SSL_CTX");
384 			}
385 		}
386 #endif
387 #ifdef HAVE_NGTCP2
388 		if(cfg_has_quic(cfg)) {
389 			if(!(daemon->listen_quic_sslctx = quic_sslctx_create(
390 				cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) {
391 				fatal_exit("could not set up quic SSL_CTX");
392 			}
393 		}
394 #endif /* HAVE_NGTCP2 */
395 	}
396 	if(!(daemon->connect_dot_sslctx = connect_sslctx_create(NULL, NULL,
397 		cfg->tls_cert_bundle, cfg->tls_win_cert)))
398 		fatal_exit("could not set up connect SSL_CTX");
399 
400 	/* open ports */
401 	/* keep reporting that we are busy starting */
402 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2200);
403 	verbose(VERB_QUERY, "winservice - open ports");
404 	if(!daemon_open_shared_ports(daemon)) return 0;
405 	verbose(VERB_QUERY, "winservice - ports opened");
406 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2000);
407 
408 	*d = daemon;
409 	*c = cfg;
410 	return 1;
411 }
412 
413 /**
414  * Deinit the service
415  */
416 static void
service_deinit(struct daemon * daemon,struct config_file * cfg)417 service_deinit(struct daemon* daemon, struct config_file* cfg)
418 {
419 	daemon_cleanup(daemon);
420 	config_delete(cfg);
421 	daemon_delete(daemon);
422 }
423 
424 #ifdef DOXYGEN
425 #define ATTR_UNUSED(x) x
426 #endif
427 /**
428  * The main function for the service.
429  * Called by the services API when starting unbound on windows in background.
430  * Arguments could have been present in the string 'path'.
431  * @param argc: nr args
432  * @param argv: arg text.
433  */
434 static void
service_main(DWORD ATTR_UNUSED (argc),LPTSTR * ATTR_UNUSED (argv))435 service_main(DWORD ATTR_UNUSED(argc), LPTSTR* ATTR_UNUSED(argv))
436 {
437 	struct config_file* cfg = NULL;
438 	struct daemon* daemon = NULL;
439 
440 	service_status_handle = RegisterServiceCtrlHandler(SERVICE_NAME,
441 		(LPHANDLER_FUNCTION)hdlr);
442 	if(!service_status_handle) {
443 		reportev("Could not RegisterServiceCtrlHandler");
444 		return;
445 	}
446 
447 	service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
448 	service_status.dwServiceSpecificExitCode = 0;
449 
450 	/* see if we have root anchor update enabled */
451 	call_root_update();
452 
453 	/* we are now starting up */
454 	report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
455 	if(!service_init(0, &daemon, &cfg)) {
456 		reportev("Could not service_init");
457 		report_status(SERVICE_STOPPED, NO_ERROR, 0);
458 		return;
459 	}
460 
461 	/* event that gets signalled when we want to quit; it
462 	 * should get registered in the worker-0 waiting loop. */
463 	service_stop_event = WSACreateEvent();
464 	if(service_stop_event == WSA_INVALID_EVENT) {
465 		log_err("WSACreateEvent: %s", wsa_strerror(WSAGetLastError()));
466 		reportev("Could not WSACreateEvent");
467 		report_status(SERVICE_STOPPED, NO_ERROR, 0);
468 		return;
469 	}
470 	if(!WSAResetEvent(service_stop_event)) {
471 		log_err("WSAResetEvent: %s", wsa_strerror(WSAGetLastError()));
472 	}
473 
474 	/* SetServiceStatus SERVICE_RUNNING;*/
475 	report_status(SERVICE_RUNNING, NO_ERROR, 0);
476 	verbose(VERB_QUERY, "winservice - init complete");
477 
478 	/* daemon performs work */
479 	while(!service_stop_shutdown) {
480 		daemon_fork(daemon);
481 		if(!service_stop_shutdown) {
482 			daemon_cleanup(daemon);
483 			config_delete(cfg); cfg=NULL;
484 			if(!service_init(1, &daemon, &cfg)) {
485 				reportev("Could not service_init");
486 				report_status(SERVICE_STOPPED, NO_ERROR, 0);
487 				return;
488 			}
489 		}
490 	}
491 
492 	/* exit */
493 	verbose(VERB_ALGO, "winservice - cleanup.");
494 	report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
495 	if(service_stop_event) (void)WSACloseEvent(service_stop_event);
496 	service_deinit(daemon, cfg);
497 	free(service_cfgfile);
498 	verbose(VERB_QUERY, "winservice - full stop");
499 	report_status(SERVICE_STOPPED, NO_ERROR, 0);
500 }
501 
502 /** start the service */
503 static void
service_start(const char * cfgfile,int v,int c)504 service_start(const char* cfgfile, int v, int c)
505 {
506 	SERVICE_TABLE_ENTRY myservices[2] = {
507 		{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main},
508 		{NULL, NULL} };
509 	verbosity=v;
510 	if(verbosity >= VERB_QUERY) {
511 		/* log to file about start sequence */
512 		fclose(fopen("C:\\unbound.log", "w"));
513 		log_init("C:\\unbound.log", 0, 0);
514 		verbose(VERB_QUERY, "open logfile");
515 	} else log_init(0, 1, 0); /* otherwise, use Application log */
516 	if(c) {
517 		service_cfgfile = strdup(cfgfile);
518 		if(!service_cfgfile) fatal_exit("out of memory");
519 	} else 	service_cfgfile = NULL;
520 	service_cmdline_verbose = v;
521 	/* this call returns when service has stopped. */
522 	if(!StartServiceCtrlDispatcher(myservices)) {
523 		reportev("Could not StartServiceCtrlDispatcher");
524 	}
525 }
526 
527 void
wsvc_command_option(const char * wopt,const char * cfgfile,int v,int c)528 wsvc_command_option(const char* wopt, const char* cfgfile, int v, int c)
529 {
530 	if(strcmp(wopt, "install") == 0)
531 		wsvc_install(stdout, NULL);
532 	else if(strcmp(wopt, "remove") == 0)
533 		wsvc_remove(stdout);
534 	else if(strcmp(wopt, "service") == 0)
535 		service_start(cfgfile, v, c);
536 	else if(strcmp(wopt, "start") == 0)
537 		wsvc_rc_start(stdout);
538 	else if(strcmp(wopt, "stop") == 0)
539 		wsvc_rc_stop(stdout);
540 	else fatal_exit("unknown option: %s", wopt);
541 	exit(0);
542 }
543 
544 void
worker_win_stop_cb(int ATTR_UNUSED (fd),short ATTR_UNUSED (ev),void * arg)545 worker_win_stop_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void* arg)
546 {
547         struct worker* worker = (struct worker*)arg;
548         verbose(VERB_QUERY, "caught stop signal (wsaevent)");
549         worker->need_to_exit = 1;
550         comm_base_exit(worker->base);
551 }
552 
553 /** wait for cron process to finish */
554 static void
waitforit(PROCESS_INFORMATION * pinfo)555 waitforit(PROCESS_INFORMATION* pinfo)
556 {
557 	DWORD ret = WaitForSingleObject(pinfo->hProcess, INFINITE);
558 	verbose(VERB_ALGO, "cronaction done");
559 	if(ret != WAIT_OBJECT_0) {
560 		return; /* did not end successfully */
561 	}
562 	if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
563 		log_err("GetExitCodeProcess failed");
564 		return;
565 	}
566 	verbose(VERB_ALGO, "exit code is %d", (int)ret);
567 	if(ret != 1) {
568 		if(!WSASetEvent(service_stop_event))
569 			log_err("Could not WSASetEvent: %s",
570 			wsa_strerror(WSAGetLastError()));
571 	}
572 }
573 
574 /** Do the cron action and wait for result exit value */
575 static void*
win_do_cron(void * ATTR_UNUSED (arg))576 win_do_cron(void* ATTR_UNUSED(arg))
577 {
578 	int mynum=65;
579 	char* cronaction;
580 	log_thread_set(&mynum);
581  	cronaction = lookup_reg_str("Software\\Unbound", "CronAction");
582 	if(cronaction && strlen(cronaction)>0) {
583 		STARTUPINFO sinfo;
584 		PROCESS_INFORMATION pinfo;
585 		memset(&pinfo, 0, sizeof(pinfo));
586 		memset(&sinfo, 0, sizeof(sinfo));
587 		sinfo.cb = sizeof(sinfo);
588 		verbose(VERB_ALGO, "cronaction: %s", cronaction);
589 		if(!CreateProcess(NULL, cronaction, NULL, NULL, 0,
590 			CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
591 			log_err("CreateProcess error");
592 		else {
593 			waitforit(&pinfo);
594 			CloseHandle(pinfo.hProcess);
595 			CloseHandle(pinfo.hThread);
596 		}
597 	}
598 	free(cronaction);
599 	/* stop self */
600 	CloseHandle(cron_thread);
601 	cron_thread = NULL;
602 	return NULL;
603 }
604 
605 /** Set the timer for cron for the next wake up */
606 static void
set_cron_timer(void)607 set_cron_timer(void)
608 {
609 	struct timeval tv;
610 	int crontime;
611 	if(cron_was_quick == 0) {
612 		cron_was_quick = 1;
613 		crontime = 3600; /* first update some time after boot */
614 	} else {
615 		crontime = lookup_reg_int("Software\\Unbound", "CronTime");
616 		if(crontime == 0) crontime = 60*60*24; /* 24 hours */
617 	}
618 	memset(&tv, 0, sizeof(tv));
619 	tv.tv_sec = (time_t)crontime;
620 	comm_timer_set(service_cron, &tv);
621 }
622 
623 void
wsvc_cron_cb(void * arg)624 wsvc_cron_cb(void* arg)
625 {
626 	struct worker* worker = (struct worker*)arg;
627 	/* perform cronned operation */
628 	verbose(VERB_ALGO, "cron timer callback");
629 	if(cron_thread == NULL) {
630 		/* create new thread to do it */
631 		ub_thread_create(&cron_thread, win_do_cron, worker);
632 	}
633 	/* reschedule */
634 	set_cron_timer();
635 }
636 
wsvc_setup_worker(struct worker * worker)637 void wsvc_setup_worker(struct worker* worker)
638 {
639 	/* if not started with -w service, do nothing */
640 	if(!service_stop_event)
641 		return;
642 	if(!(service_stop_ev = ub_winsock_register_wsaevent(
643 		comm_base_internal(worker->base), service_stop_event,
644 		&worker_win_stop_cb, worker))) {
645 		fatal_exit("could not register wsaevent");
646 		return;
647 	}
648 	if(!service_cron) {
649 		service_cron = comm_timer_create(worker->base,
650 			wsvc_cron_cb, worker);
651 		if(!service_cron)
652 			fatal_exit("could not create cron timer");
653 		set_cron_timer();
654 	}
655 }
656 
wsvc_desetup_worker(struct worker * ATTR_UNUSED (worker))657 void wsvc_desetup_worker(struct worker* ATTR_UNUSED(worker))
658 {
659 	comm_timer_delete(service_cron);
660 	service_cron = NULL;
661 }
662