#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"
static SERVICE_STATUS service_status;
static SERVICE_STATUS_HANDLE service_status_handle;
static WSAEVENT service_stop_event = NULL;
static struct event service_stop_ev;
static int service_stop_shutdown = 0;
static char* service_cfgfile = CONFIGFILE;
static int service_cmdline_verbose = 0;
static struct comm_timer* service_cron = NULL;
static ub_thread_t cron_thread = NULL;
static int cron_was_quick = 0;
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);
}
static void
hdlr(DWORD ctrl)
{
if(ctrl == SERVICE_CONTROL_STOP) {
report_status(SERVICE_STOP_PENDING, NO_ERROR, 0);
service_stop_shutdown = 1;
if(!WSASetEvent(service_stop_event))
log_err("Could not WSASetEvent: %s",
wsa_strerror(WSAGetLastError()));
return;
} else {
report_status(service_status.dwCurrentState, NO_ERROR, 0);
}
}
static void
reportev(const char* str)
{
char b[256];
char e[256];
HANDLE* s;
LPCTSTR msg = b;
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,
EVENTLOG_ERROR_TYPE,
0,
MSG_GENERIC_ERR,
NULL,
1,
0,
&msg,
NULL);
DeregisterEventSource(s);
}
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;
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;
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;
result = strdup((char*)buf);
if(!result) reportev("out of memory");
}
return result;
}
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;
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;
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;
result = atoi((char*)buf);
} else if(type == REG_DWORD) {
DWORD r;
memmove(&r, buf, sizeof(r));
result = r;
}
return result;
}
static void
waitforubanchor(PROCESS_INFORMATION* pinfo)
{
DWORD count = 7900;
DWORD ret = WAIT_TIMEOUT;
while(ret == WAIT_TIMEOUT) {
ret = WaitForSingleObject(pinfo->hProcess, 100);
if(count > 4000) count -= 100;
else count--;
if(count > 3000)
report_status(SERVICE_START_PENDING, NO_ERROR, count);
}
verbose(VERB_ALGO, "unbound-anchor done");
if(ret != WAIT_OBJECT_0) {
return;
}
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.");
}
}
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);
}
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");
}
if(r) daemon = *d;
else daemon = daemon_init();
if(!daemon) return 0;
if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2800);
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");
verbosity = cfg->verbosity + service_cmdline_verbose;
if(cfg->directory && cfg->directory[0]) {
if(chdir(cfg->directory)) {
log_err("could not chdir to %s: %s",
cfg->directory, strerror(errno));
if(errno != ENOENT)
return 0;
log_warn("could not change directory - continuing");
} else
verbose(VERB_QUERY, "chdir to %s", cfg->directory);
}
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;
}
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;
}
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
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;
call_root_update();
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;
}
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()));
}
report_status(SERVICE_RUNNING, NO_ERROR, 0);
verbose(VERB_QUERY, "winservice - init complete");
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;
}
}
}
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);
}
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) {
fclose(fopen("C:\\unbound.log", "w"));
log_init("C:\\unbound.log", 0, 0);
verbose(VERB_QUERY, "open logfile");
} else log_init(0, 1, 0);
if(c) {
service_cfgfile = strdup(cfgfile);
if(!service_cfgfile) fatal_exit("out of memory");
} else service_cfgfile = NULL;
service_cmdline_verbose = v;
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);
}
static void
waitforit(PROCESS_INFORMATION* pinfo)
{
DWORD ret = WaitForSingleObject(pinfo->hProcess, INFINITE);
verbose(VERB_ALGO, "cronaction done");
if(ret != WAIT_OBJECT_0) {
return;
}
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()));
}
}
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);
CloseHandle(cron_thread);
cron_thread = NULL;
return NULL;
}
static void
set_cron_timer()
{
struct timeval tv;
int crontime;
if(cron_was_quick == 0) {
cron_was_quick = 1;
crontime = 3600;
} else {
crontime = lookup_reg_int("Software\\Unbound", "CronTime");
if(crontime == 0) crontime = 60*60*24;
}
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;
verbose(VERB_ALGO, "cron timer callback");
if(cron_thread == NULL) {
ub_thread_create(&cron_thread, win_do_cron, worker);
}
set_cron_timer();
}
void wsvc_setup_worker(struct worker* worker)
{
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;
}