/*
* riggerd/riggerd.c - implementation of dnssec-trigger daemon
*
* Copyright (c) 2011, 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
* Implementation of dnssec-trigger daemon.
*/
#include "config.h"
#include "log.h"
#include "cfg.h"
#include "svr.h"
#include "reshook.h"
#include "netevent.h"
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#include <signal.h>
#include <fcntl.h>
#ifdef HAVE_OPENSSL_ENGINE_H
#include <openssl/engine.h>
#endif
#ifdef HAVE_OPENSSL_CONF_H
#include <openssl/conf.h>
#endif
#ifdef USE_WINSOCK
#include "winrc/netlist.h"
#include "winrc/win_svc.h"
#endif
#ifdef HOOKS_OSX
#include "osx/wakelist.h"
#endif
/** print usage text */
static void
usage(void)
{
printf("usage: dnssec-triggerd [options]\n");
printf(" -h this help\n");
printf(" -v increase verbosity\n");
printf(" -d do not fork into the background\n");
printf(" -c file config file, default %s\n", CONFIGFILE);
printf(" -u uninstall the dns override on the system\n");
printf(" makes resolv.conf mutable again.\n");
#ifdef USE_WINSOCK
printf("-w opt windows option: \n");
printf(" install, remove, start, stop - manage the service\n");
printf(" waitstop - wait for svc to stop (-c servicename)\n");
printf(" service - used to start from services control panel\n");
#endif
}
/** variable to signal if we need to reload */
static sig_atomic_t sig_reload = 0;
/** sighandler. Since we must have one
* * @param sig: signal number.
* * @return signal handler return type (void or int).
* */
static RETSIGTYPE record_sigh(int sig)
{
switch(sig) {
#if defined(SIGCHLD) && defined(HOOKS_OSX)
case SIGCHLD:
if(1) {
int status = 0;
pid_t p;
/* waitpid for the ended process we forked at start,
* so that it does not turn into a zombie */
while( (p=waitpid(-1, &status, WNOHANG)) > 0) {
verbose(VERB_ALGO, "child proc %d exited %d",
(int)p, (int)WEXITSTATUS(status));
}
if(p == -1 && errno != ECHILD)
log_err("waitpid: %s", strerror(errno));
}
break;
#endif
#ifdef SIGHUP
case SIGHUP:
sig_reload = 1;
/* fall through and exit commbase with reload boolean set */
/* fallthrough */
#endif
/* fallthrough */
case SIGTERM:
/* fallthrough */
#ifdef SIGQUIT
/* fallthrough */
case SIGQUIT:
/* fallthrough */
#endif
#ifdef SIGBREAK
/* fallthrough */
case SIGBREAK:
/* fallthrough */
#endif
/* fallthrough */
case SIGINT:
if(global_svr)
comm_base_exit(global_svr->base);
else fatal_exit("killed by signal %d", (int)sig);
break;
#ifdef SIGPIPE
case SIGPIPE:
break;
#endif
default:
log_err("ignoring signal %d", sig);
}
}
#ifdef HOOKS_OSX
static void
osx_probe_hook(void)
{
int s;
pid_t pid = fork();
switch(pid) {
default: /* main */
return;
case -1:
/* error */
log_err("cannot fork: %s", strerror(errno));
return;
case 0:
/* child */
break;
}
/* same value as in script - cause reprobe */
unlink("/tmp/dnssec-trigger-osx.tmp");
if((s=system(LIBEXEC_DIR"/dnssec-trigger-osx.sh")) == -1)
log_err("cannot exec dnssec-trigger hook osx %s",
strerror(errno));
else exit (WEXITSTATUS(s));
exit (0);
}
#endif /* HOOKS_OSX */
/** store pid in pidfile */
static void
store_pid(char* pidfile)
{
FILE* f;
if(!pidfile || pidfile[0]==0)
return;
f = fopen(pidfile, "w");
if(!f) fatal_exit("could not write pid %s: %s",
pidfile, strerror(errno));
if(fprintf(f, "%u\n", (unsigned)getpid()) < 0)
fatal_exit("could not write pid %s: %s",
pidfile, strerror(errno));
fclose(f);
}
/** delete pidfile */
static void
unlink_pid(char* pidfile)
{
int fd;
/* truncate pidfile */
fd = open(pidfile, O_WRONLY | O_TRUNC, 0644);
if(fd != -1)
close(fd);
unlink(pidfile);
}
/** detach from command line */
static void
detach(void)
{
#if defined(HAVE_DAEMON) && !defined(DEPRECATED_DAEMON)
/* use POSIX daemon(3) function */
if(daemon(1, 0) != 0)
fatal_exit("daemon failed: %s", strerror(errno));
#else /* no HAVE_DAEMON */
#ifdef HAVE_FORK
int fd;
/* Take off... */
switch (fork()) {
case 0:
break;
case -1:
fatal_exit("fork failed: %s", strerror(errno));
default:
/* exit interactive session */
exit(0);
}
/* detach */
#ifdef HAVE_SETSID
if(setsid() == -1)
fatal_exit("setsid() failed: %s", strerror(errno));
#endif
if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
if (fd > 2)
(void)close(fd);
}
#endif /* HAVE_FORK */
#endif /* HAVE_DAEMON */
}
/** do main work of daemon */
static void
do_main_work(const char* cfgfile, int nodaemonize, int verb)
{
struct cfg* cfg;
struct svr* svr;
/* start signal handlers */
if( signal(SIGTERM, record_sigh) == SIG_ERR ||
#ifdef SIGQUIT
signal(SIGQUIT, record_sigh) == SIG_ERR ||
#endif
#ifdef SIGBREAK
signal(SIGBREAK, record_sigh) == SIG_ERR ||
#endif
#ifdef SIGHUP
signal(SIGHUP, record_sigh) == SIG_ERR ||
#endif
#ifdef SIGPIPE
signal(SIGPIPE, SIG_IGN) == SIG_ERR ||
#endif
#if defined(SIGCHLD) && defined(HOOKS_OSX)
signal(SIGCHLD, record_sigh) == SIG_ERR ||
#endif
signal(SIGINT, record_sigh) == SIG_ERR
)
log_err("install sighandler: %s", strerror(errno));
/* start daemon */
cfg = cfg_create(cfgfile);
verbosity += verb;
if(!cfg) fatal_exit("could not create config");
svr = svr_create(cfg);
if(!svr) fatal_exit("could not init server");
log_init(cfg->logfile, cfg->use_syslog, cfg->chroot);
if(!nodaemonize)
detach();
store_pid(cfg->pidfile);
log_info("%s start", PACKAGE_STRING);
/* start 127.0.0.1 service (assumes not left in insecure mode),
* unbound starts in authority-direct mode by default */
/* TODO: check if already localhost and if so do not provide a small
* window of opportunity here */
hook_resolv_localhost(cfg);
#ifdef USE_WINSOCK
netlist_start(svr);
#endif
#ifdef HOOKS_OSX
osx_probe_hook();
osx_wakelistener_start(cfg);
#endif
while(1) {
svr_service(svr);
if(sig_reload) {
struct cfg* c2;
verbose(VERB_OPS, "%s reload", PACKAGE_STRING);
if(!(c2 = cfg_create(cfgfile)))
log_err("could not reload config");
else {
cfg_delete(cfg);
cfg = c2;
svr->cfg = cfg;
}
/* reopen log after HUP to facilitate log rotation */
if(!cfg->use_syslog)
log_init(cfg->logfile, 0, cfg->chroot);
sig_reload = 0;
continue;
}
break;
}
/* attempt to set 127.0.0.1 in case we weren't, for next reboot,
so that during the reboot there is no window of opportunity */
if(svr->insecure_state)
hook_resolv_localhost(cfg);
unlink_pid(cfg->pidfile);
log_info("%s stop", PACKAGE_STRING);
svr_delete(svr);
cfg_delete(cfg);
}
/** getopt global, in case header files fail to declare it. */
extern int optind;
/** getopt global, in case header files fail to declare it. */
extern char* optarg;
/**
* main program. Set options given commandline arguments.
* @param argc: number of commandline arguments.
* @param argv: array of commandline arguments.
* @return: exit status of the program.
*/
int main(int argc, char *argv[])
{
int c;
const char* cfgfile = CONFIGFILE;
int nodaemonize = 0, verb = 0, uninit_it = 0;
const char* winopt = NULL;
#ifdef USE_WINSOCK
int cmdline_cfg = 0;
int r;
WSADATA wsa_data;
r = WSAStartup(MAKEWORD(2,2), &wsa_data);
if(r != 0) {
fatal_exit("could not init winsock. WSAStartup: %s",
wsa_strerror(r));
}
#endif /* USE_WINSOCK */
log_ident_set("dnssec-triggerd");
log_init(NULL, 0, NULL);
while( (c=getopt(argc, argv, "c:dhuvw:")) != -1) {
switch(c) {
case 'c':
cfgfile = optarg;
#ifdef USE_WINSOCK
cmdline_cfg = 1;
#endif
break;
case 'u':
uninit_it = 1;
break;
case 'v':
verbosity++;
verb++;
break;
case 'w':
winopt = optarg;
break;
case 'd':
nodaemonize=1;
break;
default:
case 'h':
usage();
return 1;
}
}
argc -= optind;
argv += optind;
if(argc != 0) {
usage();
return 1;
}
ERR_load_crypto_strings();
ERR_load_SSL_strings();
OpenSSL_add_all_algorithms();
(void)SSL_library_init();
if(uninit_it) {
struct cfg* cfg = cfg_create(cfgfile);
if(!cfg) fatal_exit("could not create config");
hook_resolv_uninstall(cfg);
} else if(winopt) {
#ifdef USE_WINSOCK
wsvc_command_option(winopt, cfgfile, verb, cmdline_cfg);
#else
fatal_exit("option not supported");
#endif
} else {
do_main_work(cfgfile, nodaemonize, verb);
}
EVP_cleanup();
#ifdef HAVE_OPENSSL_ENGINE_H
ENGINE_cleanup();
#endif
#ifdef HAVE_OPENSSL_CONF_H
CONF_modules_free();
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000
CRYPTO_cleanup_all_ex_data();
ERR_remove_state(0);
ERR_free_strings();
RAND_cleanup();
#endif
#ifdef USE_WINSOCK
if(WSACleanup() != 0) {
log_err("Could not WSACleanup: %s",
wsa_strerror(WSAGetLastError()));
}
#endif
log_init(NULL, 0, NULL); /* close logfile */
return 0;
}