mirror of
https://github.com/NLnetLabs/unbound.git
synced 2026-01-16 03:32:54 -05:00
361 lines
11 KiB
C
361 lines
11 KiB
C
/*
|
|
* 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 REGENTS 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 "daemon/daemon.h"
|
|
#include "util/config_file.h"
|
|
|
|
/** service name for unbound (internal to ServiceManager) */
|
|
#define SERVICE_NAME "unbound"
|
|
|
|
/** from gen_msg.h - success message record for windows message log */
|
|
#define MSG_GENERIC_SUCCESS ((WORD)0x00010001L)
|
|
/** from gen_msg.h - informational message record for windows message log */
|
|
#define MSG_GENERIC_INFO ((WORD)0x40010002L)
|
|
/** from gen_msg.h - warning message record for windows message log */
|
|
#define MSG_GENERIC_WARN ((WORD)0x80010003L)
|
|
/** from gen_msg.h - error message record for windows message log */
|
|
#define MSG_GENERIC_ERR ((WORD)0xC0010004L)
|
|
|
|
/** global service status */
|
|
SERVICE_STATUS service_status;
|
|
/** global service status handle */
|
|
SERVICE_STATUS_HANDLE service_status_handle;
|
|
/** global service stop event */
|
|
HANDLE service_stop_event = NULL;
|
|
|
|
/** exit with windows error */
|
|
static void
|
|
fatal_win(const char* str)
|
|
{
|
|
fatal_exit("%s (%d)", str, (int)GetLastError());
|
|
}
|
|
|
|
/** put quotes around string. Needs one space in front
|
|
* @param str: to be quoted.
|
|
* @param maxlen: max length of the string buffer.
|
|
*/
|
|
static void
|
|
quote_it(char* str, size_t maxlen)
|
|
{
|
|
if(strlen(str) == maxlen)
|
|
fatal_exit("string too long %s", str);
|
|
str[0]='"';
|
|
str[strlen(str)+1]=0;
|
|
str[strlen(str)]='"';
|
|
}
|
|
|
|
/** Install service in servicecontrolmanager */
|
|
static void
|
|
wsvc_install(void)
|
|
{
|
|
SC_HANDLE scm;
|
|
SC_HANDLE sv;
|
|
TCHAR path[MAX_PATH+2+256];
|
|
printf("installing unbound service\n");
|
|
if(!GetModuleFileName(NULL, path+1, MAX_PATH))
|
|
fatal_win("could not GetModuleFileName");
|
|
/* have to quote it because of spaces in directory names */
|
|
/* could append arguments to be sent to ServiceMain */
|
|
quote_it(path, sizeof(path));
|
|
strcat(path, " -w service");
|
|
scm = OpenSCManager(NULL, NULL, (int)SC_MANAGER_CREATE_SERVICE);
|
|
if(!scm) fatal_win("could not OpenSCManager");
|
|
sv = CreateService(
|
|
scm,
|
|
SERVICE_NAME, /* name of service */
|
|
"Unbound DNS validator", /* display name */
|
|
SERVICE_ALL_ACCESS, /* desired access */
|
|
SERVICE_WIN32_OWN_PROCESS, /* service type */
|
|
SERVICE_AUTO_START, /* start type */
|
|
SERVICE_ERROR_NORMAL, /* error control type */
|
|
path, /* path to service's binary */
|
|
NULL, /* no load ordering group */
|
|
NULL, /* no tag identifier */
|
|
NULL, /* no deps */
|
|
NULL, /* on LocalSystem */
|
|
NULL /* no password */
|
|
);
|
|
if(!sv) {
|
|
CloseServiceHandle(scm);
|
|
fatal_win("could not CreateService");
|
|
}
|
|
CloseServiceHandle(sv);
|
|
CloseServiceHandle(scm);
|
|
printf("unbound service installed\n");
|
|
}
|
|
|
|
/** Remove installed service from servicecontrolmanager */
|
|
static void
|
|
wsvc_remove(void)
|
|
{
|
|
SC_HANDLE scm;
|
|
SC_HANDLE sv;
|
|
printf("removing unbound service\n");
|
|
scm = OpenSCManager(NULL, NULL, (int)SC_MANAGER_ALL_ACCESS);
|
|
if(!scm) fatal_win("could not OpenSCManager");
|
|
sv = OpenService(scm, SERVICE_NAME, DELETE);
|
|
if(!sv) {
|
|
CloseServiceHandle(scm);
|
|
fatal_win("could not OpenService");
|
|
}
|
|
if(!DeleteService(sv)) {
|
|
fatal_win("could not DeleteService");
|
|
}
|
|
CloseServiceHandle(sv);
|
|
CloseServiceHandle(scm);
|
|
printf("unbound service removed\n");
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
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);
|
|
/* send signal to stop */
|
|
SetEvent(service_stop_event);
|
|
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];
|
|
HANDLE* s;
|
|
LPCTSTR msg = b;
|
|
/* print quickly to keep GetLastError value */
|
|
snprintf(b, sizeof(b), "%s: %s (%d)", SERVICE_NAME,
|
|
str, (int)GetLastError());
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Init service. Keeps calling status pending to tell service control
|
|
* manager that this process is not hanging.
|
|
* @param d: daemon returned here.
|
|
* @param c: config file returned here.
|
|
* @return false if failed.
|
|
*/
|
|
static int
|
|
service_init(struct daemon** d, struct config_file** c)
|
|
{
|
|
struct config_file* cfg = NULL;
|
|
struct daemon* daemon = NULL;
|
|
const char* cfgfile = CONFIGFILE;
|
|
|
|
/* create daemon */
|
|
daemon = daemon_init();
|
|
if(!daemon) return 0;
|
|
report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
|
|
|
|
/* read config */
|
|
cfg = config_create();
|
|
if(!cfg) return 0;
|
|
if(!config_read(cfg, cfgfile, daemon->chroot)) {
|
|
if(errno != ENOENT) {
|
|
/* could not read config file */
|
|
return 0;
|
|
}
|
|
/* could not open config file, using defaults */
|
|
}
|
|
report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
|
|
|
|
/* apply settings and init */
|
|
verbosity = cfg->verbosity;
|
|
if(cfg->directory && cfg->directory[0]) {
|
|
if(chdir(cfg->directory)) return 0;
|
|
verbose(VERB_QUERY, "chdir to %s", cfg->directory);
|
|
}
|
|
log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir);
|
|
report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
|
|
daemon_apply_cfg(daemon, cfg);
|
|
|
|
/* open ports */
|
|
/* keep reporting that we are busy starting */
|
|
report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
|
|
if(!daemon_open_shared_ports(daemon)) return 0;
|
|
report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
|
|
|
|
*d = daemon;
|
|
*c = cfg;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/* we are now starting up */
|
|
report_status(SERVICE_START_PENDING, NO_ERROR, 3000);
|
|
if(!service_init(&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 = CreateEvent(NULL, 1, 0, NULL);
|
|
if(!service_stop_event) {
|
|
reportev("Could not CreateEvent");
|
|
report_status(SERVICE_STOPPED, NO_ERROR, 0);
|
|
return;
|
|
}
|
|
|
|
/* SetServiceStatus SERVICE_RUNNING;*/
|
|
report_status(SERVICE_RUNNING, NO_ERROR, 0);
|
|
|
|
/* daemon_fork(daemon) , but wait on stop event ! */
|
|
/* instead of this while loop */
|
|
|
|
/* wait until exit */
|
|
while(1) {
|
|
WaitForSingleObject(service_stop_event, INFINITE);
|
|
report_status(SERVICE_STOPPED, NO_ERROR, 0);
|
|
FILE* out = fopen("C:\\output.txt", "a");
|
|
fprintf(out, "stopped!\n");
|
|
fclose(out);
|
|
return;
|
|
}
|
|
|
|
verbose(VERB_ALGO, "cleanup.");
|
|
daemon_cleanup(daemon);
|
|
config_delete(cfg);
|
|
daemon_delete(daemon);
|
|
report_status(SERVICE_STOPPED, NO_ERROR, 0);
|
|
}
|
|
|
|
/** start the service */
|
|
static void
|
|
service_start()
|
|
{
|
|
SERVICE_TABLE_ENTRY myservices[2] = {
|
|
{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main},
|
|
{NULL, NULL} };
|
|
/* this call returns when service has stopped. */
|
|
if(!StartServiceCtrlDispatcher(myservices)) {
|
|
reportev("Could not StartServiceCtrlDispatcher");
|
|
}
|
|
}
|
|
|
|
void
|
|
wsvc_command_option(const char* str)
|
|
{
|
|
if(strcmp(str, "install") == 0)
|
|
wsvc_install();
|
|
else if(strcmp(str, "remove") == 0)
|
|
wsvc_remove();
|
|
else if(strcmp(str, "service") == 0)
|
|
service_start();
|
|
else fatal_exit("unknown option: %s", str);
|
|
exit(0);
|
|
}
|