2 * winrc/win_svc.c - windows services API implementation for unbound
4 * Copyright (c) 2009, NLnet Labs. All rights reserved.
6 * This software is open source.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
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.
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.
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.
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).
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"
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;
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.
83 static void report_status(DWORD state
, DWORD exitcode
, DWORD wait
)
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
);
99 * Service control handler. Called by serviceControlManager when a control
100 * code is sent to the service (with ControlService).
101 * @param ctrl: control code
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()));
115 /* ctrl == SERVICE_CONTROL_INTERROGATE or whatever */
117 report_status(service_status
.dwCurrentState
, NO_ERROR
, 0);
122 * report event to system event log
123 * For use during startup and shutdown.
124 * @param str: the error
127 reportev(const char* str
)
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
);
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 */
146 NULL
); /* binary data */
147 DeregisterEventSource(s
);
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.
158 lookup_reg_str(const char* key
, const char* name
)
163 DWORD len
= (DWORD
)sizeof(buf
);
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");
173 ret
= RegQueryValueEx(hk
, (LPCTSTR
)name
, 0, &type
, buf
, &len
);
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");
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");
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.
198 lookup_reg_int(const char* key
, const char* name
)
203 DWORD len
= (DWORD
)sizeof(buf
);
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");
213 ret
= RegQueryValueEx(hk
, (LPCTSTR
)name
, 0, &type
, buf
, &len
);
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");
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
) {
228 memmove(&r
, buf
, sizeof(r
));
234 /** wait for unbound-anchor process to finish */
236 waitforubanchor(PROCESS_INFORMATION
* pinfo
)
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 */
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 */
249 report_status(SERVICE_START_PENDING
, NO_ERROR
, count
);
251 verbose(VERB_ALGO
, "unbound-anchor done");
252 if(ret
!= WAIT_OBJECT_0
) {
253 return; /* did not end successfully */
255 if(!GetExitCodeProcess(pinfo
->hProcess
, &ret
)) {
256 log_err("GetExitCodeProcess failed");
259 verbose(VERB_ALGO
, "unbound-anchor exit code is %d", (int)ret
);
261 log_info("The root trust anchor has been updated.");
267 * Perform root anchor update if so configured, by calling that process
270 call_root_update(void)
273 rootanchor
= lookup_reg_str("Software\\Unbound", "RootAnchor");
274 if(rootanchor
&& strlen(rootanchor
)>0) {
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");
286 waitforubanchor(&pinfo
);
287 CloseHandle(pinfo
.hProcess
);
288 CloseHandle(pinfo
.hThread
);
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.
303 service_init(int r
, struct daemon
** d
, struct config_file
** c
)
305 struct config_file
* cfg
= NULL
;
306 struct daemon
* daemon
= NULL
;
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");
317 else daemon
= daemon_init();
318 if(!daemon
) return 0;
319 if(!r
) report_status(SERVICE_START_PENDING
, NO_ERROR
, 2800);
322 cfg
= config_create();
324 if(!config_read(cfg
, service_cfgfile
, daemon
->chroot
)) {
325 if(errno
!= ENOENT
) {
326 log_err("error in config file");
329 log_warn("could not open config file, using defaults");
331 if(!r
) report_status(SERVICE_START_PENDING
, NO_ERROR
, 2600);
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
));
342 log_warn("could not change directory - continuing");
344 verbose(VERB_QUERY
, "chdir to %s", cfg
->directory
);
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
);
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
);
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);
376 service_deinit(struct daemon
* daemon
, struct config_file
* cfg
)
378 daemon_cleanup(daemon
);
380 daemon_delete(daemon
);
384 #define ATTR_UNUSED(x) x
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.
394 service_main(DWORD
ATTR_UNUSED(argc
), LPTSTR
* ATTR_UNUSED(argv
))
396 struct config_file
* cfg
= NULL
;
397 struct daemon
* daemon
= NULL
;
399 service_status_handle
= RegisterServiceCtrlHandler(SERVICE_NAME
,
400 (LPHANDLER_FUNCTION
)hdlr
);
401 if(!service_status_handle
) {
402 reportev("Could not RegisterServiceCtrlHandler");
406 service_status
.dwServiceType
= SERVICE_WIN32_OWN_PROCESS
;
407 service_status
.dwServiceSpecificExitCode
= 0;
409 /* see if we have root anchor update enabled */
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);
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);
429 if(!WSAResetEvent(service_stop_event
)) {
430 log_err("WSAResetEvent: %s", wsa_strerror(WSAGetLastError()));
433 /* SetServiceStatus SERVICE_RUNNING;*/
434 report_status(SERVICE_RUNNING
, NO_ERROR
, 0);
435 verbose(VERB_QUERY
, "winservice - init complete");
437 /* daemon performs work */
438 while(!service_stop_shutdown
) {
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);
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);
461 /** start the service */
463 service_start(const char* cfgfile
, int v
, int c
)
465 SERVICE_TABLE_ENTRY myservices
[2] = {
466 {SERVICE_NAME
, (LPSERVICE_MAIN_FUNCTION
)service_main
},
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 */
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");
487 wsvc_command_option(const char* wopt
, const char* cfgfile
, int v
, int c
)
489 if(strcmp(wopt
, "install") == 0)
490 wsvc_install(stdout
, NULL
);
491 else if(strcmp(wopt
, "remove") == 0)
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
);
504 worker_win_stop_cb(int ATTR_UNUSED(fd
), short ATTR_UNUSED(ev
), void* arg
)
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
);
512 /** wait for cron process to finish */
514 waitforit(PROCESS_INFORMATION
* pinfo
)
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 */
521 if(!GetExitCodeProcess(pinfo
->hProcess
, &ret
)) {
522 log_err("GetExitCodeProcess failed");
525 verbose(VERB_ALGO
, "exit code is %d", (int)ret
);
527 if(!WSASetEvent(service_stop_event
))
528 log_err("Could not WSASetEvent: %s",
529 wsa_strerror(WSAGetLastError()));
533 /** Do the cron action and wait for result exit value */
535 win_do_cron(void* ATTR_UNUSED(arg
))
539 log_thread_set(&mynum
);
540 cronaction
= lookup_reg_str("Software\\Unbound", "CronAction");
541 if(cronaction
&& strlen(cronaction
)>0) {
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");
553 CloseHandle(pinfo
.hProcess
);
554 CloseHandle(pinfo
.hThread
);
559 CloseHandle(cron_thread
);
564 /** Set the timer for cron for the next wake up */
570 if(cron_was_quick
== 0) {
572 crontime
= 3600; /* first update some time after boot */
574 crontime
= lookup_reg_int("Software\\Unbound", "CronTime");
575 if(crontime
== 0) crontime
= 60*60*24; /* 24 hours */
577 memset(&tv
, 0, sizeof(tv
));
578 tv
.tv_sec
= (time_t)crontime
;
579 comm_timer_set(service_cron
, &tv
);
583 wsvc_cron_cb(void* arg
)
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
);
596 void wsvc_setup_worker(struct worker
* worker
)
598 /* if not started with -w service, do nothing */
599 if(!service_stop_event
)
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");
608 service_cron
= comm_timer_create(worker
->base
,
609 wsvc_cron_cb
, worker
);
611 fatal_exit("could not create cron timer");
616 void wsvc_desetup_worker(struct worker
* ATTR_UNUSED(worker
))
618 comm_timer_delete(service_cron
);