xref: /netbsd-src/external/bsd/unbound/dist/winrc/win_svc.c (revision 92e958de60c71aa0f2452bd7074cbb006fe6546b)
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 
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 ub_event* service_stop_ev = NULL;
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 	w_config_adjust_directory(cfg);
337 	if(cfg->directory && cfg->directory[0]) {
338 		char* dir = cfg->directory;
339 		if(chdir(dir)) {
340 			log_err("could not chdir to %s: %s",
341 				dir, strerror(errno));
342 			if(errno != ENOENT)
343 				return 0;
344 			log_warn("could not change directory - continuing");
345 		} else
346 			verbose(VERB_QUERY, "chdir to %s", dir);
347 	}
348 	log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
349 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2400);
350 	verbose(VERB_QUERY, "winservice - apply cfg");
351 	daemon_apply_cfg(daemon, cfg);
352 
353 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2300);
354 	if(!(daemon->rc = daemon_remote_create(cfg))) {
355 		log_err("could not set up remote-control");
356 		daemon_delete(daemon);
357 		config_delete(cfg);
358 		return 0;
359 	}
360 
361 	/* open ports */
362 	/* keep reporting that we are busy starting */
363 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2200);
364 	verbose(VERB_QUERY, "winservice - open ports");
365 	if(!daemon_open_shared_ports(daemon)) return 0;
366 	verbose(VERB_QUERY, "winservice - ports opened");
367 	if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2000);
368 
369 	*d = daemon;
370 	*c = cfg;
371 	return 1;
372 }
373 
374 /**
375  * Deinit the service
376  */
377 static void
378 service_deinit(struct daemon* daemon, struct config_file* cfg)
379 {
380 	daemon_cleanup(daemon);
381 	config_delete(cfg);
382 	daemon_delete(daemon);
383 }
384 
385 #ifdef DOXYGEN
386 #define ATTR_UNUSED(x) x
387 #endif
388 /**
389  * The main function for the service.
390  * Called by the services API when starting unbound on windows in background.
391  * Arguments could have been present in the string 'path'.
392  * @param argc: nr args
393  * @param argv: arg text.
394  */
395 static void
396 service_main(DWORD ATTR_UNUSED(argc), LPTSTR* ATTR_UNUSED(argv))
397 {
398 	struct config_file* cfg = NULL;
399 	struct daemon* daemon = NULL;
400 
401 	service_status_handle = RegisterServiceCtrlHandler(SERVICE_NAME,
402 		(LPHANDLER_FUNCTION)hdlr);
403 	if(!service_status_handle) {
404 		reportev("Could not RegisterServiceCtrlHandler");
405 		return;
406 	}
407 
408 	service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
409 	service_status.dwServiceSpecificExitCode = 0;
410 
411 	/* see if we have root anchor update enabled */
412 	call_root_update();
413 
414 	/* we are now starting up */
415 	report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
416 	if(!service_init(0, &daemon, &cfg)) {
417 		reportev("Could not service_init");
418 		report_status(SERVICE_STOPPED, NO_ERROR, 0);
419 		return;
420 	}
421 
422 	/* event that gets signalled when we want to quit; it
423 	 * should get registered in the worker-0 waiting loop. */
424 	service_stop_event = WSACreateEvent();
425 	if(service_stop_event == WSA_INVALID_EVENT) {
426 		log_err("WSACreateEvent: %s", wsa_strerror(WSAGetLastError()));
427 		reportev("Could not WSACreateEvent");
428 		report_status(SERVICE_STOPPED, NO_ERROR, 0);
429 		return;
430 	}
431 	if(!WSAResetEvent(service_stop_event)) {
432 		log_err("WSAResetEvent: %s", wsa_strerror(WSAGetLastError()));
433 	}
434 
435 	/* SetServiceStatus SERVICE_RUNNING;*/
436 	report_status(SERVICE_RUNNING, NO_ERROR, 0);
437 	verbose(VERB_QUERY, "winservice - init complete");
438 
439 	/* daemon performs work */
440 	while(!service_stop_shutdown) {
441 		daemon_fork(daemon);
442 		if(!service_stop_shutdown) {
443 			daemon_cleanup(daemon);
444 			config_delete(cfg); cfg=NULL;
445 			if(!service_init(1, &daemon, &cfg)) {
446 				reportev("Could not service_init");
447 				report_status(SERVICE_STOPPED, NO_ERROR, 0);
448 				return;
449 			}
450 		}
451 	}
452 
453 	/* exit */
454 	verbose(VERB_ALGO, "winservice - cleanup.");
455 	report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
456 	if(service_stop_event) (void)WSACloseEvent(service_stop_event);
457 	service_deinit(daemon, cfg);
458 	free(service_cfgfile);
459 	verbose(VERB_QUERY, "winservice - full stop");
460 	report_status(SERVICE_STOPPED, NO_ERROR, 0);
461 }
462 
463 /** start the service */
464 static void
465 service_start(const char* cfgfile, int v, int c)
466 {
467 	SERVICE_TABLE_ENTRY myservices[2] = {
468 		{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main},
469 		{NULL, NULL} };
470 	verbosity=v;
471 	if(verbosity >= VERB_QUERY) {
472 		/* log to file about start sequence */
473 		fclose(fopen("C:\\unbound.log", "w"));
474 		log_init("C:\\unbound.log", 0, 0);
475 		verbose(VERB_QUERY, "open logfile");
476 	} else log_init(0, 1, 0); /* otherwise, use Application log */
477 	if(c) {
478 		service_cfgfile = strdup(cfgfile);
479 		if(!service_cfgfile) fatal_exit("out of memory");
480 	} else 	service_cfgfile = NULL;
481 	service_cmdline_verbose = v;
482 	/* this call returns when service has stopped. */
483 	if(!StartServiceCtrlDispatcher(myservices)) {
484 		reportev("Could not StartServiceCtrlDispatcher");
485 	}
486 }
487 
488 void
489 wsvc_command_option(const char* wopt, const char* cfgfile, int v, int c)
490 {
491 	if(strcmp(wopt, "install") == 0)
492 		wsvc_install(stdout, NULL);
493 	else if(strcmp(wopt, "remove") == 0)
494 		wsvc_remove(stdout);
495 	else if(strcmp(wopt, "service") == 0)
496 		service_start(cfgfile, v, c);
497 	else if(strcmp(wopt, "start") == 0)
498 		wsvc_rc_start(stdout);
499 	else if(strcmp(wopt, "stop") == 0)
500 		wsvc_rc_stop(stdout);
501 	else fatal_exit("unknown option: %s", wopt);
502 	exit(0);
503 }
504 
505 void
506 worker_win_stop_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void* arg)
507 {
508         struct worker* worker = (struct worker*)arg;
509         verbose(VERB_QUERY, "caught stop signal (wsaevent)");
510         worker->need_to_exit = 1;
511         comm_base_exit(worker->base);
512 }
513 
514 /** wait for cron process to finish */
515 static void
516 waitforit(PROCESS_INFORMATION* pinfo)
517 {
518 	DWORD ret = WaitForSingleObject(pinfo->hProcess, INFINITE);
519 	verbose(VERB_ALGO, "cronaction done");
520 	if(ret != WAIT_OBJECT_0) {
521 		return; /* did not end successfully */
522 	}
523 	if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
524 		log_err("GetExitCodeProcess failed");
525 		return;
526 	}
527 	verbose(VERB_ALGO, "exit code is %d", (int)ret);
528 	if(ret != 1) {
529 		if(!WSASetEvent(service_stop_event))
530 			log_err("Could not WSASetEvent: %s",
531 			wsa_strerror(WSAGetLastError()));
532 	}
533 }
534 
535 /** Do the cron action and wait for result exit value */
536 static void*
537 win_do_cron(void* ATTR_UNUSED(arg))
538 {
539 	int mynum=65;
540 	char* cronaction;
541 	log_thread_set(&mynum);
542  	cronaction = lookup_reg_str("Software\\Unbound", "CronAction");
543 	if(cronaction && strlen(cronaction)>0) {
544 		STARTUPINFO sinfo;
545 		PROCESS_INFORMATION pinfo;
546 		memset(&pinfo, 0, sizeof(pinfo));
547 		memset(&sinfo, 0, sizeof(sinfo));
548 		sinfo.cb = sizeof(sinfo);
549 		verbose(VERB_ALGO, "cronaction: %s", cronaction);
550 		if(!CreateProcess(NULL, cronaction, NULL, NULL, 0,
551 			CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
552 			log_err("CreateProcess error");
553 		else {
554 			waitforit(&pinfo);
555 			CloseHandle(pinfo.hProcess);
556 			CloseHandle(pinfo.hThread);
557 		}
558 	}
559 	free(cronaction);
560 	/* stop self */
561 	CloseHandle(cron_thread);
562 	cron_thread = NULL;
563 	return NULL;
564 }
565 
566 /** Set the timer for cron for the next wake up */
567 static void
568 set_cron_timer()
569 {
570 	struct timeval tv;
571 	int crontime;
572 	if(cron_was_quick == 0) {
573 		cron_was_quick = 1;
574 		crontime = 3600; /* first update some time after boot */
575 	} else {
576 		crontime = lookup_reg_int("Software\\Unbound", "CronTime");
577 		if(crontime == 0) crontime = 60*60*24; /* 24 hours */
578 	}
579 	memset(&tv, 0, sizeof(tv));
580 	tv.tv_sec = (time_t)crontime;
581 	comm_timer_set(service_cron, &tv);
582 }
583 
584 void
585 wsvc_cron_cb(void* arg)
586 {
587 	struct worker* worker = (struct worker*)arg;
588 	/* perform cronned operation */
589 	verbose(VERB_ALGO, "cron timer callback");
590 	if(cron_thread == NULL) {
591 		/* create new thread to do it */
592 		ub_thread_create(&cron_thread, win_do_cron, worker);
593 	}
594 	/* reschedule */
595 	set_cron_timer();
596 }
597 
598 void wsvc_setup_worker(struct worker* worker)
599 {
600 	/* if not started with -w service, do nothing */
601 	if(!service_stop_event)
602 		return;
603 	if(!(service_stop_ev = ub_winsock_register_wsaevent(
604 		comm_base_internal(worker->base), service_stop_event,
605 		&worker_win_stop_cb, worker))) {
606 		fatal_exit("could not register wsaevent");
607 		return;
608 	}
609 	if(!service_cron) {
610 		service_cron = comm_timer_create(worker->base,
611 			wsvc_cron_cb, worker);
612 		if(!service_cron)
613 			fatal_exit("could not create cron timer");
614 		set_cron_timer();
615 	}
616 }
617 
618 void wsvc_desetup_worker(struct worker* ATTR_UNUSED(worker))
619 {
620 	comm_timer_delete(service_cron);
621 	service_cron = NULL;
622 }
623