/*
* remote.c - remote control for the NSD daemon.
*
* Copyright (c) 2008, 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 the remote control functionality for the daemon.
* The remote control can be performed using either the commandline
* nsd-control tool, or a TLS capable web browser.
* The channel is secured using TLSv1, and certificates.
* Both the server and the client(control tool) have their own keys.
*/
#include "config.h"
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif
#ifdef HAVE_SYS_UN_H
# include <sys/un.h>
#endif
#ifndef AF_LOCAL
#define AF_LOCAL AF_UNIX
#endif
/** number of seconds timeout on incoming remote control handshake */
#define REMOTE_CONTROL_TCP_TIMEOUT 120
/** repattern to master or slave */
#define REPAT_SLAVE 1
#define REPAT_MASTER 2
/** if you want zero to be inhibited in stats output.
* it omits zeroes for types that have no acronym and unused-rcodes */
const int inhibit_zero = 1;
/**
* a busy control command connection, SSL state
* Defined here to keep the definition private, and keep SSL out of the .h
*/
struct rc_state {
/** the next item in list */
struct rc_state* next, *prev;
/* if the event was added to the event_base */
int event_added;
/** the commpoint */
struct event c;
/** timeout for this state */
struct timeval tval;
/** in the handshake part */
enum { rc_none, rc_hs_read, rc_hs_write } shake_state;
#ifdef HAVE_SSL
/** the ssl state */
SSL* ssl;
#endif
/** file descriptor */
int fd;
/** the rc this is part of */
struct daemon_remote* rc;
/** stats list next item */
struct rc_state* stats_next;
};
/**
* list of events for accepting connections
*/
struct acceptlist {
struct acceptlist* next;
int event_added;
struct event c;
char* ident;
struct daemon_remote* rc;
};
/**
* The remote control state.
*/
struct daemon_remote {
/** the master process for this remote control */
struct xfrd_state* xfrd;
/** commpoints for accepting remote control connections */
struct acceptlist* accept_list;
/* if certificates are used */
int use_cert;
/** number of active commpoints that are handling remote control */
int active;
/** max active commpoints */
int max_active;
/** current commpoints busy; double linked, malloced */
struct rc_state* busy_list;
/** last time stats was reported */
struct timeval stats_time, boot_time;
#ifdef HAVE_SSL
/** the SSL context for creating new SSL streams */
SSL_CTX* ctx;
#endif
};
/**
* Connection to print to, either SSL or plain over fd
*/
struct remote_stream {
#ifdef HAVE_SSL
/** SSL structure, nonNULL if using SSL */
SSL* ssl;
#endif
/** file descriptor for plain transfer */
int fd;
};
typedef struct remote_stream RES;
/**
* Print fixed line of text over ssl connection in blocking mode
* @param res: print to
* @param text: the text.
* @return false on connection failure.
*/
static int ssl_print_text(RES* res, const char* text);
/**
* printf style printing to the ssl connection
* @param res: the RES connection to print to. Blocking.
* @param format: printf style format string.
* @return success or false on a network failure.
*/
static int ssl_printf(RES* res, const char* format, ...)
ATTR_FORMAT(printf, 2, 3);
/**
* Read until \n is encountered
* If stream signals EOF, the string up to then is returned (without \n).
* @param res: the RES connection to read from. blocking.
* @param buf: buffer to read to.
* @param max: size of buffer.
* @return false on connection failure.
*/
static int ssl_read_line(RES* res, char* buf, size_t max);
/** perform the accept of a new remote control connection */
static void
remote_accept_callback(int fd, short event, void* arg);
/** perform remote control */
static void
remote_control_callback(int fd, short event, void* arg);
#ifdef BIND8_STATS
/* process the statistics and output them */
static void process_stats(RES* ssl, xfrd_state_type* xfrd, int peek);
#endif
if(options_remote_is_address(cfg)) {
#ifdef HAVE_SSL
if(!remote_setup_ctx(rc, cfg)) {
daemon_remote_delete(rc);
return NULL;
}
rc->use_cert = 1;
#else
log_msg(LOG_ERR, "Could not setup remote control: NSD was compiled without SSL.");
#endif /* HAVE_SSL */
} else {
struct ip_address_option* o;
#ifdef HAVE_SSL
rc->ctx = NULL;
#endif
rc->use_cert = 0;
for(o = cfg->control_interface; o; o = o->next) {
if(o->address && o->address[0] != '/')
log_msg(LOG_WARNING, "control-interface %s is not using TLS, but plain transfer, because first control-interface in config file is a local socket (starts with a /).", o->address);
}
}
/* and try to open the ports */
if(!daemon_remote_open_ports(rc, cfg)) {
log_msg(LOG_ERR, "could not open remote control port");
daemon_remote_delete(rc);
return NULL;
}
static int
create_tcp_accept_sock(struct addrinfo* addr, int* noproto)
{
#if defined(SO_REUSEADDR) || (defined(INET6) && (defined(IPV6_V6ONLY) || defined(IPV6_USE_MIN_MTU) || defined(IPV6_MTU)))
int on = 1;
#endif
int s;
*noproto = 0;
if ((s = socket(addr->ai_family, addr->ai_socktype, 0)) == -1) {
#if defined(INET6)
if (addr->ai_family == AF_INET6 &&
errno == EAFNOSUPPORT) {
*noproto = 1;
log_msg(LOG_WARNING, "fallback to TCP4, no IPv6: not supported");
return -1;
}
#endif /* INET6 */
log_msg(LOG_ERR, "can't create a socket: %s", strerror(errno));
return -1;
}
#ifdef SO_REUSEADDR
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
log_msg(LOG_ERR, "setsockopt(..., SO_REUSEADDR, ...) failed: %s", strerror(errno));
}
#endif /* SO_REUSEADDR */
#if defined(INET6) && defined(IPV6_V6ONLY)
if (addr->ai_family == AF_INET6 &&
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
{
log_msg(LOG_ERR, "setsockopt(..., IPV6_V6ONLY, ...) failed: %s", strerror(errno));
close(s);
return -1;
}
#endif
/* set it nonblocking */
/* (StevensUNP p463), if tcp listening socket is blocking, then
it may block in accept, even if select() says readable. */
if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
log_msg(LOG_ERR, "cannot fcntl tcp: %s", strerror(errno));
}
/* Bind it... */
if (bind(s, (struct sockaddr *)addr->ai_addr, addr->ai_addrlen) != 0) {
log_msg(LOG_ERR, "can't bind tcp socket: %s", strerror(errno));
close(s);
return -1;
}
/* Listen to it... */
if (listen(s, TCP_BACKLOG_REMOTE) == -1) {
log_msg(LOG_ERR, "can't listen: %s", strerror(errno));
close(s);
return -1;
}
return s;
}
/**
* Add and open a new control port
* @param rc: rc with result list.
* @param ip: ip str
* @param nr: port nr
* @param noproto_is_err: if lack of protocol support is an error.
* @return false on failure.
*/
static int
add_open(struct daemon_remote* rc, struct nsd_options* cfg, const char* ip,
int nr, int noproto_is_err)
{
struct addrinfo hints;
struct addrinfo* res;
struct acceptlist* hl;
int noproto = 0;
int fd, r;
char port[15];
snprintf(port, sizeof(port), "%d", nr);
port[sizeof(port)-1]=0;
memset(&hints, 0, sizeof(hints));
assert(ip);
if(ip[0] == '/') {
/* This looks like a local socket */
fd = create_local_accept_sock(ip, &noproto);
/*
* Change socket ownership and permissions so users other
* than root can access it provided they are in the same
* group as the user we run as.
*/
if(fd != -1) {
#ifdef HAVE_CHOWN
if(chmod(ip, (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) == -1) {
VERBOSITY(3, (LOG_INFO, "cannot chmod control socket %s: %s", ip, strerror(errno)));
}
if (cfg->username && cfg->username[0] &&
nsd.uid != (uid_t)-1) {
if(chown(ip, nsd.uid, nsd.gid) == -1)
VERBOSITY(2, (LOG_INFO, "cannot chown %u.%u %s: %s",
(unsigned)nsd.uid, (unsigned)nsd.gid,
ip, strerror(errno)));
}
#else
(void)cfg;
#endif
}
} else {
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
/* if we had no interface ip name, "default" is what we
* would do getaddrinfo for. */
if((r = getaddrinfo(ip, port, &hints, &res)) != 0 || !res) {
log_msg(LOG_ERR, "control interface %s:%s getaddrinfo: %s %s",
ip, port, gai_strerror(r),
#ifdef EAI_SYSTEM
r==EAI_SYSTEM?(char*)strerror(errno):""
#else
""
#endif
);
return 0;
}
/* open fd */
fd = create_tcp_accept_sock(res, &noproto);
freeaddrinfo(res);
}
if(fd == -1 && noproto) {
if(!noproto_is_err)
return 1; /* return success, but do nothing */
log_msg(LOG_ERR, "cannot open control interface %s %d : "
"protocol not supported", ip, nr);
return 0;
}
if(fd == -1) {
log_msg(LOG_ERR, "cannot open control interface %s %d", ip, nr);
return 0;
}
/* setup state to service the remote control command */
n = (struct rc_state*)calloc(1, sizeof(*n));
if(!n) {
log_msg(LOG_ERR, "out of memory");
goto close_exit;
}
/* perform the first nonblocking read already, for windows,
* so it can return wouldblock. could be faster too. */
remote_control_callback(newfd, EV_READ, n);
}
/** print text over the ssl connection */
static int
ssl_print_vmsg(RES* ssl, const char* format, va_list args)
{
char msg[1024];
vsnprintf(msg, sizeof(msg), format, args);
return ssl_print_text(ssl, msg);
}
/** printf style printing to the ssl connection */
static int
ssl_printf(RES* ssl, const char* format, ...)
{
va_list args;
int ret;
va_start(args, format);
ret = ssl_print_vmsg(ssl, format, args);
va_end(args);
return ret;
}
static int
ssl_read_line(RES* res, char* buf, size_t max)
{
size_t len = 0;
if(!res)
return 0;
while(len < max) {
buf[len] = 0; /* terminate for safety and please checkers */
/* this byte is written if we read a byte from the input */
#ifdef HAVE_SSL
if(res->ssl) {
int r;
ERR_clear_error();
if((r=SSL_read(res->ssl, buf+len, 1)) <= 0) {
if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) {
buf[len] = 0;
return 1;
}
log_crypto_err("could not SSL_read");
return 0;
}
} else {
#endif /* HAVE_SSL */
while(1) {
ssize_t rr = read(res->fd, buf+len, 1);
if(rr <= 0) {
if(rr == 0) {
buf[len] = 0;
return 1;
}
if(errno == EINTR || errno == EAGAIN)
continue;
log_msg(LOG_ERR, "could not read: %s",
strerror(errno));
return 0;
}
break;
}
#ifdef HAVE_SSL
}
#endif /* HAVE_SSL */
if(buf[len] == '\n') {
/* return string without \n */
buf[len] = 0;
return 1;
}
len++;
}
buf[max-1] = 0;
log_msg(LOG_ERR, "control line too long (%d): %s", (int)max, buf);
return 0;
}
/** skip whitespace, return new pointer into string */
static char*
skipwhite(char* str)
{
/* EOS \0 is not a space */
while( isspace((unsigned char)*str) )
str++;
return str;
}
/** send the OK to the control client */
static void
send_ok(RES* ssl)
{
(void)ssl_printf(ssl, "ok\n");
}
/** get zone argument (if any) or NULL, false on error */
static int
get_zone_arg(RES* ssl, xfrd_state_type* xfrd, char* arg,
struct zone_options** zo)
{
const dname_type* dname;
if(!arg[0]) {
/* no argument present, return NULL */
*zo = NULL;
return 1;
}
dname = dname_parse(xfrd->region, arg);
if(!dname) {
(void)ssl_printf(ssl, "error cannot parse zone name '%s'\n", arg);
*zo = NULL;
return 0;
}
*zo = zone_options_find(xfrd->nsd->options, dname);
region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
if(!*zo) {
(void)ssl_printf(ssl, "error zone %s not configured\n", arg);
return 0;
}
return 1;
}
/** do the stop command */
static void
do_stop(RES* ssl, xfrd_state_type* xfrd)
{
xfrd->need_to_send_shutdown = 1;
/** do the log_reopen command, it only needs reload_now */
static void
do_log_reopen(RES* ssl, xfrd_state_type* xfrd)
{
xfrd_set_reload_now(xfrd);
send_ok(ssl);
}
/** force transfer a zone */
static void
force_transfer_zone(xfrd_zone_type* zone)
{
/* if in TCP transaction, stop it immediately. */
if(zone->tcp_conn != -1)
xfrd_tcp_release(xfrd->tcp_set, zone);
else if(zone->zone_handler.ev_fd != -1)
xfrd_udp_release(zone);
/* pretend we not longer have it and force any
* zone to be downloaded (even same serial, w AXFR) */
zone->soa_disk_acquired = 0;
zone->soa_nsd_acquired = 0;
xfrd_handle_notify_and_start_xfr(zone, NULL);
}
/** do the force transfer command */
static void
do_force_transfer(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
struct zone_options* zo;
xfrd_zone_type* zone;
if(!get_zone_arg(ssl, xfrd, arg, &zo))
return;
if(zo) {
zone = (xfrd_zone_type*)rbtree_search(xfrd->zones, (const
dname_type*)zo->node.key);
if(zone) {
force_transfer_zone(zone);
send_ok(ssl);
} else {
(void)ssl_printf(ssl, "error zone not slave\n");
}
} else {
RBTREE_FOR(zone, xfrd_zone_type*, xfrd->zones) {
force_transfer_zone(zone);
}
(void)ssl_printf(ssl, "ok, %lu zones\n", (unsigned long)xfrd->zones->count);
}
}
/** do the verbosity command */
static void
do_verbosity(RES* ssl, char* str)
{
int val = atoi(str);
if(strcmp(str, "") == 0) {
(void)ssl_printf(ssl, "verbosity %d\n", verbosity);
return;
}
if(val == 0 && strcmp(str, "0") != 0) {
(void)ssl_printf(ssl, "error in verbosity number syntax: %s\n", str);
return;
}
verbosity = val;
task_new_set_verbosity(xfrd->nsd->task[xfrd->nsd->mytask],
xfrd->last_task, val);
xfrd_set_reload_now(xfrd);
send_ok(ssl);
}
/** find second argument, modifies string */
static int
find_arg2(RES* ssl, char* arg, char** arg2)
{
char* as = strrchr(arg, ' ');
if(as) {
as[0]=0;
*arg2 = as+1;
while(isspace((unsigned char)*as) && as > arg)
as--;
as[0]=0;
return 1;
}
*arg2 = NULL;
(void)ssl_printf(ssl, "error could not find next argument "
"after %s\n", arg);
return 0;
}
/** find second and third arguments, modifies string,
* does not print error for missing arg3 so that if it does not find an
* arg3, the caller can use two arguments. */
static int
find_arg3(RES* ssl, char* arg, char** arg2, char** arg3)
{
if(find_arg2(ssl, arg, arg2)) {
char* as;
*arg3 = *arg2;
as = strrchr(arg, ' ');
if(as) {
as[0]=0;
*arg2 = as+1;
while(isspace((unsigned char)*as) && as > arg)
as--;
as[0]=0;
return 1;
}
}
*arg3 = NULL;
return 0;
}
/** do the stats command */
static void
do_stats(RES* ssl, xfrd_state_type* xfrd, int peek)
{
#ifdef BIND8_STATS
process_stats(ssl, xfrd, peek);
#else
(void)xfrd; (void)peek;
(void)ssl_printf(ssl, "error no stats enabled at compile time\n");
#endif /* BIND8_STATS */
}
/** see if we have more zonestatistics entries and it has to be incremented */
static void
zonestat_inc_ifneeded(xfrd_state_type* xfrd)
{
#ifdef USE_ZONE_STATS
if(xfrd->nsd->options->zonestatnames->count != xfrd->zonestat_safe)
task_new_zonestat_inc(xfrd->nsd->task[xfrd->nsd->mytask],
xfrd->last_task,
xfrd->nsd->options->zonestatnames->count);
#else
(void)xfrd;
#endif /* USE_ZONE_STATS */
}
/** perform the changezone command for one zone */
static int
perform_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
const dname_type* dname;
struct zone_options* zopt;
char* arg2 = NULL;
if(!find_arg2(ssl, arg, &arg2))
return 0;
/* if we add it to the xfrd now, then xfrd could download AXFR and
* store it and the NSD-reload would see it in the difffile before
* it sees the add-config task.
*/
/* thus: AXFRs and IXFRs must store the pattern name in the
* difffile, so that it can be added when the AXFR or IXFR is seen.
*/
/* check that the pattern exists */
if(!rbtree_search(xfrd->nsd->options->patterns, arg2)) {
(void)ssl_printf(ssl, "error pattern %s does not exist\n",
arg2);
return 0;
}
/* see if zone is a duplicate */
if( (zopt=zone_options_find(xfrd->nsd->options, dname)) ) {
if(zopt->part_of_config) {
(void)ssl_printf(ssl, "error zone defined in nsd.conf, "
"cannot delete it in this manner: remove it from "
"nsd.conf yourself and repattern\n");
region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
dname = NULL;
return 0;
}
/* found the zone, now delete it */
/* create deletion task */
/* this deletion task is processed before the addition task,
* that is created below, in the same reload process, causing
* a seamless change from one to the other, with no downtime
* for the zone. */
task_new_del_zone(xfrd->nsd->task[xfrd->nsd->mytask],
xfrd->last_task, dname);
xfrd_set_reload_now(xfrd);
/* delete it in xfrd */
if(zone_is_slave(zopt)) {
xfrd_del_slave_zone(xfrd, dname);
}
xfrd_del_notify(xfrd, dname);
/* delete from config */
zone_list_del(xfrd->nsd->options, zopt);
} else {
(void)ssl_printf(ssl, "zone %s did not exist, creating", arg);
}
region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
dname = NULL;
/* add to zonelist and adds to config in memory */
zopt = zone_list_add(xfrd->nsd->options, arg, arg2);
if(!zopt) {
/* also dname parse error here */
(void)ssl_printf(ssl, "error could not add zonelist entry\n");
return 0;
}
/* make addzone task and schedule reload */
task_new_add_zone(xfrd->nsd->task[xfrd->nsd->mytask],
xfrd->last_task, arg, arg2,
getzonestatid(xfrd->nsd->options, zopt));
zonestat_inc_ifneeded(xfrd);
xfrd_set_reload_now(xfrd);
/* add to xfrd - notify (for master and slaves) */
init_notify_send(xfrd->notify_zones, xfrd->region, zopt);
/* add to xfrd - slave */
if(zone_is_slave(zopt)) {
xfrd_init_slave_zone(xfrd, zopt);
}
return 1;
}
/** perform the addzone command for one zone */
static int
perform_addzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
const dname_type* dname;
struct zone_options* zopt;
char* arg2 = NULL;
if(!find_arg2(ssl, arg, &arg2))
return 0;
/* if we add it to the xfrd now, then xfrd could download AXFR and
* store it and the NSD-reload would see it in the difffile before
* it sees the add-config task.
*/
/* thus: AXFRs and IXFRs must store the pattern name in the
* difffile, so that it can be added when the AXFR or IXFR is seen.
*/
/* check that the pattern exists */
if(!rbtree_search(xfrd->nsd->options->patterns, arg2)) {
(void)ssl_printf(ssl, "error pattern %s does not exist\n",
arg2);
return 0;
}
/* see if zone is a duplicate */
if( zone_options_find(xfrd->nsd->options, dname) ) {
region_recycle(xfrd->region, (void*)dname,
dname_total_size(dname));
(void)ssl_printf(ssl, "zone %s already exists\n", arg);
return 1;
}
region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
dname = NULL;
/* add to zonelist and adds to config in memory */
zopt = zone_list_add(xfrd->nsd->options, arg, arg2);
if(!zopt) {
/* also dname parse error here */
(void)ssl_printf(ssl, "error could not add zonelist entry\n");
return 0;
}
/* make addzone task and schedule reload */
task_new_add_zone(xfrd->nsd->task[xfrd->nsd->mytask],
xfrd->last_task, arg, arg2,
getzonestatid(xfrd->nsd->options, zopt));
zonestat_inc_ifneeded(xfrd);
xfrd_set_reload_now(xfrd);
/* add to xfrd - notify (for master and slaves) */
init_notify_send(xfrd->notify_zones, xfrd->region, zopt);
/* add to xfrd - slave */
if(zone_is_slave(zopt)) {
xfrd_init_slave_zone(xfrd, zopt);
}
return 1;
}
/** perform the delzone command for one zone */
static int
perform_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
const dname_type* dname;
struct zone_options* zopt;
/* see if we have the zone in question */
zopt = zone_options_find(xfrd->nsd->options, dname);
if(!zopt) {
region_recycle(xfrd->region, (void*)dname,
dname_total_size(dname));
/* nothing to do */
(void)ssl_printf(ssl, "warning zone %s not present\n", arg);
return 0;
}
/* see if it can be deleted */
if(zopt->part_of_config) {
region_recycle(xfrd->region, (void*)dname,
dname_total_size(dname));
(void)ssl_printf(ssl, "error zone defined in nsd.conf, "
"cannot delete it in this manner: remove it from "
"nsd.conf yourself and repattern\n");
return 0;
}
/* create deletion task */
task_new_del_zone(xfrd->nsd->task[xfrd->nsd->mytask],
xfrd->last_task, dname);
xfrd_set_reload_now(xfrd);
/* delete it in xfrd */
if(zone_is_slave(zopt)) {
xfrd_del_slave_zone(xfrd, dname);
}
xfrd_del_notify(xfrd, dname);
/* delete from config */
zone_list_del(xfrd->nsd->options, zopt);
/** do the addzones command */
static void
do_addzones(RES* ssl, xfrd_state_type* xfrd)
{
char buf[2048];
int num = 0;
while(ssl_read_line(ssl, buf, sizeof(buf))) {
if(buf[0] == 0x04 && buf[1] == 0)
break; /* end of transmission */
if(!perform_addzone(ssl, xfrd, buf)) {
if(!ssl_printf(ssl, "error for input line '%s'\n",
buf))
return;
} else {
if(!ssl_printf(ssl, "added: %s\n", buf))
return;
num++;
}
}
(void)ssl_printf(ssl, "added %d zones\n", num);
}
/** do the delzones command */
static void
do_delzones(RES* ssl, xfrd_state_type* xfrd)
{
char buf[2048];
int num = 0;
while(ssl_read_line(ssl, buf, sizeof(buf))) {
if(buf[0] == 0x04 && buf[1] == 0)
break; /* end of transmission */
if(!perform_delzone(ssl, xfrd, buf)) {
if(!ssl_printf(ssl, "error for input line '%s'\n",
buf))
return;
} else {
if(!ssl_printf(ssl, "removed: %s\n", buf))
return;
num++;
}
}
(void)ssl_printf(ssl, "deleted %d zones\n", num);
}
/** remove TSIG key from config and add task so that reload does too */
static void remove_key(xfrd_state_type* xfrd, const char* kname)
{
/* add task before deletion because the name string could be deleted */
task_new_del_key(xfrd->nsd->task[xfrd->nsd->mytask], xfrd->last_task,
kname);
key_options_remove(xfrd->nsd->options, kname);
xfrd_set_reload_now(xfrd); /* this is executed when the current control
command ends, thus the entire config changes are bunched up */
}
/** add TSIG key to config and add task so that reload does too */
static void add_key(xfrd_state_type* xfrd, struct key_options* k)
{
key_options_add_modify(xfrd->nsd->options, k);
task_new_add_key(xfrd->nsd->task[xfrd->nsd->mytask], xfrd->last_task,
k);
xfrd_set_reload_now(xfrd);
}
/** find zone given the implicit pattern */
static const dname_type*
parse_implicit_name(xfrd_state_type* xfrd,const char* pname)
{
if(strncmp(pname, PATTERN_IMPLICIT_MARKER,
strlen(PATTERN_IMPLICIT_MARKER)) != 0)
return NULL;
return dname_parse(xfrd->region, pname +
strlen(PATTERN_IMPLICIT_MARKER));
}
/** remove cfgzone and add task so that reload does too */
static void
remove_cfgzone(xfrd_state_type* xfrd, const char* pname)
{
/* dname and find the zone for the implicit pattern */
struct zone_options* zopt = NULL;
const dname_type* dname = parse_implicit_name(xfrd, pname);
if(!dname) {
/* should have a parseable name, but it did not */
return;
}
/* find the zone entry for the implicit pattern */
zopt = zone_options_find(xfrd->nsd->options, dname);
if(!zopt) {
/* this should not happen; implicit pattern has zone entry */
region_recycle(xfrd->region, (void*)dname,
dname_total_size(dname));
return;
}
/** add cfgzone and add task so that reload does too */
static void
add_cfgzone(xfrd_state_type* xfrd, const char* pname)
{
/* add to our zonelist */
struct zone_options* zopt = zone_options_create(
xfrd->nsd->options->region);
if(!zopt)
return;
zopt->part_of_config = 1;
zopt->name = region_strdup(xfrd->nsd->options->region,
pname + strlen(PATTERN_IMPLICIT_MARKER));
zopt->pattern = pattern_options_find(xfrd->nsd->options, pname);
if(!zopt->name || !zopt->pattern)
return;
if(!nsd_options_insert_zone(xfrd->nsd->options, zopt)) {
log_msg(LOG_ERR, "bad domain name or duplicate zone '%s' "
"pattern %s", zopt->name, pname);
}
/* make addzone task and schedule reload */
task_new_add_zone(xfrd->nsd->task[xfrd->nsd->mytask],
xfrd->last_task, zopt->name, pname,
getzonestatid(xfrd->nsd->options, zopt));
/* zonestat_inc is done after the entire config file has been done */
xfrd_set_reload_now(xfrd);
/* add to xfrd - notify (for master and slaves) */
init_notify_send(xfrd->notify_zones, xfrd->region, zopt);
/* add to xfrd - slave */
if(zone_is_slave(zopt)) {
xfrd_init_slave_zone(xfrd, zopt);
}
}
/** remove pattern and add task so that reload does too */
static void
remove_pat(xfrd_state_type* xfrd, const char* name)
{
/* add task before deletion, because name-string could be deleted */
task_new_del_pattern(xfrd->nsd->task[xfrd->nsd->mytask],
xfrd->last_task, name);
pattern_options_remove(xfrd->nsd->options, name);
xfrd_set_reload_now(xfrd);
}
/** add pattern and add task so that reload does too */
static void
add_pat(xfrd_state_type* xfrd, struct pattern_options* p)
{
pattern_options_add_modify(xfrd->nsd->options, p);
task_new_add_pattern(xfrd->nsd->task[xfrd->nsd->mytask],
xfrd->last_task, p);
xfrd_set_reload_now(xfrd);
}
/** interrupt zones that are using changed or removed patterns */
static void
repat_interrupt_zones(xfrd_state_type* xfrd, struct nsd_options* newopt)
{
/* if masterlist changed:
* interrupt slave zone (UDP or TCP) transfers.
* slave zones reset master to start of list.
*/
xfrd_zone_type* xz;
struct notify_zone* nz;
RBTREE_FOR(xz, xfrd_zone_type*, xfrd->zones) {
struct pattern_options* oldp = xz->zone_options->pattern;
struct pattern_options* newp = pattern_options_find(newopt,
oldp->pname);
if(!newp || !acl_list_equal(oldp->request_xfr,
newp->request_xfr)) {
/* interrupt transfer */
if(xz->tcp_conn != -1) {
xfrd_tcp_release(xfrd->tcp_set, xz);
xfrd_set_refresh_now(xz);
} else if(xz->zone_handler.ev_fd != -1) {
xfrd_udp_release(xz);
xfrd_set_refresh_now(xz);
}
xz->master = 0;
xz->master_num = 0;
xz->next_master = -1;
xz->round_num = -1; /* fresh set of retries */
}
}
/* if notify list changed:
* interrupt notify that is busy.
* reset notify to start of list. (clear all other reset_notify)
*/
RBTREE_FOR(nz, struct notify_zone*, xfrd->notify_zones) {
struct pattern_options* oldp = nz->options->pattern;
struct pattern_options* newp = pattern_options_find(newopt,
oldp->pname);
if(!newp || !acl_list_equal(oldp->notify, newp->notify)) {
/* interrupt notify */
if(nz->notify_send_enable) {
notify_disable(nz);
/* set to restart the notify after the
* pattern has been changed. */
nz->notify_restart = 2;
} else {
nz->notify_restart = 1;
}
} else {
nz->notify_restart = 0;
}
}
}
/** check if patterns have changed */
static void
repat_patterns(xfrd_state_type* xfrd, struct nsd_options* newopt)
{
/* zones that use changed patterns must have:
* - their AXFR/IXFR interrupted: try again, acl may have changed.
* if the old master/key still exists, OK, fix master-numptrs and
* keep going. Otherwise, stop xfer and reset TSIG.
* - send NOTIFY reset to start of NOTIFY list (and TSIG reset).
*/
struct nsd_options* oldopt = xfrd->nsd->options;
struct pattern_options* p;
int search_zones = 0;
repat_interrupt_zones(xfrd, newopt);
/* find deleted patterns */
p = (struct pattern_options*)rbtree_first(oldopt->patterns);
while((rbnode_type*)p != RBTREE_NULL) {
struct pattern_options* next = (struct pattern_options*)
rbtree_next((rbnode_type*)p);
if(!pattern_options_find(newopt, p->pname)) {
if(p->implicit) {
/* first remove its zone */
VERBOSITY(1, (LOG_INFO, "zone removed from config: %s", p->pname + strlen(PATTERN_IMPLICIT_MARKER)));
remove_cfgzone(xfrd, p->pname);
}
remove_pat(xfrd, p->pname);
}
p = next;
}
/* find added or changed patterns */
RBTREE_FOR(p, struct pattern_options*, newopt->patterns) {
struct pattern_options* origp = pattern_options_find(oldopt,
p->pname);
if(!origp) {
/* no zones can use it, no zone_interrupt needed */
add_pat(xfrd, p);
if(p->implicit) {
VERBOSITY(1, (LOG_INFO, "zone added to config: %s", p->pname + strlen(PATTERN_IMPLICIT_MARKER)));
add_cfgzone(xfrd, p->pname);
}
} else if(!pattern_options_equal(p, origp)) {
uint8_t newstate = 0;
if (p->request_xfr && !origp->request_xfr) {
newstate = REPAT_SLAVE;
} else if (!p->request_xfr && origp->request_xfr) {
newstate = REPAT_MASTER;
}
add_pat(xfrd, p);
if (p->implicit && newstate) {
const dname_type* dname =
parse_implicit_name(xfrd, p->pname);
if (dname) {
if (newstate == REPAT_SLAVE) {
struct zone_options* zopt =
zone_options_find(
oldopt, dname);
if (zopt) {
xfrd_init_slave_zone(
xfrd, zopt);
}
} else if (newstate == REPAT_MASTER) {
xfrd_del_slave_zone(xfrd,
dname);
}
region_recycle(xfrd->region,
(void*)dname,
dname_total_size(dname));
}
} else if(!p->implicit && newstate) {
/* search all zones with this pattern */
search_zones = 1;
origp->xfrd_flags = newstate;
}
}
}
if (search_zones) {
struct zone_options* zone_opt;
/* search in oldopt because 1) it contains zonelist zones,
* and 2) you need oldopt(existing) to call xfrd_init */
RBTREE_FOR(zone_opt, struct zone_options*, oldopt->zone_options) {
struct pattern_options* oldp = zone_opt->pattern;
if (!oldp->implicit) {
if (oldp->xfrd_flags == REPAT_SLAVE) {
/* xfrd needs stable reference so get
* it from the oldopt(modified) tree */
xfrd_init_slave_zone(xfrd, zone_opt);
} else if (oldp->xfrd_flags == REPAT_MASTER) {
xfrd_del_slave_zone(xfrd,
(const dname_type*)
zone_opt->node.key);
}
oldp->xfrd_flags = 0;
}
}
}
repat_interrupt_notify_start(xfrd);
}
/** true if options are different that can be set via repat. */
static int
repat_options_changed(xfrd_state_type* xfrd, struct nsd_options* newopt)
{
#ifdef RATELIMIT
if(xfrd->nsd->options->rrl_ratelimit != newopt->rrl_ratelimit)
return 1;
if(xfrd->nsd->options->rrl_whitelist_ratelimit != newopt->rrl_whitelist_ratelimit)
return 1;
if(xfrd->nsd->options->rrl_slip != newopt->rrl_slip)
return 1;
#else
(void)xfrd; (void)newopt;
#endif
return 0;
}
/** print errors over ssl, gets pointer-to-pointer to ssl, so it can set
* the pointer to NULL on failure and stop printing */
static void
print_ssl_cfg_err(void* arg, const char* str)
{
RES** ssl = (RES**)arg;
if(!*ssl) return;
if(!ssl_printf(*ssl, "%s", str))
*ssl = NULL; /* failed, stop printing */
}
/** do the repattern command: reread config file and apply keys, patterns */
static void
do_repattern(RES* ssl, xfrd_state_type* xfrd)
{
region_type* region = region_create(xalloc, free);
struct nsd_options* opt;
const char* cfgfile = xfrd->nsd->options->configfile;
/* check chroot and configfile, if possible to reread */
if(xfrd->nsd->chrootdir) {
size_t l = strlen(xfrd->nsd->chrootdir);
while(l>0 && xfrd->nsd->chrootdir[l-1] == '/')
--l;
if(strncmp(xfrd->nsd->chrootdir, cfgfile, l) != 0) {
(void)ssl_printf(ssl, "error %s is not relative to %s: "
"chroot prevents reread of config\n",
cfgfile, xfrd->nsd->chrootdir);
region_destroy(region);
return;
}
cfgfile += l;
}
(void)ssl_printf(ssl, "reconfig start, read %s\n", cfgfile);
opt = nsd_options_create(region);
if(!parse_options_file(opt, cfgfile, &print_ssl_cfg_err, &ssl)) {
/* error already printed */
region_destroy(region);
return;
}
/* check for differences in TSIG keys and patterns, and apply,
* first the keys, so that pattern->keyptr can be set right. */
repat_keys(xfrd, opt);
repat_patterns(xfrd, opt);
repat_options(xfrd, opt);
zonestat_inc_ifneeded(xfrd);
send_ok(ssl);
region_destroy(region);
}
/** do the serverpid command: printout pid of server process */
static void
do_serverpid(RES* ssl, xfrd_state_type* xfrd)
{
(void)ssl_printf(ssl, "%u\n", (unsigned)xfrd->reload_pid);
}
/** do the assoc_tsig command: associate the zone to use the tsig name */
static void
do_assoc_tsig(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
region_type* region = xfrd->nsd->options->region;
char* arg2 = NULL;
struct zone_options* zone;
struct key_options* key_opt;
if(used_key) {
(void)ssl_printf(ssl, "error: key: %s is in use and cannot be deleted\n", arg);
return;
} else {
remove_key(xfrd, arg);
log_msg(LOG_INFO, "key: %s is successfully deleted\n", arg);
}
/** check for name with end-of-string, space or tab after it */
static int
cmdcmp(char* p, const char* cmd, size_t len)
{
return strncmp(p,cmd,len)==0 && (p[len]==0||p[len]==' '||p[len]=='\t');
}
/* rcode */
for(i=0; i<17; i++) {
if(inhibit_zero && st->rcode[i] == 0 &&
i > RCODE_YXDOMAIN) /* NSD does not use larger */
continue;
if(!ssl_printf(ssl, "%s%snum.rcode.%s=%lu\n", n, d, rcstr[i],
(unsigned long)st->rcode[i]))
return;
}
/* edns */
if(!ssl_printf(ssl, "%s%snum.edns=%lu\n", n, d, (unsigned long)st->edns))
return;
/* ednserr */
if(!ssl_printf(ssl, "%s%snum.ednserr=%lu\n", n, d,
(unsigned long)st->ednserr))
return;
/* qudp */
if(!ssl_printf(ssl, "%s%snum.udp=%lu\n", n, d, (unsigned long)st->qudp))
return;
/* qudp6 */
if(!ssl_printf(ssl, "%s%snum.udp6=%lu\n", n, d, (unsigned long)st->qudp6))
return;
/* ctcp */
if(!ssl_printf(ssl, "%s%snum.tcp=%lu\n", n, d, (unsigned long)st->ctcp))
return;
/* ctcp6 */
if(!ssl_printf(ssl, "%s%snum.tcp6=%lu\n", n, d, (unsigned long)st->ctcp6))
return;
/* ctls */
if(!ssl_printf(ssl, "%s%snum.tls=%lu\n", n, d, (unsigned long)st->ctls))
return;
/* ctls6 */
if(!ssl_printf(ssl, "%s%snum.tls6=%lu\n", n, d, (unsigned long)st->ctls6))
return;
/* nona */
if(!ssl_printf(ssl, "%s%snum.answer_wo_aa=%lu\n", n, d,
(unsigned long)st->nona))
return;
/* rxerr */
if(!ssl_printf(ssl, "%s%snum.rxerr=%lu\n", n, d, (unsigned long)st->rxerr))
return;
/* txerr */
if(!ssl_printf(ssl, "%s%snum.txerr=%lu\n", n, d, (unsigned long)st->txerr))
return;
/* number of requested-axfr, number of times axfr served to clients */
if(!ssl_printf(ssl, "%s%snum.raxfr=%lu\n", n, d, (unsigned long)st->raxfr))
return;
/* number of requested-ixfr, number of times ixfr served to clients */
if(!ssl_printf(ssl, "%s%snum.rixfr=%lu\n", n, d, (unsigned long)st->rixfr))
return;
/* truncated */
if(!ssl_printf(ssl, "%s%snum.truncated=%lu\n", n, d,
(unsigned long)st->truncated))
return;
static void
zonestat_print(RES* ssl, xfrd_state_type* xfrd, int clear,
struct nsdst** zonestats)
{
struct zonestatname* n;
struct nsdst stat0, stat1;
RBTREE_FOR(n, struct zonestatname*, xfrd->nsd->options->zonestatnames){
char* name = (char*)n->node.key;
if(n->id >= xfrd->zonestat_safe)
continue; /* newly allocated and reload has not yet
done and replied with new size */
if(name == NULL || name[0]==0)
continue; /* empty name, do not output */
/* the statistics are stored in two blocks, during reload
* the newly forked processes get the other block to use,
* these blocks are mmapped and are currently in use to
* add statistics to */
memcpy(&stat0, &zonestats[0][n->id], sizeof(stat0));
memcpy(&stat1, &zonestats[1][n->id], sizeof(stat1));
stats_add(&stat0, &stat1);
/* save a copy of current (cumulative) stats in stat1 */
memcpy(&stat1, &stat0, sizeof(stat1));
/* subtract last total of stats that was 'cleared' */
if(n->id < xfrd->zonestat_clear_num &&
xfrd->zonestat_clear[n->id])
stats_subtract(&stat0, xfrd->zonestat_clear[n->id]);
if(clear) {
/* extend storage array if needed */
if(n->id >= xfrd->zonestat_clear_num) {
if(n->id+1 < xfrd->nsd->options->zonestatnames->count)
resize_zonestat(xfrd, xfrd->nsd->options->zonestatnames->count);
else
resize_zonestat(xfrd, n->id+1);
}
if(!xfrd->zonestat_clear[n->id])
xfrd->zonestat_clear[n->id] = xalloc(
sizeof(struct nsdst));
/* store last total of stats */
memcpy(xfrd->zonestat_clear[n->id], &stat1,
sizeof(struct nsdst));
}
/* stat0 contains the details that we want to print */
if(!ssl_printf(ssl, "%s%snum.queries=%lu\n", name, ".",
(unsigned long)(stat0.qudp + stat0.qudp6 + stat0.ctcp +
stat0.ctcp6 + stat0.ctls + stat0.ctls6)))
return;
print_stat_block(ssl, name, ".", &stat0);
}
}
#endif /* USE_ZONE_STATS */
/* per CPU and total */
for(i=0; i<xfrd->nsd->child_count; i++) {
if(!ssl_printf(ssl, "server%d.queries=%lu\n", (int)i,
(unsigned long)xfrd->nsd->children[i].query_count))
return;
total += xfrd->nsd->children[i].query_count;
}
if(!ssl_printf(ssl, "num.queries=%lu\n", (unsigned long)total))
return;
/* time elapsed and uptime (in seconds) */
timeval_subtract(&uptime, now, &xfrd->nsd->rc->boot_time);
timeval_subtract(&elapsed, now, &xfrd->nsd->rc->stats_time);
if(!ssl_printf(ssl, "time.boot=%lu.%6.6lu\n",
(unsigned long)uptime.tv_sec, (unsigned long)uptime.tv_usec))
return;
if(!ssl_printf(ssl, "time.elapsed=%lu.%6.6lu\n",
(unsigned long)elapsed.tv_sec, (unsigned long)elapsed.tv_usec))
return;
/* add the old and new processes stat values into the first part of the
* array of stats */
static void
process_stats_add_old_new(xfrd_state_type* xfrd, struct nsdst* stats)
{
size_t i;
uint64_t dbd = stats[0].db_disk;
uint64_t dbm = stats[0].db_mem;
/* The old and new server processes have separate stat blocks,
* and these are added up together. This results in the statistics
* values per server-child. The reload task briefly forks both
* old and new server processes. */
for(i=0; i<xfrd->nsd->child_count; i++) {
stats_add(&stats[i], &stats[xfrd->nsd->child_count+i]);
}
stats[0].db_disk = dbd;
stats[0].db_mem = dbm;
}
/* manage clearing of stats, a cumulative count of cleared statistics */
static void
process_stats_manage_clear(xfrd_state_type* xfrd, struct nsdst* stats,
int peek)
{
struct nsdst st;
size_t i;
if(peek) {
/* Subtract the earlier resetted values from the numbers,
* but do not reset the values that are retrieved now. */
if(!xfrd->stat_clear)
return; /* nothing to subtract */
for(i=0; i<xfrd->nsd->child_count; i++) {
/* subtract cumulative count that has been reset */
stats_subtract(&stats[i], &xfrd->stat_clear[i]);
}
return;
}
if(!xfrd->stat_clear)
xfrd->stat_clear = region_alloc_zero(xfrd->region,
sizeof(struct nsdst)*xfrd->nsd->child_count);
for(i=0; i<xfrd->nsd->child_count; i++) {
/* store cumulative count copy */
memcpy(&st, &stats[i], sizeof(st));
/* subtract cumulative count that has been reset */
stats_subtract(&stats[i], &xfrd->stat_clear[i]);
/* store cumulative count in the cleared value array */
memcpy(&xfrd->stat_clear[i], &st, sizeof(st));
}
}
/* add up the statistics to get the total over the server children. */
static void
process_stats_add_total(xfrd_state_type* xfrd, struct nsdst* total,
struct nsdst* stats)
{
size_t i;
/* copy over the first one, with also the nonadded values. */
memcpy(total, &stats[0], sizeof(*total));
xfrd->nsd->children[0].query_count = stats[0].qudp + stats[0].qudp6
+ stats[0].ctcp + stats[0].ctcp6 + stats[0].ctls
+ stats[0].ctls6;
for(i=1; i<xfrd->nsd->child_count; i++) {
stats_add(total, &stats[i]);
xfrd->nsd->children[i].query_count = stats[i].qudp
+ stats[i].qudp6 + stats[i].ctcp + stats[i].ctcp6
+ stats[i].ctls + stats[i].ctls6;
}
}
/* process the statistics and output them */
static void
process_stats(RES* ssl, xfrd_state_type* xfrd, int peek)
{
struct timeval stattime;
struct nsdst* stats, *zonestats[2], total;
if (unlink(path) && errno != ENOENT) {
/* The socket already exists and cannot be removed */
log_msg(LOG_ERR, "Cannot remove old local socket %s (%s)",
path, strerror(errno));
goto err;
}