2014-10-05 15:44:31 -06:00
|
|
|
/*
|
|
|
|
* winrc/win_svc.c - windows services API implementation for unbound
|
|
|
|
*
|
|
|
|
* Copyright (c) 2009, NLnet Labs. All rights reserved.
|
|
|
|
*
|
|
|
|
* This software is open source.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
*
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
|
|
* and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* Neither the name of the NLNET LABS nor the names of its contributors may
|
|
|
|
* be used to endorse or promote products derived from this software without
|
|
|
|
* specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \file
|
|
|
|
*
|
|
|
|
* This file contains functions to integrate with the windows services API.
|
|
|
|
* This means it handles the commandline switches to install and remove
|
|
|
|
* the service (via CreateService and DeleteService), it handles
|
|
|
|
* the ServiceMain() main service entry point when started as a service,
|
|
|
|
* and it handles the Handler[_ex]() to process requests to the service
|
|
|
|
* (such as start and stop and status).
|
|
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "winrc/win_svc.h"
|
|
|
|
#include "winrc/w_inst.h"
|
|
|
|
#include "daemon/daemon.h"
|
|
|
|
#include "daemon/worker.h"
|
|
|
|
#include "daemon/remote.h"
|
|
|
|
#include "util/config_file.h"
|
|
|
|
#include "util/netevent.h"
|
|
|
|
#include "util/winsock_event.h"
|
|
|
|
|
|
|
|
/** global service status */
|
|
|
|
static SERVICE_STATUS service_status;
|
|
|
|
/** global service status handle */
|
|
|
|
static SERVICE_STATUS_HANDLE service_status_handle;
|
|
|
|
/** global service stop event */
|
|
|
|
static WSAEVENT service_stop_event = NULL;
|
|
|
|
/** event struct for stop callbacks */
|
|
|
|
static struct event service_stop_ev;
|
|
|
|
/** if stop even means shutdown or restart */
|
|
|
|
static int service_stop_shutdown = 0;
|
|
|
|
/** config file to open. global communication to service_main() */
|
|
|
|
static char* service_cfgfile = CONFIGFILE;
|
|
|
|
/** commandline verbosity. global communication to service_main() */
|
|
|
|
static int service_cmdline_verbose = 0;
|
|
|
|
/** the cron callback */
|
|
|
|
static struct comm_timer* service_cron = NULL;
|
|
|
|
/** the cron thread */
|
|
|
|
static ub_thread_t cron_thread = NULL;
|
|
|
|
/** if cron has already done its quick check */
|
|
|
|
static int cron_was_quick = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Report current service status to service control manager
|
|
|
|
* @param state: current state
|
|
|
|
* @param exitcode: error code (when stopped)
|
|
|
|
* @param wait: pending operation estimated time in milliseconds.
|
|
|
|
*/
|
|
|
|
static void report_status(DWORD state, DWORD exitcode, DWORD wait)
|
|
|
|
{
|
|
|
|
static DWORD checkpoint = 1;
|
|
|
|
service_status.dwCurrentState = state;
|
|
|
|
service_status.dwWin32ExitCode = exitcode;
|
|
|
|
service_status.dwWaitHint = wait;
|
|
|
|
if(state == SERVICE_START_PENDING)
|
|
|
|
service_status.dwControlsAccepted = 0;
|
|
|
|
else service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
|
|
|
if(state == SERVICE_RUNNING || state == SERVICE_STOPPED)
|
|
|
|
service_status.dwCheckPoint = 0;
|
|
|
|
else service_status.dwCheckPoint = checkpoint++;
|
|
|
|
SetServiceStatus(service_status_handle, &service_status);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Service control handler. Called by serviceControlManager when a control
|
|
|
|
* code is sent to the service (with ControlService).
|
|
|
|
* @param ctrl: control code
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
hdlr(DWORD ctrl)
|
|
|
|
{
|
|
|
|
if(ctrl == SERVICE_CONTROL_STOP) {
|
|
|
|
report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
|
|
|
|
service_stop_shutdown = 1;
|
|
|
|
/* send signal to stop */
|
|
|
|
if(!WSASetEvent(service_stop_event))
|
|
|
|
log_err("Could not WSASetEvent: %s",
|
|
|
|
wsa_strerror(WSAGetLastError()));
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
/* ctrl == SERVICE_CONTROL_INTERROGATE or whatever */
|
|
|
|
/* update status */
|
|
|
|
report_status(service_status.dwCurrentState, NO_ERROR, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* report event to system event log
|
|
|
|
* For use during startup and shutdown.
|
|
|
|
* @param str: the error
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
reportev(const char* str)
|
|
|
|
{
|
|
|
|
char b[256];
|
|
|
|
char e[256];
|
|
|
|
HANDLE* s;
|
|
|
|
LPCTSTR msg = b;
|
|
|
|
/* print quickly to keep GetLastError value */
|
|
|
|
wsvc_err2str(e, sizeof(e), str, GetLastError());
|
|
|
|
snprintf(b, sizeof(b), "%s: %s", SERVICE_NAME, e);
|
|
|
|
s = RegisterEventSource(NULL, SERVICE_NAME);
|
|
|
|
if(!s) return;
|
|
|
|
ReportEvent(s, /* event log */
|
|
|
|
EVENTLOG_ERROR_TYPE, /* event type */
|
|
|
|
0, /* event category */
|
|
|
|
MSG_GENERIC_ERR, /* event ID (from gen_msg.mc) */
|
|
|
|
NULL, /* user security context */
|
|
|
|
1, /* numstrings */
|
|
|
|
0, /* binary size */
|
|
|
|
&msg, /* strings */
|
|
|
|
NULL); /* binary data */
|
|
|
|
DeregisterEventSource(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Obtain registry string (if it exists).
|
|
|
|
* @param key: key string
|
|
|
|
* @param name: name of value to fetch.
|
|
|
|
* @return malloced string with the result or NULL if it did not
|
|
|
|
* exist on an error (logged) was encountered.
|
|
|
|
*/
|
|
|
|
static char*
|
|
|
|
lookup_reg_str(const char* key, const char* name)
|
|
|
|
{
|
|
|
|
HKEY hk = NULL;
|
|
|
|
DWORD type = 0;
|
|
|
|
BYTE buf[1024];
|
|
|
|
DWORD len = (DWORD)sizeof(buf);
|
|
|
|
LONG ret;
|
|
|
|
char* result = NULL;
|
|
|
|
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hk);
|
|
|
|
if(ret == ERROR_FILE_NOT_FOUND)
|
|
|
|
return NULL; /* key does not exist */
|
|
|
|
else if(ret != ERROR_SUCCESS) {
|
|
|
|
reportev("RegOpenKeyEx failed");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
ret = RegQueryValueEx(hk, (LPCTSTR)name, 0, &type, buf, &len);
|
|
|
|
if(RegCloseKey(hk))
|
|
|
|
reportev("RegCloseKey");
|
|
|
|
if(ret == ERROR_FILE_NOT_FOUND)
|
|
|
|
return NULL; /* name does not exist */
|
|
|
|
else if(ret != ERROR_SUCCESS) {
|
|
|
|
reportev("RegQueryValueEx failed");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
if(type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ) {
|
|
|
|
buf[sizeof(buf)-1] = 0;
|
|
|
|
buf[sizeof(buf)-2] = 0; /* for multi_sz */
|
|
|
|
result = strdup((char*)buf);
|
|
|
|
if(!result) reportev("out of memory");
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Obtain registry integer (if it exists).
|
|
|
|
* @param key: key string
|
|
|
|
* @param name: name of value to fetch.
|
|
|
|
* @return integer value (if it exists), or 0 on error.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
lookup_reg_int(const char* key, const char* name)
|
|
|
|
{
|
|
|
|
HKEY hk = NULL;
|
|
|
|
DWORD type = 0;
|
|
|
|
BYTE buf[1024];
|
|
|
|
DWORD len = (DWORD)sizeof(buf);
|
|
|
|
LONG ret;
|
|
|
|
int result = 0;
|
|
|
|
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hk);
|
|
|
|
if(ret == ERROR_FILE_NOT_FOUND)
|
|
|
|
return 0; /* key does not exist */
|
|
|
|
else if(ret != ERROR_SUCCESS) {
|
|
|
|
reportev("RegOpenKeyEx failed");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
ret = RegQueryValueEx(hk, (LPCTSTR)name, 0, &type, buf, &len);
|
|
|
|
if(RegCloseKey(hk))
|
|
|
|
reportev("RegCloseKey");
|
|
|
|
if(ret == ERROR_FILE_NOT_FOUND)
|
|
|
|
return 0; /* name does not exist */
|
|
|
|
else if(ret != ERROR_SUCCESS) {
|
|
|
|
reportev("RegQueryValueEx failed");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if(type == REG_SZ || type == REG_MULTI_SZ || type == REG_EXPAND_SZ) {
|
|
|
|
buf[sizeof(buf)-1] = 0;
|
|
|
|
buf[sizeof(buf)-2] = 0; /* for multi_sz */
|
|
|
|
result = atoi((char*)buf);
|
|
|
|
} else if(type == REG_DWORD) {
|
|
|
|
DWORD r;
|
|
|
|
memmove(&r, buf, sizeof(r));
|
|
|
|
result = r;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** wait for unbound-anchor process to finish */
|
|
|
|
static void
|
|
|
|
waitforubanchor(PROCESS_INFORMATION* pinfo)
|
|
|
|
{
|
|
|
|
/* we have 5 seconds scheduled for it, usually it will be very fast,
|
|
|
|
* with only a UDP message or two (100 msec or so), but the https
|
|
|
|
* connections could take some time */
|
|
|
|
DWORD count = 7900;
|
|
|
|
DWORD ret = WAIT_TIMEOUT;
|
|
|
|
/* decrease timer every 1/10 second, we are still starting up */
|
|
|
|
while(ret == WAIT_TIMEOUT) {
|
|
|
|
ret = WaitForSingleObject(pinfo->hProcess, 100);
|
|
|
|
if(count > 4000) count -= 100;
|
|
|
|
else count--; /* go slow, it is taking long */
|
|
|
|
if(count > 3000)
|
|
|
|
report_status(SERVICE_START_PENDING, NO_ERROR, count);
|
|
|
|
}
|
|
|
|
verbose(VERB_ALGO, "unbound-anchor done");
|
|
|
|
if(ret != WAIT_OBJECT_0) {
|
|
|
|
return; /* did not end successfully */
|
|
|
|
}
|
|
|
|
if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
|
|
|
|
log_err("GetExitCodeProcess failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
verbose(VERB_ALGO, "unbound-anchor exit code is %d", (int)ret);
|
|
|
|
if(ret != 0) {
|
|
|
|
log_info("The root trust anchor has been updated.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Perform root anchor update if so configured, by calling that process
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
call_root_update(void)
|
|
|
|
{
|
|
|
|
char* rootanchor;
|
|
|
|
rootanchor = lookup_reg_str("Software\\Unbound", "RootAnchor");
|
|
|
|
if(rootanchor && strlen(rootanchor)>0) {
|
|
|
|
STARTUPINFO sinfo;
|
|
|
|
PROCESS_INFORMATION pinfo;
|
|
|
|
memset(&pinfo, 0, sizeof(pinfo));
|
|
|
|
memset(&sinfo, 0, sizeof(sinfo));
|
|
|
|
sinfo.cb = sizeof(sinfo);
|
|
|
|
verbose(VERB_ALGO, "rootanchor: %s", rootanchor);
|
|
|
|
report_status(SERVICE_START_PENDING, NO_ERROR, 8000);
|
|
|
|
if(!CreateProcess(NULL, rootanchor, NULL, NULL, 0,
|
|
|
|
CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
|
|
|
|
log_err("CreateProcess error for unbound-anchor.exe");
|
|
|
|
else {
|
|
|
|
waitforubanchor(&pinfo);
|
|
|
|
CloseHandle(pinfo.hProcess);
|
|
|
|
CloseHandle(pinfo.hThread);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(rootanchor);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Init service. Keeps calling status pending to tell service control
|
|
|
|
* manager that this process is not hanging.
|
|
|
|
* @param r: restart, true on restart
|
|
|
|
* @param d: daemon returned here.
|
|
|
|
* @param c: config file returned here.
|
|
|
|
* @return false if failed.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
service_init(int r, struct daemon** d, struct config_file** c)
|
|
|
|
{
|
|
|
|
struct config_file* cfg = NULL;
|
|
|
|
struct daemon* daemon = NULL;
|
|
|
|
|
|
|
|
if(!service_cfgfile) {
|
|
|
|
char* newf = lookup_reg_str("Software\\Unbound", "ConfigFile");
|
|
|
|
if(newf) service_cfgfile = newf;
|
|
|
|
else service_cfgfile = strdup(CONFIGFILE);
|
|
|
|
if(!service_cfgfile) fatal_exit("out of memory");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create daemon */
|
|
|
|
if(r) daemon = *d;
|
|
|
|
else daemon = daemon_init();
|
|
|
|
if(!daemon) return 0;
|
|
|
|
if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2800);
|
|
|
|
|
|
|
|
/* read config */
|
|
|
|
cfg = config_create();
|
|
|
|
if(!cfg) return 0;
|
|
|
|
if(!config_read(cfg, service_cfgfile, daemon->chroot)) {
|
|
|
|
if(errno != ENOENT) {
|
|
|
|
log_err("error in config file");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
log_warn("could not open config file, using defaults");
|
|
|
|
}
|
|
|
|
if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2600);
|
|
|
|
|
|
|
|
verbose(VERB_QUERY, "winservice - apply settings");
|
|
|
|
/* apply settings and init */
|
|
|
|
verbosity = cfg->verbosity + service_cmdline_verbose;
|
2015-12-30 03:57:50 -07:00
|
|
|
w_config_adjust_directory(cfg);
|
2014-10-05 15:44:31 -06:00
|
|
|
if(cfg->directory && cfg->directory[0]) {
|
2015-12-30 03:57:50 -07:00
|
|
|
char* dir = cfg->directory;
|
|
|
|
if(chdir(dir)) {
|
2014-10-05 15:44:31 -06:00
|
|
|
log_err("could not chdir to %s: %s",
|
2015-12-30 03:57:50 -07:00
|
|
|
dir, strerror(errno));
|
2014-10-05 15:44:31 -06:00
|
|
|
if(errno != ENOENT)
|
|
|
|
return 0;
|
|
|
|
log_warn("could not change directory - continuing");
|
|
|
|
} else
|
2015-12-30 03:57:50 -07:00
|
|
|
verbose(VERB_QUERY, "chdir to %s", dir);
|
2014-10-05 15:44:31 -06:00
|
|
|
}
|
|
|
|
log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
|
|
|
|
if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2400);
|
|
|
|
verbose(VERB_QUERY, "winservice - apply cfg");
|
|
|
|
daemon_apply_cfg(daemon, cfg);
|
|
|
|
|
|
|
|
if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2300);
|
|
|
|
if(!(daemon->rc = daemon_remote_create(cfg))) {
|
|
|
|
log_err("could not set up remote-control");
|
|
|
|
daemon_delete(daemon);
|
|
|
|
config_delete(cfg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* open ports */
|
|
|
|
/* keep reporting that we are busy starting */
|
|
|
|
if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2200);
|
|
|
|
verbose(VERB_QUERY, "winservice - open ports");
|
|
|
|
if(!daemon_open_shared_ports(daemon)) return 0;
|
|
|
|
verbose(VERB_QUERY, "winservice - ports opened");
|
|
|
|
if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2000);
|
|
|
|
|
|
|
|
*d = daemon;
|
|
|
|
*c = cfg;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deinit the service
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
service_deinit(struct daemon* daemon, struct config_file* cfg)
|
|
|
|
{
|
|
|
|
daemon_cleanup(daemon);
|
|
|
|
config_delete(cfg);
|
|
|
|
daemon_delete(daemon);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DOXYGEN
|
|
|
|
#define ATTR_UNUSED(x) x
|
|
|
|
#endif
|
|
|
|
/**
|
|
|
|
* The main function for the service.
|
|
|
|
* Called by the services API when starting unbound on windows in background.
|
|
|
|
* Arguments could have been present in the string 'path'.
|
|
|
|
* @param argc: nr args
|
|
|
|
* @param argv: arg text.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
service_main(DWORD ATTR_UNUSED(argc), LPTSTR* ATTR_UNUSED(argv))
|
|
|
|
{
|
|
|
|
struct config_file* cfg = NULL;
|
|
|
|
struct daemon* daemon = NULL;
|
|
|
|
|
|
|
|
service_status_handle = RegisterServiceCtrlHandler(SERVICE_NAME,
|
|
|
|
(LPHANDLER_FUNCTION)hdlr);
|
|
|
|
if(!service_status_handle) {
|
|
|
|
reportev("Could not RegisterServiceCtrlHandler");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
|
|
|
service_status.dwServiceSpecificExitCode = 0;
|
|
|
|
|
|
|
|
/* see if we have root anchor update enabled */
|
|
|
|
call_root_update();
|
|
|
|
|
|
|
|
/* we are now starting up */
|
|
|
|
report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
|
|
|
|
if(!service_init(0, &daemon, &cfg)) {
|
|
|
|
reportev("Could not service_init");
|
|
|
|
report_status(SERVICE_STOPPED, NO_ERROR, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* event that gets signalled when we want to quit; it
|
|
|
|
* should get registered in the worker-0 waiting loop. */
|
|
|
|
service_stop_event = WSACreateEvent();
|
|
|
|
if(service_stop_event == WSA_INVALID_EVENT) {
|
|
|
|
log_err("WSACreateEvent: %s", wsa_strerror(WSAGetLastError()));
|
|
|
|
reportev("Could not WSACreateEvent");
|
|
|
|
report_status(SERVICE_STOPPED, NO_ERROR, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(!WSAResetEvent(service_stop_event)) {
|
|
|
|
log_err("WSAResetEvent: %s", wsa_strerror(WSAGetLastError()));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* SetServiceStatus SERVICE_RUNNING;*/
|
|
|
|
report_status(SERVICE_RUNNING, NO_ERROR, 0);
|
|
|
|
verbose(VERB_QUERY, "winservice - init complete");
|
|
|
|
|
|
|
|
/* daemon performs work */
|
|
|
|
while(!service_stop_shutdown) {
|
|
|
|
daemon_fork(daemon);
|
|
|
|
if(!service_stop_shutdown) {
|
|
|
|
daemon_cleanup(daemon);
|
|
|
|
config_delete(cfg); cfg=NULL;
|
|
|
|
if(!service_init(1, &daemon, &cfg)) {
|
|
|
|
reportev("Could not service_init");
|
|
|
|
report_status(SERVICE_STOPPED, NO_ERROR, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* exit */
|
|
|
|
verbose(VERB_ALGO, "winservice - cleanup.");
|
|
|
|
report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
|
|
|
|
service_deinit(daemon, cfg);
|
|
|
|
free(service_cfgfile);
|
|
|
|
if(service_stop_event) (void)WSACloseEvent(service_stop_event);
|
|
|
|
verbose(VERB_QUERY, "winservice - full stop");
|
|
|
|
report_status(SERVICE_STOPPED, NO_ERROR, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** start the service */
|
|
|
|
static void
|
|
|
|
service_start(const char* cfgfile, int v, int c)
|
|
|
|
{
|
|
|
|
SERVICE_TABLE_ENTRY myservices[2] = {
|
|
|
|
{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main},
|
|
|
|
{NULL, NULL} };
|
|
|
|
verbosity=v;
|
|
|
|
if(verbosity >= VERB_QUERY) {
|
|
|
|
/* log to file about start sequence */
|
|
|
|
fclose(fopen("C:\\unbound.log", "w"));
|
|
|
|
log_init("C:\\unbound.log", 0, 0);
|
|
|
|
verbose(VERB_QUERY, "open logfile");
|
|
|
|
} else log_init(0, 1, 0); /* otherwise, use Application log */
|
|
|
|
if(c) {
|
|
|
|
service_cfgfile = strdup(cfgfile);
|
|
|
|
if(!service_cfgfile) fatal_exit("out of memory");
|
|
|
|
} else service_cfgfile = NULL;
|
|
|
|
service_cmdline_verbose = v;
|
|
|
|
/* this call returns when service has stopped. */
|
|
|
|
if(!StartServiceCtrlDispatcher(myservices)) {
|
|
|
|
reportev("Could not StartServiceCtrlDispatcher");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
wsvc_command_option(const char* wopt, const char* cfgfile, int v, int c)
|
|
|
|
{
|
|
|
|
if(strcmp(wopt, "install") == 0)
|
|
|
|
wsvc_install(stdout, NULL);
|
|
|
|
else if(strcmp(wopt, "remove") == 0)
|
|
|
|
wsvc_remove(stdout);
|
|
|
|
else if(strcmp(wopt, "service") == 0)
|
|
|
|
service_start(cfgfile, v, c);
|
|
|
|
else if(strcmp(wopt, "start") == 0)
|
|
|
|
wsvc_rc_start(stdout);
|
|
|
|
else if(strcmp(wopt, "stop") == 0)
|
|
|
|
wsvc_rc_stop(stdout);
|
|
|
|
else fatal_exit("unknown option: %s", wopt);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
worker_win_stop_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void* arg)
|
|
|
|
{
|
|
|
|
struct worker* worker = (struct worker*)arg;
|
|
|
|
verbose(VERB_QUERY, "caught stop signal (wsaevent)");
|
|
|
|
worker->need_to_exit = 1;
|
|
|
|
comm_base_exit(worker->base);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** wait for cron process to finish */
|
|
|
|
static void
|
|
|
|
waitforit(PROCESS_INFORMATION* pinfo)
|
|
|
|
{
|
|
|
|
DWORD ret = WaitForSingleObject(pinfo->hProcess, INFINITE);
|
|
|
|
verbose(VERB_ALGO, "cronaction done");
|
|
|
|
if(ret != WAIT_OBJECT_0) {
|
|
|
|
return; /* did not end successfully */
|
|
|
|
}
|
|
|
|
if(!GetExitCodeProcess(pinfo->hProcess, &ret)) {
|
|
|
|
log_err("GetExitCodeProcess failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
verbose(VERB_ALGO, "exit code is %d", (int)ret);
|
|
|
|
if(ret != 1) {
|
|
|
|
if(!WSASetEvent(service_stop_event))
|
|
|
|
log_err("Could not WSASetEvent: %s",
|
|
|
|
wsa_strerror(WSAGetLastError()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Do the cron action and wait for result exit value */
|
|
|
|
static void*
|
|
|
|
win_do_cron(void* ATTR_UNUSED(arg))
|
|
|
|
{
|
|
|
|
int mynum=65;
|
|
|
|
char* cronaction;
|
|
|
|
log_thread_set(&mynum);
|
|
|
|
cronaction = lookup_reg_str("Software\\Unbound", "CronAction");
|
|
|
|
if(cronaction && strlen(cronaction)>0) {
|
|
|
|
STARTUPINFO sinfo;
|
|
|
|
PROCESS_INFORMATION pinfo;
|
|
|
|
memset(&pinfo, 0, sizeof(pinfo));
|
|
|
|
memset(&sinfo, 0, sizeof(sinfo));
|
|
|
|
sinfo.cb = sizeof(sinfo);
|
|
|
|
verbose(VERB_ALGO, "cronaction: %s", cronaction);
|
|
|
|
if(!CreateProcess(NULL, cronaction, NULL, NULL, 0,
|
|
|
|
CREATE_NO_WINDOW, NULL, NULL, &sinfo, &pinfo))
|
|
|
|
log_err("CreateProcess error");
|
|
|
|
else {
|
|
|
|
waitforit(&pinfo);
|
|
|
|
CloseHandle(pinfo.hProcess);
|
|
|
|
CloseHandle(pinfo.hThread);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(cronaction);
|
|
|
|
/* stop self */
|
|
|
|
CloseHandle(cron_thread);
|
|
|
|
cron_thread = NULL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Set the timer for cron for the next wake up */
|
|
|
|
static void
|
|
|
|
set_cron_timer()
|
|
|
|
{
|
|
|
|
struct timeval tv;
|
|
|
|
int crontime;
|
|
|
|
if(cron_was_quick == 0) {
|
|
|
|
cron_was_quick = 1;
|
|
|
|
crontime = 3600; /* first update some time after boot */
|
|
|
|
} else {
|
|
|
|
crontime = lookup_reg_int("Software\\Unbound", "CronTime");
|
|
|
|
if(crontime == 0) crontime = 60*60*24; /* 24 hours */
|
|
|
|
}
|
|
|
|
memset(&tv, 0, sizeof(tv));
|
|
|
|
tv.tv_sec = (time_t)crontime;
|
|
|
|
comm_timer_set(service_cron, &tv);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
wsvc_cron_cb(void* arg)
|
|
|
|
{
|
|
|
|
struct worker* worker = (struct worker*)arg;
|
|
|
|
/* perform cronned operation */
|
|
|
|
verbose(VERB_ALGO, "cron timer callback");
|
|
|
|
if(cron_thread == NULL) {
|
|
|
|
/* create new thread to do it */
|
|
|
|
ub_thread_create(&cron_thread, win_do_cron, worker);
|
|
|
|
}
|
|
|
|
/* reschedule */
|
|
|
|
set_cron_timer();
|
|
|
|
}
|
|
|
|
|
|
|
|
void wsvc_setup_worker(struct worker* worker)
|
|
|
|
{
|
|
|
|
/* if not started with -w service, do nothing */
|
|
|
|
if(!service_stop_event)
|
|
|
|
return;
|
|
|
|
if(!winsock_register_wsaevent(comm_base_internal(worker->base),
|
|
|
|
&service_stop_ev, service_stop_event,
|
|
|
|
&worker_win_stop_cb, worker)) {
|
|
|
|
fatal_exit("could not register wsaevent");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(!service_cron) {
|
|
|
|
service_cron = comm_timer_create(worker->base,
|
|
|
|
wsvc_cron_cb, worker);
|
|
|
|
if(!service_cron)
|
|
|
|
fatal_exit("could not create cron timer");
|
|
|
|
set_cron_timer();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void wsvc_desetup_worker(struct worker* ATTR_UNUSED(worker))
|
|
|
|
{
|
|
|
|
comm_timer_delete(service_cron);
|
|
|
|
service_cron = NULL;
|
|
|
|
}
|