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