[BACK]Return to remote.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / nsd

File: [local] / src / usr.sbin / nsd / remote.c (download)

Revision 1.27, Fri Apr 12 15:53:34 2024 UTC (8 weeks, 1 day ago) by florian
Branch: MAIN
CVS Tags: HEAD
Changes since 1.26: +169 -18 lines

Update to nsd 4.9.1

sparc64 built test by tb
OK tb, sthen

/*
 * 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_SSL
#ifdef HAVE_OPENSSL_SSL_H
#include <openssl/ssl.h>
#endif
#ifdef HAVE_OPENSSL_ERR_H
#include <openssl/err.h>
#endif
#ifdef HAVE_OPENSSL_RAND_H
#include <openssl/rand.h>
#endif
#endif /* HAVE_SSL */
#include <ctype.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <errno.h>
#ifndef USE_MINI_EVENT
#  ifdef HAVE_EVENT_H
#    include <event.h>
#  else
#    include <event2/event.h>
#    include "event2/event_struct.h"
#    include "event2/event_compat.h"
#  endif
#else
#  include "mini_event.h"
#endif
#include "remote.h"
#include "util.h"
#include "xfrd.h"
#include "xfrd-catalog-zones.h"
#include "xfrd-notify.h"
#include "xfrd-tcp.h"
#include "nsd.h"
#include "options.h"
#include "difffile.h"
#include "ipc.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
#define REPAT_CATALOG_CONSUMER        4
#define REPAT_CATALOG_CONSUMER_DEINIT 8

/** 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

/** ---- end of private defines ---- **/

#ifdef HAVE_SSL
/** log ssl crypto err */
static void
log_crypto_err(const char* str)
{
	/* error:[error code]:[library name]:[function name]:[reason string] */
	char buf[128];
	unsigned long e;
	ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
	log_msg(LOG_ERR, "%s crypto %s", str, buf);
	while( (e=ERR_get_error()) ) {
		ERR_error_string_n(e, buf, sizeof(buf));
		log_msg(LOG_ERR, "and additionally crypto %s", buf);
	}
}
#endif /* HAVE_SSL */

#ifdef BIND8_STATS
/** subtract timers and the values do not overflow or become negative */
static void
timeval_subtract(struct timeval* d, const struct timeval* end, 
	const struct timeval* start)
{
#ifndef S_SPLINT_S
	time_t end_usec = end->tv_usec;
	d->tv_sec = end->tv_sec - start->tv_sec;
	if(end_usec < start->tv_usec) {
		end_usec += 1000000;
		d->tv_sec--;
	}
	d->tv_usec = end_usec - start->tv_usec;
#endif
}
#endif /* BIND8_STATS */

#ifdef HAVE_SSL
static int
remote_setup_ctx(struct daemon_remote* rc, struct nsd_options* cfg)
{
	char* s_cert = cfg->server_cert_file;
	char* s_key = cfg->server_key_file;
	rc->ctx = server_tls_ctx_setup(s_key, s_cert, s_cert);
	if(!rc->ctx) {
		log_msg(LOG_ERR, "could not setup remote control TLS context");
		return 0;
	}
	return 1;
}
#endif /* HAVE_SSL */

struct daemon_remote*
daemon_remote_create(struct nsd_options* cfg)
{
	struct daemon_remote* rc = (struct daemon_remote*)xalloc_zero(
		sizeof(*rc));
	rc->max_active = 10;
	assert(cfg->control_enable);

	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;
	}

	if(gettimeofday(&rc->boot_time, NULL) == -1)
		log_msg(LOG_ERR, "gettimeofday: %s", strerror(errno));
	rc->stats_time = rc->boot_time;

	return rc;
}

void daemon_remote_close(struct daemon_remote* rc)
{
	struct rc_state* p, *np;
	struct acceptlist* h, *nh;
	if(!rc) return;

	/* close listen sockets */
	h = rc->accept_list;
	while(h) {
		nh = h->next;
		if(h->event_added)
			event_del(&h->c);
		close(h->c.ev_fd);
		free(h->ident);
		free(h);
		h = nh;
	}
	rc->accept_list = NULL;

	/* close busy connection sockets */
	p = rc->busy_list;
	while(p) {
		np = p->next;
		if(p->event_added)
			event_del(&p->c);
#ifdef HAVE_SSL
		if(p->ssl)
			SSL_free(p->ssl);
#endif
		close(p->c.ev_fd);
		free(p);
		p = np;
	}
	rc->busy_list = NULL;
	rc->active = 0;
}

void daemon_remote_delete(struct daemon_remote* rc)
{
	if(!rc) return;
	daemon_remote_close(rc);
#ifdef HAVE_SSL
	if(rc->ctx) {
		SSL_CTX_free(rc->ctx);
	}
#endif
	free(rc);
}

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;
	}

	/* alloc */
	hl = (struct acceptlist*)xalloc_zero(sizeof(*hl));
	hl->rc = rc;
	hl->ident = strdup(ip);
	if(!hl->ident) {
		log_msg(LOG_ERR, "malloc failure");
		close(fd);
		free(hl);
		return 0;
	}
	hl->next = rc->accept_list;
	rc->accept_list = hl;

	hl->c.ev_fd = fd;
	hl->event_added = 0;
	return 1;
}

int
daemon_remote_open_ports(struct daemon_remote* rc, struct nsd_options* cfg)
{
	assert(cfg->control_enable && cfg->control_port);
	if(cfg->control_interface) {
		ip_address_option_type* p;
		for(p = cfg->control_interface; p; p = p->next) {
			if(!add_open(rc, cfg, p->address, cfg->control_port, 1)) {
				return 0;
			}
		}
	} else {
		/* defaults */
		if(cfg->do_ip6 && !add_open(rc, cfg, "::1", cfg->control_port, 0)) {
			return 0;
		}
		if(cfg->do_ip4 &&
			!add_open(rc, cfg, "127.0.0.1", cfg->control_port, 1)) {
			return 0;
		}
	}
	return 1;
}

void
daemon_remote_attach(struct daemon_remote* rc, struct xfrd_state* xfrd)
{
	int fd;
	struct acceptlist* p;
	if(!rc) return;
	rc->xfrd = xfrd;
	for(p = rc->accept_list; p; p = p->next) {
		/* add event */
		fd = p->c.ev_fd;
		memset(&p->c, 0, sizeof(p->c));
		event_set(&p->c, fd, EV_PERSIST|EV_READ, remote_accept_callback,
			p);
		if(event_base_set(xfrd->event_base, &p->c) != 0)
			log_msg(LOG_ERR, "remote: cannot set event_base");
		if(event_add(&p->c, NULL) != 0)
			log_msg(LOG_ERR, "remote: cannot add event");
		p->event_added = 1;
	}
}

static void
remote_accept_callback(int fd, short event, void* arg)
{
	struct acceptlist *hl = (struct acceptlist*)arg;
	struct daemon_remote *rc = hl->rc;
#ifdef INET6
	struct sockaddr_storage addr;
#else
	struct sockaddr_in addr;
#endif
	socklen_t addrlen;
	int newfd;
	struct rc_state* n;

	if (!(event & EV_READ)) {
		return;
	}

	/* perform the accept */
	addrlen = sizeof(addr);
#ifndef HAVE_ACCEPT4
	newfd = accept(fd, (struct sockaddr*)&addr, &addrlen);
#else
	newfd = accept4(fd, (struct sockaddr*)&addr, &addrlen, SOCK_NONBLOCK);
#endif
	if(newfd == -1) {
		if (    errno != EINTR
			&& errno != EWOULDBLOCK
#ifdef ECONNABORTED
			&& errno != ECONNABORTED
#endif /* ECONNABORTED */
#ifdef EPROTO
			&& errno != EPROTO
#endif /* EPROTO */
			) {
			log_msg(LOG_ERR, "accept failed: %s", strerror(errno));
		}
		return;
	}

	/* create new commpoint unless we are servicing already */
	if(rc->active >= rc->max_active) {
		log_msg(LOG_WARNING, "drop incoming remote control: "
			"too many connections");
	close_exit:
		close(newfd);
		return;
	}

#ifndef HAVE_ACCEPT4
	if (fcntl(newfd, F_SETFL, O_NONBLOCK) == -1) {
		log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
		goto close_exit;
	}
#endif

	/* 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;
	}

	n->tval.tv_sec = REMOTE_CONTROL_TCP_TIMEOUT; 
	n->tval.tv_usec = 0L;
	n->fd = newfd;

	memset(&n->c, 0, sizeof(n->c));
	event_set(&n->c, newfd, EV_PERSIST|EV_TIMEOUT|EV_READ,
		remote_control_callback, n);
	if(event_base_set(xfrd->event_base, &n->c) != 0) {
		log_msg(LOG_ERR, "remote_accept: cannot set event_base");
		free(n);
		goto close_exit;
	}
	if(event_add(&n->c, &n->tval) != 0) {
		log_msg(LOG_ERR, "remote_accept: cannot add event");
		free(n);
		goto close_exit;
	}
	n->event_added = 1;

	if(2 <= verbosity) {
		if(hl->ident && hl->ident[0] == '/') {
			VERBOSITY(2, (LOG_INFO, "new control connection from %s", hl->ident));
		} else {
			char s[128];
			addr2str(&addr, s, sizeof(s));
			VERBOSITY(2, (LOG_INFO, "new control connection from %s", s));
		}
	}

#ifdef HAVE_SSL
	if(rc->ctx) {
		n->shake_state = rc_hs_read;
		n->ssl = SSL_new(rc->ctx);
		if(!n->ssl) {
			log_crypto_err("could not SSL_new");
			if(n->event_added)
				event_del(&n->c);
			free(n);
			goto close_exit;
		}
		SSL_set_accept_state(n->ssl);
		(void)SSL_set_mode(n->ssl, SSL_MODE_AUTO_RETRY);
		if(!SSL_set_fd(n->ssl, newfd)) {
			log_crypto_err("could not SSL_set_fd");
			if(n->event_added)
				event_del(&n->c);
			SSL_free(n->ssl);
			free(n);
			goto close_exit;
		}
	} else {
		n->ssl = NULL;
	}
#endif /* HAVE_SSL */

	n->rc = rc;
	n->stats_next = NULL;
	n->prev = NULL;
	n->next = rc->busy_list;
	if(n->next) n->next->prev = n;
	rc->busy_list = n;
	rc->active ++;

	/* perform the first nonblocking read already, for windows, 
	 * so it can return wouldblock. could be faster too. */
	remote_control_callback(newfd, EV_READ, n);
}

/** delete from list */
static void
state_list_remove_elem(struct rc_state** list, struct rc_state* todel)
{
	if(todel->prev) todel->prev->next = todel->next;
	else	*list = todel->next;
	if(todel->next) todel->next->prev = todel->prev;
}

/** decrease active count and remove commpoint from busy list */
static void
clean_point(struct daemon_remote* rc, struct rc_state* s)
{
	state_list_remove_elem(&rc->busy_list, s);
	rc->active --;
	if(s->event_added)
		event_del(&s->c);
#ifdef HAVE_SSL
	if(s->ssl) {
		SSL_shutdown(s->ssl);
		SSL_free(s->ssl);
	}
#endif /* HAVE_SSL */
	close(s->c.ev_fd);
	free(s);
}

static int
ssl_print_text(RES* res, const char* text)
{
	if(!res) 
		return 0;
#ifdef HAVE_SSL
	if(res->ssl) {
		int r;
		ERR_clear_error();
		if((r=SSL_write(res->ssl, text, (int)strlen(text))) <= 0) {
			if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN) {
				VERBOSITY(2, (LOG_WARNING, "in SSL_write, peer "
					"closed connection"));
				return 0;
			}
			log_crypto_err("could not SSL_write");
			return 0;
		}
	} else {
#endif /* HAVE_SSL */
		if(write_socket(res->fd, text, strlen(text)) <= 0) {
			log_msg(LOG_ERR, "could not write: %s",
				strerror(errno));
			return 0;
		}
#ifdef HAVE_SSL
	}
#endif /* HAVE_SSL */
	return 1;
}

/** 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;

	if(!(xfrd->ipc_handler_flags&EV_WRITE)) {
		ipc_xfrd_set_listening(xfrd, EV_PERSIST|EV_READ|EV_WRITE);
	}

	send_ok(ssl);
}

/** 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);
}

/** do the reload command */
static void
do_reload(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	struct zone_options* zo;
	if(!get_zone_arg(ssl, xfrd, arg, &zo))
		return;
	task_new_check_zonefiles(xfrd->nsd->task[xfrd->nsd->mytask],
		xfrd->last_task, zo?(const dname_type*)zo->node.key:NULL);
	xfrd_set_reload_now(xfrd);
	send_ok(ssl);
}

/** do the write command */
static void
do_write(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	struct zone_options* zo;
	if(!get_zone_arg(ssl, xfrd, arg, &zo))
		return;
	task_new_write_zonefiles(xfrd->nsd->task[xfrd->nsd->mytask],
		xfrd->last_task, zo?(const dname_type*)zo->node.key:NULL);
	xfrd_set_reload_now(xfrd);
	send_ok(ssl);
}

/** do the notify command */
static void
do_notify(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	struct zone_options* zo;
	if(!get_zone_arg(ssl, xfrd, arg, &zo))
		return;
	if(zo) {
		struct notify_zone* n = (struct notify_zone*)rbtree_search(
			xfrd->notify_zones, (const dname_type*)zo->node.key);
		if(n) {
			xfrd_notify_start(n, xfrd);
			send_ok(ssl);
		} else {
			(void)ssl_printf(ssl, "error zone does not have notify\n");
		}
	} else {
		struct notify_zone* n;
		RBTREE_FOR(n, struct notify_zone*, xfrd->notify_zones) {
			xfrd_notify_start(n, xfrd);
		}
		send_ok(ssl);
	}
}

/** do the transfer command */
static void
do_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) {
			xfrd_handle_notify_and_start_xfr(zone, NULL);
			send_ok(ssl);
		} else {
			(void)ssl_printf(ssl, "error zone not secondary\n");
		}
	} else {
		RBTREE_FOR(zone, xfrd_zone_type*, xfrd->zones) {
			xfrd_handle_notify_and_start_xfr(zone, NULL);
		}
		(void)ssl_printf(ssl, "ok, %lu zones\n", (unsigned long)xfrd->zones->count);
	}
}

/** 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 secondary\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);
	}
}

static int
print_soa_status(RES* ssl, const char* str, xfrd_soa_type* soa, time_t acq)
{
	if(acq) {
		if(!ssl_printf(ssl, "	%s: \"%u since %s\"\n", str,
			(unsigned)ntohl(soa->serial), xfrd_pretty_time(acq)))
			return 0;
	} else {
		if(!ssl_printf(ssl, "	%s: none\n", str))
			return 0;
	}
	return 1;
}

/** print zonestatus for one domain */
static int
print_zonestatus(RES* ssl, xfrd_state_type* xfrd, struct zone_options* zo)
{
	xfrd_zone_type* xz = (xfrd_zone_type*)rbtree_search(xfrd->zones,
		(const dname_type*)zo->node.key);
	struct notify_zone* nz = (struct notify_zone*)rbtree_search(
		xfrd->notify_zones, (const dname_type*)zo->node.key);
	if(!ssl_printf(ssl, "zone:	%s\n", zo->name))
		return 0;
	if(!zo->part_of_config) {
		if(!ssl_printf(ssl, "	pattern: %s\n", zo->pattern->pname))
			return 0;
	}
	if(zone_is_catalog_consumer(zo)) {
		zone_type* zone = namedb_find_zone(xfrd->nsd->db,
				(const dname_type*)zo->node.key);
		struct xfrd_catalog_consumer_zone* consumer_zone =
			(struct xfrd_catalog_consumer_zone*)
			rbtree_search( xfrd->catalog_consumer_zones
			             , zo->node.key);

		if(!ssl_printf(ssl, "	catalog: consumer"))
			return 0;
		if(zone && zone->soa_rrset && zone->soa_rrset->rrs
		&& zone->soa_rrset->rrs[0].rdata_count > 2
		&& rdata_atom_size(zone->soa_rrset->rrs[0].rdatas[2]) ==
							sizeof(uint32_t)) {
			if(!ssl_printf(ssl, " (serial: %u, # members: %zu)\n",
					read_uint32(rdata_atom_data(
					zone->soa_rrset->rrs[0].rdatas[2])),
					  consumer_zone
					? consumer_zone->member_ids.count : 0))
				return 0;

		} else if(!ssl_printf(ssl, "\n"))
			return 0;
		if(invalid_catalog_consumer_zone(zo)) {
			if(!ssl_printf(ssl, "	catalog-invalid: %s\n",
					invalid_catalog_consumer_zone(zo)))
				return 0;
		}
	}
	if(zone_is_catalog_producer(zo)) {
		struct xfrd_catalog_producer_zone* producer_zone =
			(struct xfrd_catalog_producer_zone*)
			rbtree_search( xfrd->catalog_producer_zones
			             , zo->node.key);
		if(!ssl_printf(ssl, "	catalog: producer"))
			return 0;
		if(producer_zone) {
			if(!ssl_printf(ssl, " (serial: %u, # members: %zu)\n",
					(unsigned)producer_zone->serial,
				       	producer_zone->member_ids.count))
				return 0;
		} else if(!ssl_printf(ssl, "\n"))
			return 0;
		if (zone_is_slave(zo)) {
			if(!ssl_printf(ssl, "	catalog-invalid: a catalog "
					"producer cannot be a secondary zone"))
				return 0;
		}
	}
	if(zone_is_catalog_member(zo)) {
		if(!ssl_printf(ssl, "	catalog-member-id: %s\n",
		   as_catalog_member_zone(zo)->member_id
		 ? dname_to_string(as_catalog_member_zone(zo)->member_id, NULL)
		 : "ERROR member-id is missing!"))
			return 0;
	}
	if(nz) {
		if(nz->is_waiting) {
			if(!ssl_printf(ssl, "	notify: \"waiting-for-fd\"\n"))
				return 0;
		} else if(nz->notify_send_enable || nz->notify_send6_enable) {
			int i;
			if(!ssl_printf(ssl, "	notify: \"send"))
				return 0;
			for(i=0; i<NOTIFY_CONCURRENT_MAX; i++) {
				if(!nz->pkts[i].dest) continue;
				if(!ssl_printf(ssl, " %s",
					nz->pkts[i].dest->ip_address_spec))
					return 0;
			}
			if(!ssl_printf(ssl, " with serial %u\"\n",
				(unsigned)ntohl(nz->current_soa->serial)))
				return 0;
		}
	}
	if(!xz) {
		if(!ssl_printf(ssl, "	state: primary\n"))
			return 0;
		return 1;
	}
	if(!ssl_printf(ssl, "	state: %s\n",
		(xz->state == xfrd_zone_ok)?"ok":(
		(xz->state == xfrd_zone_expired)?"expired":"refreshing")))
		return 0;
	if(!print_soa_status(ssl, "served-serial", &xz->soa_nsd,
		xz->soa_nsd_acquired))
		return 0;
	if(!print_soa_status(ssl, "commit-serial", &xz->soa_disk,
		xz->soa_disk_acquired))
		return 0;
	if(xz->round_num != -1) {
		if(!print_soa_status(ssl, "notified-serial", &xz->soa_notified,
			xz->soa_notified_acquired))
			return 0;
	} else if(xz->event_added) {
		if(!ssl_printf(ssl, "\twait: \"%lu sec between attempts\"\n",
			(unsigned long)xz->timeout.tv_sec))
			return 0;
	}

	/* UDP */
	if(xz->udp_waiting) {
		if(!ssl_printf(ssl, "	transfer: \"waiting-for-UDP-fd\"\n"))
			return 0;
	} else if(xz->zone_handler.ev_fd != -1 && xz->tcp_conn == -1) {
		if(!ssl_printf(ssl, "	transfer: \"sent UDP to %s\"\n",
			xz->master->ip_address_spec))
			return 0;
	}

	/* TCP */
	if(xz->tcp_waiting) {
		if(!ssl_printf(ssl, "	transfer: \"waiting-for-TCP-fd\"\n"))
			return 0;
	} else if(xz->tcp_conn != -1) {
		if(!ssl_printf(ssl, "	transfer: \"TCP connected to %s\"\n",
			xz->master->ip_address_spec))
			return 0;
	}

	return 1;
}

/** do the zonestatus command */
static void
do_zonestatus(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	struct zone_options* zo;
	if(!get_zone_arg(ssl, xfrd, arg, &zo))
		return;
	if(zo) (void)print_zonestatus(ssl, xfrd, zo);
	else {
		RBTREE_FOR(zo, struct zone_options*,
			xfrd->nsd->options->zone_options) {
			if(!print_zonestatus(ssl, xfrd, zo))
				return;
		}
	}
}

/** 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 status command */
static void
do_status(RES* ssl, xfrd_state_type* xfrd)
{
	if(!ssl_printf(ssl, "version: %s\n", PACKAGE_VERSION))
		return;
	if(!ssl_printf(ssl, "verbosity: %d\n", verbosity))
		return;
#ifdef RATELIMIT
	if(!ssl_printf(ssl, "ratelimit: %d\n",
		(int)xfrd->nsd->options->rrl_ratelimit))
		return;
#else
	(void)xfrd;
#endif
}

/** 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;
	}

	dname = dname_parse(xfrd->region, arg);
	if(!dname) {
		(void)ssl_printf(ssl, "error cannot parse zone name\n");
		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;
		}
		if(zone_is_catalog_consumer_member(zopt)) {
			(void)ssl_printf(ssl, "Error: Zone is a catalog "
			  "consumer member zone with id %s\nRepattern in the "
			  "catalog with a group property.\n", dname_to_string(
			  as_catalog_member_zone(zopt)->member_id, NULL));
			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 it in xfrd's catalog consumers list */
		if(zone_is_catalog_consumer(zopt)) {
			xfrd_deinit_catalog_consumer_zone(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_or_cat(xfrd->nsd->options, arg, arg2,
			xfrd_add_catalog_producer_member);
	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 - catalog consumer zones */
	if (zone_is_catalog_consumer(zopt)) {
		xfrd_init_catalog_consumer_zone(xfrd, zopt);
	}
	/* 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;
	}

	dname = dname_parse(xfrd->region, arg);
	if(!dname) {
		(void)ssl_printf(ssl, "error cannot parse zone name\n");
		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_or_cat(xfrd->nsd->options, arg, arg2,
			xfrd_add_catalog_producer_member);
	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 - catalog consumer zones */
	if (zone_is_catalog_consumer(zopt)) {
		xfrd_init_catalog_consumer_zone(xfrd, zopt);
	}
	/* 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;
	/* dont recycle dname when it becomes part of a xfrd_producer_member */
	int recycle_dname = 1;

	dname = dname_parse(xfrd->region, arg);
	if(!dname) {
		(void)ssl_printf(ssl, "error cannot parse zone name\n");
		return 0;
	}

	/* 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;
	}
	if(zone_is_catalog_consumer_member(zopt)
	&& as_catalog_member_zone(zopt)->member_id) {
		(void)ssl_printf(ssl, "Error: Zone is a catalog consumer "
		  "member zone with id %s\nRemove the member id from the "
		  "catalog to delete this zone.\n", dname_to_string(
		  as_catalog_member_zone(zopt)->member_id, NULL));
		region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
		dname = NULL;
		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 it in xfrd's catalog consumers list */
	if(zone_is_catalog_consumer(zopt)) {
		xfrd_deinit_catalog_consumer_zone(xfrd, dname);
	} else {
		recycle_dname = !xfrd_del_catalog_producer_member(xfrd, dname);
	}
	/* delete from config */
	zone_list_del(xfrd->nsd->options, zopt);

	if(recycle_dname)
		region_recycle(xfrd->region,
				(void*)dname, dname_total_size(dname));
	return 1;
}

/** do the addzone command */
static void
do_addzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	if(!perform_addzone(ssl, xfrd, arg))
		return;
	send_ok(ssl);
}

/** do the delzone command */
static void
do_delzone(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	if(!perform_delzone(ssl, xfrd, arg))
		return;
	send_ok(ssl);
}

/** do the changezone command */
static void
do_changezone(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	if(!perform_changezone(ssl, xfrd, arg))
		return;
	send_ok(ssl);
}

/** 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);
}

/** check if keys have changed */
static void repat_keys(xfrd_state_type* xfrd, struct nsd_options* newopt)
{
	struct nsd_options* oldopt = xfrd->nsd->options;
	struct key_options* k;
	/* find deleted keys */
	k = (struct key_options*)rbtree_first(oldopt->keys);
	while((rbnode_type*)k != RBTREE_NULL) {
		struct key_options* next = (struct key_options*)rbtree_next(
			(rbnode_type*)k);
		if(!key_options_find(newopt, k->name))
			remove_key(xfrd, k->name);
		k = next;
	}
	/* find added or changed keys */
	RBTREE_FOR(k, struct key_options*, newopt->keys) {
		struct key_options* origk = key_options_find(oldopt, k->name);
		if(!origk)
			add_key(xfrd, k);
		else if(!key_options_equal(k, origk))
			add_key(xfrd, k);
	}
}

/** 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;
	}

	/* 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 it in xfrd's catalog consumers list */
	if(zone_is_catalog_consumer(zopt)) {
		xfrd_deinit_catalog_consumer_zone(xfrd, dname);
	}

	/* delete from zoneoptions */
	zone_options_delete(xfrd->nsd->options, zopt);

	/* recycle parsed dname */
	region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
}

/** 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 - catalog consumer zones */
	if (zone_is_catalog_consumer(zopt)) {
		xfrd_init_catalog_consumer_zone(xfrd, zopt);
	}
	/* 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;
		}
	}
}

/** for notify, after the pattern changes, restart the affected notifies */
static void
repat_interrupt_notify_start(xfrd_state_type* xfrd)
{
	struct notify_zone* nz;
	RBTREE_FOR(nz, struct notify_zone*, xfrd->notify_zones) {
		if(nz->notify_restart) {
			if(nz->notify_current)
				nz->notify_current = nz->options->pattern->notify;
			if(nz->notify_restart == 2) {
				if(nz->notify_restart)
					xfrd_notify_start(nz, xfrd);
			}
		}
	}
}

/** 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;
			}
			if (   p->catalog_role == CATALOG_ROLE_CONSUMER
			&& origp->catalog_role != CATALOG_ROLE_CONSUMER) {
				newstate |= REPAT_CATALOG_CONSUMER;
			} else if (p->catalog_role != CATALOG_ROLE_CONSUMER
			    && origp->catalog_role == CATALOG_ROLE_CONSUMER) {
				newstate |= REPAT_CATALOG_CONSUMER_DEINIT;
			}
			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);
					}
					if (newstate & REPAT_CATALOG_CONSUMER) {
						struct zone_options* zopt =
							zone_options_find(
							oldopt, dname);
						if (zopt) {
							xfrd_init_catalog_consumer_zone(
								xfrd, zopt);
						}
					} else if (newstate & REPAT_CATALOG_CONSUMER_DEINIT) {
						xfrd_deinit_catalog_consumer_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);
				}
				if (oldp->xfrd_flags & REPAT_CATALOG_CONSUMER) {
					xfrd_init_catalog_consumer_zone(xfrd,
							zone_opt);
				} else if (oldp->xfrd_flags & REPAT_CATALOG_CONSUMER_DEINIT) {
					xfrd_deinit_catalog_consumer_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;
}

/** check if global options have changed */
static void
repat_options(xfrd_state_type* xfrd, struct nsd_options* newopt)
{
	if(repat_options_changed(xfrd, newopt)) {
		/* update our options */
#ifdef RATELIMIT
		xfrd->nsd->options->rrl_ratelimit = newopt->rrl_ratelimit;
		xfrd->nsd->options->rrl_whitelist_ratelimit = newopt->rrl_whitelist_ratelimit;
		xfrd->nsd->options->rrl_slip = newopt->rrl_slip;
#endif
		task_new_opt_change(xfrd->nsd->task[xfrd->nsd->mytask],
			xfrd->last_task, newopt);
		xfrd_set_reload_now(xfrd);
	}
}

/** 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,
				xfrd->nsd->options)) {
		/* 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 print_tsig command: printout tsig info */
static void
do_print_tsig(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	if(*arg == '\0') {
		struct key_options* key;
		RBTREE_FOR(key, struct key_options*, xfrd->nsd->options->keys) {
			if(!ssl_printf(ssl, "key: name: \"%s\" secret: \"%s\" algorithm: %s\n", key->name, key->secret, key->algorithm))
				return;
		}
		return;
	} else {
		struct key_options* key_opts = key_options_find(xfrd->nsd->options, arg);
		if(!key_opts) {
			(void)ssl_printf(ssl, "error: no such key with name: %s\n", arg);
			return;
		} else {
			(void)ssl_printf(ssl, "key: name: \"%s\" secret: \"%s\" algorithm: %s\n", arg, key_opts->secret, key_opts->algorithm);
		}
	}
}

/** do the update_tsig command: change existing tsig to new secret */
static void
do_update_tsig(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	struct region* region = xfrd->nsd->options->region;
	char* arg2 = NULL;
	uint8_t data[65536]; /* 64K */
	struct key_options* key_opt;

	if(*arg == '\0') {
		(void)ssl_printf(ssl, "error: missing argument (keyname)\n");
		return;
	}
	if(!find_arg2(ssl, arg, &arg2)) {
		(void)ssl_printf(ssl, "error: missing argument (secret)\n");
		return;
	}
	key_opt = key_options_find(xfrd->nsd->options, arg);
	if(!key_opt) {
		(void)ssl_printf(ssl, "error: no such key with name: %s\n", arg);
		memset(arg2, 0xdd, strlen(arg2));
		return;
	}
	if(__b64_pton(arg2, data, sizeof(data)) == -1) {
		(void)ssl_printf(ssl, "error: the secret: %s is not in b64 format\n", arg2);
		memset(data, 0xdd, sizeof(data)); /* wipe secret */
		memset(arg2, 0xdd, strlen(arg2));
		return;
	}
	log_msg(LOG_INFO, "changing secret provided with the key: %s with old secret %s and algo: %s\n", arg, key_opt->secret, key_opt->algorithm);
	if(key_opt->secret) {
		/* wipe old secret */
		memset(key_opt->secret, 0xdd, strlen(key_opt->secret));
		region_recycle(region, key_opt->secret,
			strlen(key_opt->secret)+1);
	}
	key_opt->secret = region_strdup(region, arg2);
	log_msg(LOG_INFO, "the key: %s has new secret %s and algorithm: %s\n", arg, key_opt->secret, key_opt->algorithm);
	/* wipe secret from temp parse buffer */
	memset(arg2, 0xdd, strlen(arg2));
	memset(data, 0xdd, sizeof(data));

	key_options_desetup(region, key_opt);
	key_options_setup(region, key_opt);
	task_new_add_key(xfrd->nsd->task[xfrd->nsd->mytask], xfrd->last_task,
		key_opt);
	xfrd_set_reload_now(xfrd);

	send_ok(ssl);
}

/** do the add tsig command, add new key with name, secret and algo given */
static void
do_add_tsig(RES* ssl, xfrd_state_type* xfrd, char* arg)
{
	char* arg2 = NULL;
	char* arg3 = NULL;
	uint8_t data[65536]; /* 64KB */
	uint8_t dname[MAXDOMAINLEN+1];
	char algo[256];
	region_type* region = xfrd->nsd->options->region;
	struct key_options* new_key_opt;

	if(*arg == '\0') {
		(void)ssl_printf(ssl, "error: missing argument (keyname)\n");
		return;
	}
	if(!find_arg3(ssl, arg, &arg2, &arg3)) {
		strlcpy(algo, "hmac-sha256", sizeof(algo));
	} else {
		strlcpy(algo, arg3, sizeof(algo));
	}
	if(!arg2) {
		(void)ssl_printf(ssl, "error: missing argument (secret)\n");
		return;
	}
	if(key_options_find(xfrd->nsd->options, arg)) {
		(void)ssl_printf(ssl, "error: key %s already exists\n", arg);
		memset(arg2, 0xdd, strlen(arg2));
		return;
	}
	if(__b64_pton(arg2, data, sizeof(data)) == -1) {
		(void)ssl_printf(ssl, "error: the secret: %s is not in b64 format\n", arg2);
		memset(data, 0xdd, sizeof(data)); /* wipe secret */
		memset(arg2, 0xdd, strlen(arg2));
		return;
	}
	memset(data, 0xdd, sizeof(data)); /* wipe secret from temp buffer */
	if(!dname_parse_wire(dname, arg)) {
		(void)ssl_printf(ssl, "error: could not parse key name: %s\n", arg);
		memset(arg2, 0xdd, strlen(arg2));
		return;
	}
	if(tsig_get_algorithm_by_name(algo) == NULL) {
		(void)ssl_printf(ssl, "error: unknown algorithm: %s\n", algo);
		memset(arg2, 0xdd, strlen(arg2));
		return;
	}
	log_msg(LOG_INFO, "adding key with name: %s and secret: %s with algo: %s\n", arg, arg2, algo);
	new_key_opt = key_options_create(region);
	new_key_opt->name = region_strdup(region, arg);
	new_key_opt->secret = region_strdup(region, arg2);
	new_key_opt->algorithm = region_strdup(region, algo);
	add_key(xfrd, new_key_opt);

	/* wipe secret from temp buffer */
	memset(arg2, 0xdd, strlen(arg2));
	send_ok(ssl);
}

/** set acl entries to use the given TSIG key */
static void
zopt_set_acl_to_tsig(struct acl_options* acl, struct region* region,
	const char* key_name, struct key_options* key_opt)
{
	while(acl) {
		if(acl->blocked) {
			acl = acl->next;
			continue;
		}
		acl->nokey = 0;
		if(acl->key_name)
			region_recycle(region, (void*)acl->key_name,
				strlen(acl->key_name)+1);
		acl->key_name = region_strdup(region, key_name);
		acl->key_options = key_opt;
		acl = acl->next;
	}
}

/** 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(*arg == '\0') {
		(void)ssl_printf(ssl, "error: missing argument (zonename)\n");
		return;
	}
	if(!find_arg2(ssl, arg, &arg2)) {
		(void)ssl_printf(ssl, "error: missing argument (keyname)\n");
		return;
	}

	if(!get_zone_arg(ssl, xfrd, arg, &zone))
		return;
	if(!zone) {
		(void)ssl_printf(ssl, "error: missing argument (zone)\n");
		return;
	}
	key_opt = key_options_find(xfrd->nsd->options, arg2);
	if(!key_opt) {
		(void)ssl_printf(ssl, "error: key: %s does not exist\n", arg2);
		return;
	}

	zopt_set_acl_to_tsig(zone->pattern->allow_notify, region, arg2,
		key_opt);
	zopt_set_acl_to_tsig(zone->pattern->notify, region, arg2, key_opt);
	zopt_set_acl_to_tsig(zone->pattern->request_xfr, region, arg2,
		key_opt);
	zopt_set_acl_to_tsig(zone->pattern->provide_xfr, region, arg2,
		key_opt);
	zopt_set_acl_to_tsig(zone->pattern->allow_query, region, arg2,
		key_opt);

	task_new_add_pattern(xfrd->nsd->task[xfrd->nsd->mytask],
		xfrd->last_task, zone->pattern);
	xfrd_set_reload_now(xfrd);

	send_ok(ssl);
}

/** see if TSIG key is used in the acl */
static int
acl_contains_tsig_key(struct acl_options* acl, const char* name)
{
	while(acl) {
		if(acl->key_name && strcmp(acl->key_name, name) == 0)
			return 1;
		acl = acl->next;
	}
	return 0;
}

/** do the del_tsig command, remove an (unused) tsig */
static void
do_del_tsig(RES* ssl, xfrd_state_type* xfrd, char* arg) {
	int used_key = 0;
	struct zone_options* zone;
	struct key_options* key_opt;

	if(*arg == '\0') {
		(void)ssl_printf(ssl, "error: missing argument (keyname)\n");
		return;
	}
	key_opt = key_options_find(xfrd->nsd->options, arg);
	if(!key_opt) {
		(void)ssl_printf(ssl, "key %s does not exist, nothing to be deleted\n", arg);
		return;
	}
	RBTREE_FOR(zone, struct zone_options*, xfrd->nsd->options->zone_options)
	{
		if(acl_contains_tsig_key(zone->pattern->allow_notify, arg) ||
		   acl_contains_tsig_key(zone->pattern->notify, arg) ||
		   acl_contains_tsig_key(zone->pattern->request_xfr, arg) ||
		   acl_contains_tsig_key(zone->pattern->provide_xfr, arg) ||
		   acl_contains_tsig_key(zone->pattern->allow_query, arg)) {
			if(!ssl_printf(ssl, "zone %s uses key %s\n",
				zone->name, arg))
				return;
			used_key = 1;
			break;
		}
	}

	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);
	}

	send_ok(ssl);
}

/* returns `0` on failure */
static int
cookie_secret_file_dump(RES* ssl, nsd_type const* nsd) {
	char const* secret_file = nsd->options->cookie_secret_file;
	char secret_hex[NSD_COOKIE_SECRET_SIZE * 2 + 1];
	FILE* f;
	size_t i;
	assert( secret_file != NULL );

	/* open write only and truncate */
	if((f = fopen(secret_file, "w")) == NULL ) {
		(void)ssl_printf(ssl, "unable to open cookie secret file %s: %s",
		                 secret_file, strerror(errno));
		return 0;
	}
	for(i = 0; i < nsd->cookie_count; i++) {
		struct cookie_secret const* cs = &nsd->cookie_secrets[i];
		ssize_t const len = hex_ntop(cs->cookie_secret, NSD_COOKIE_SECRET_SIZE,
			secret_hex, sizeof(secret_hex));
		(void)len; /* silence unused variable warning with -DNDEBUG */
		assert( len == NSD_COOKIE_SECRET_SIZE * 2 );
		secret_hex[NSD_COOKIE_SECRET_SIZE * 2] = '\0';
		fprintf(f, "%s\n", secret_hex);
	}
	explicit_bzero(secret_hex, sizeof(secret_hex));
	fclose(f);
	return 1;
}

static void
do_activate_cookie_secret(RES* ssl, xfrd_state_type* xrfd, char* arg) {
	nsd_type* nsd = xrfd->nsd;
	(void)arg;

	if(nsd->cookie_count <= 1 ) {
		(void)ssl_printf(ssl, "error: no staging cookie secret to activate\n");
		return;
	}
	if(!nsd->options->cookie_secret_file || !nsd->options->cookie_secret_file[0]) {
		(void)ssl_printf(ssl, "error: no cookie secret file configured\n");
		return;
	}
	if(!cookie_secret_file_dump(ssl, nsd)) {
		(void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
				nsd->options->cookie_secret_file);
		return;
	}
	activate_cookie_secret(nsd);
	(void)cookie_secret_file_dump(ssl, nsd);
	task_new_activate_cookie_secret(xfrd->nsd->task[xfrd->nsd->mytask],
	    xfrd->last_task);
	xfrd_set_reload_now(xfrd);
	send_ok(ssl);
}

static void
do_drop_cookie_secret(RES* ssl, xfrd_state_type* xrfd, char* arg) {
	nsd_type* nsd = xrfd->nsd;
	(void)arg;

	if(nsd->cookie_count <= 1 ) {
		(void)ssl_printf(ssl, "error: can not drop the currently active cookie secret\n");
		return;
	}
	if(!nsd->options->cookie_secret_file || !nsd->options->cookie_secret_file[0]) {
		(void)ssl_printf(ssl, "error: no cookie secret file configured\n");
		return;
	}
	if(!cookie_secret_file_dump(ssl, nsd)) {
		(void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
				nsd->options->cookie_secret_file);
		return;
	}
	drop_cookie_secret(nsd);
	(void)cookie_secret_file_dump(ssl, nsd);
	task_new_drop_cookie_secret(xfrd->nsd->task[xfrd->nsd->mytask],
	    xfrd->last_task);
	xfrd_set_reload_now(xfrd);
	send_ok(ssl);
}

static void
do_add_cookie_secret(RES* ssl, xfrd_state_type* xrfd, char* arg) {
	nsd_type* nsd = xrfd->nsd;
	uint8_t secret[NSD_COOKIE_SECRET_SIZE];

	if(*arg == '\0') {
		(void)ssl_printf(ssl, "error: missing argument (cookie_secret)\n");
		return;
	}
	if(strlen(arg) != 32) {
		explicit_bzero(arg, strlen(arg));
		(void)ssl_printf(ssl, "invalid cookie secret: invalid argument length\n");
		(void)ssl_printf(ssl, "please provide a 128bit hex encoded secret\n");
		return;
	}
	if(hex_pton(arg, secret, NSD_COOKIE_SECRET_SIZE) != NSD_COOKIE_SECRET_SIZE ) {
		explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE);
		explicit_bzero(arg, strlen(arg));
		(void)ssl_printf(ssl, "invalid cookie secret: parse error\n");
		(void)ssl_printf(ssl, "please provide a 128bit hex encoded secret\n");
		return;
	}
	if(!nsd->options->cookie_secret_file || !nsd->options->cookie_secret_file[0]) {
		explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE);
		explicit_bzero(arg, strlen(arg));
		(void)ssl_printf(ssl, "error: no cookie secret file configured\n");
		return;
	}
	if(!cookie_secret_file_dump(ssl, nsd)) {
		explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE);
		explicit_bzero(arg, strlen(arg));
		(void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
				nsd->options->cookie_secret_file);
		return;
	}
	add_cookie_secret(nsd, secret);
	explicit_bzero(secret, NSD_COOKIE_SECRET_SIZE);
	(void)cookie_secret_file_dump(ssl, nsd);
	task_new_add_cookie_secret(xfrd->nsd->task[xfrd->nsd->mytask],
	    xfrd->last_task, arg);
	explicit_bzero(arg, strlen(arg));
	xfrd_set_reload_now(xfrd);
	send_ok(ssl);
}

static void
do_print_cookie_secrets(RES* ssl, xfrd_state_type* xrfd, char* arg) {
	nsd_type* nsd = xrfd->nsd;
	char secret_hex[NSD_COOKIE_SECRET_SIZE * 2 + 1];
	int i;
	(void)arg;

	/* (void)ssl_printf(ssl, "cookie_secret_count=%zu\n", nsd->cookie_count); */
	for(i = 0; (size_t)i < nsd->cookie_count; i++) {
		struct cookie_secret const* cs = &nsd->cookie_secrets[i];
		ssize_t const len = hex_ntop(cs->cookie_secret, NSD_COOKIE_SECRET_SIZE,
		                             secret_hex, sizeof(secret_hex));
		(void)len; /* silence unused variable warning with -DNDEBUG */
		assert( len == NSD_COOKIE_SECRET_SIZE * 2 );
		secret_hex[NSD_COOKIE_SECRET_SIZE * 2] = '\0';
		if (i == 0)
			(void)ssl_printf(ssl, "active : %s\n",  secret_hex);
		else if (nsd->cookie_count == 2)
			(void)ssl_printf(ssl, "staging: %s\n",  secret_hex);
		else
			(void)ssl_printf(ssl, "staging[%d]: %s\n", i, secret_hex);
	}
	explicit_bzero(secret_hex, sizeof(secret_hex));
}

/** 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');
}

/** execute a remote control command */
static void
execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd)
{
	char* p = skipwhite(cmd);
	/* compare command */
	if(cmdcmp(p, "stop", 4)) {
		do_stop(ssl, rc->xfrd);
	} else if(cmdcmp(p, "reload", 6)) {
		do_reload(ssl, rc->xfrd, skipwhite(p+6));
	} else if(cmdcmp(p, "write", 5)) {
		do_write(ssl, rc->xfrd, skipwhite(p+5));
	} else if(cmdcmp(p, "status", 6)) {
		do_status(ssl, rc->xfrd);
	} else if(cmdcmp(p, "stats_noreset", 13)) {
		do_stats(ssl, rc->xfrd, 1);
	} else if(cmdcmp(p, "stats", 5)) {
		do_stats(ssl, rc->xfrd, 0);
	} else if(cmdcmp(p, "log_reopen", 10)) {
		do_log_reopen(ssl, rc->xfrd);
	} else if(cmdcmp(p, "addzone", 7)) {
		do_addzone(ssl, rc->xfrd, skipwhite(p+7));
	} else if(cmdcmp(p, "delzone", 7)) {
		do_delzone(ssl, rc->xfrd, skipwhite(p+7));
	} else if(cmdcmp(p, "changezone", 10)) {
		do_changezone(ssl, rc->xfrd, skipwhite(p+10));
	} else if(cmdcmp(p, "addzones", 8)) {
		do_addzones(ssl, rc->xfrd);
	} else if(cmdcmp(p, "delzones", 8)) {
		do_delzones(ssl, rc->xfrd);
	} else if(cmdcmp(p, "notify", 6)) {
		do_notify(ssl, rc->xfrd, skipwhite(p+6));
	} else if(cmdcmp(p, "transfer", 8)) {
		do_transfer(ssl, rc->xfrd, skipwhite(p+8));
	} else if(cmdcmp(p, "force_transfer", 14)) {
		do_force_transfer(ssl, rc->xfrd, skipwhite(p+14));
	} else if(cmdcmp(p, "zonestatus", 10)) {
		do_zonestatus(ssl, rc->xfrd, skipwhite(p+10));
	} else if(cmdcmp(p, "verbosity", 9)) {
		do_verbosity(ssl, skipwhite(p+9));
	} else if(cmdcmp(p, "repattern", 9)) {
		do_repattern(ssl, rc->xfrd);
	} else if(cmdcmp(p, "reconfig", 8)) {
		do_repattern(ssl, rc->xfrd);
	} else if(cmdcmp(p, "serverpid", 9)) {
		do_serverpid(ssl, rc->xfrd);
	} else if(cmdcmp(p, "print_tsig", 10)) {
		do_print_tsig(ssl, rc->xfrd, skipwhite(p+10));
	} else if(cmdcmp(p, "update_tsig", 11)) {
		do_update_tsig(ssl, rc->xfrd, skipwhite(p+11));
	} else if(cmdcmp(p, "add_tsig", 8)) {
		do_add_tsig(ssl, rc->xfrd, skipwhite(p+8));
	} else if(cmdcmp(p, "assoc_tsig", 10)) {
		do_assoc_tsig(ssl, rc->xfrd, skipwhite(p+10));
	} else if(cmdcmp(p, "del_tsig", 8)) {
		do_del_tsig(ssl, rc->xfrd, skipwhite(p+8));
	} else if(cmdcmp(p, "add_cookie_secret", 17)) {
		do_add_cookie_secret(ssl, rc->xfrd, skipwhite(p+17));
	} else if(cmdcmp(p, "drop_cookie_secret", 18)) {
		do_drop_cookie_secret(ssl, rc->xfrd, skipwhite(p+18));
	} else if(cmdcmp(p, "print_cookie_secrets", 20)) {
		do_print_cookie_secrets(ssl, rc->xfrd, skipwhite(p+20));
	} else if(cmdcmp(p, "activate_cookie_secret", 22)) {
		do_activate_cookie_secret(ssl, rc->xfrd, skipwhite(p+22));
	} else {
		(void)ssl_printf(ssl, "error unknown command '%s'\n", p);
	}
}

/** handle remote control request */
static void
handle_req(struct daemon_remote* rc, struct rc_state* s, RES* res)
{
	int r;
	char pre[10];
	char magic[8];
	char buf[1024];
	if (fcntl(s->c.ev_fd, F_SETFL, 0) == -1) { /* set blocking */
		log_msg(LOG_ERR, "cannot fcntl rc: %s", strerror(errno));
	}

	/* try to read magic UBCT[version]_space_ string */
#ifdef HAVE_SSL
	if(res->ssl) {
		ERR_clear_error();
		if((r=SSL_read(res->ssl, magic, (int)sizeof(magic)-1)) <= 0) {
			if(SSL_get_error(res->ssl, r) == SSL_ERROR_ZERO_RETURN)
				return;
			log_crypto_err("could not SSL_read");
			return;
		}
	} else {
#endif /* HAVE_SSL */
		while(1) {
			ssize_t rr = read(res->fd, magic, sizeof(magic)-1);
			if(rr <= 0) {
				if(rr == 0) return;
				if(errno == EINTR || errno == EAGAIN)
					continue;
				log_msg(LOG_ERR, "could not read: %s", strerror(errno));
				return;
			}
			r = (int)rr;
			break;
		}
#ifdef HAVE_SSL
	}
#endif /* HAVE_SSL */
	magic[7] = 0;
	if( r != 7 || strncmp(magic, "NSDCT", 5) != 0) {
		VERBOSITY(2, (LOG_INFO, "control connection has bad header"));
		/* probably wrong tool connected, ignore it completely */
		return;
	}

	/* read the command line */
	if(!ssl_read_line(res, buf, sizeof(buf))) {
		return;
	}
	snprintf(pre, sizeof(pre), "NSDCT%d ", NSD_CONTROL_VERSION);
	if(strcmp(magic, pre) != 0) {
		VERBOSITY(2, (LOG_INFO, "control connection had bad "
			"version %s, cmd: %s", magic, buf));
		(void)ssl_printf(res, "error version mismatch\n");
		return;
	}
	/* always log control commands */
	VERBOSITY(0, (LOG_INFO, "control cmd: %s", buf));

	/* figure out what to do */
	execute_cmd(rc, res, buf);
}

#ifdef HAVE_SSL
/** handle SSL_do_handshake changes to the file descriptor to wait for later */
static void
remote_handshake_later(struct daemon_remote* rc, struct rc_state* s, int fd,
	int r, int r2)
{
	if(r2 == SSL_ERROR_WANT_READ) {
		if(s->shake_state == rc_hs_read) {
			/* try again later */
			return;
		}
		s->shake_state = rc_hs_read;
		if(s->event_added)
			event_del(&s->c);
		memset(&s->c, 0, sizeof(s->c));
		event_set(&s->c, fd, EV_PERSIST|EV_TIMEOUT|EV_READ,
			remote_control_callback, s);
		if(event_base_set(xfrd->event_base, &s->c) != 0)
			log_msg(LOG_ERR, "remote_accept: cannot set event_base");
		if(event_add(&s->c, &s->tval) != 0)
			log_msg(LOG_ERR, "remote_accept: cannot add event");
		s->event_added = 1;
		return;
	} else if(r2 == SSL_ERROR_WANT_WRITE) {
		if(s->shake_state == rc_hs_write) {
			/* try again later */
			return;
		}
		s->shake_state = rc_hs_write;
		if(s->event_added)
			event_del(&s->c);
		memset(&s->c, 0, sizeof(s->c));
		event_set(&s->c, fd, EV_PERSIST|EV_TIMEOUT|EV_WRITE,
			remote_control_callback, s);
		if(event_base_set(xfrd->event_base, &s->c) != 0)
			log_msg(LOG_ERR, "remote_accept: cannot set event_base");
		if(event_add(&s->c, &s->tval) != 0)
			log_msg(LOG_ERR, "remote_accept: cannot add event");
		s->event_added = 1;
		return;
	} else {
		if(r == 0)
			log_msg(LOG_ERR, "remote control connection closed prematurely");
		log_crypto_err("remote control failed ssl");
		clean_point(rc, s);
	}
}
#endif /* HAVE_SSL */

static void
remote_control_callback(int fd, short event, void* arg)
{
	RES res;
	struct rc_state* s = (struct rc_state*)arg;
	struct daemon_remote* rc = s->rc;
	if( (event&EV_TIMEOUT) ) {
		log_msg(LOG_ERR, "remote control timed out");
		clean_point(rc, s);
		return;
	}
#ifdef HAVE_SSL
	if(s->ssl) {
		/* (continue to) setup the SSL connection */
		int r;
		ERR_clear_error();
		r = SSL_do_handshake(s->ssl);
		if(r != 1) {
			int r2 = SSL_get_error(s->ssl, r);
			remote_handshake_later(rc, s, fd, r, r2);
			return;
		}
		s->shake_state = rc_none;
	}
#endif /* HAVE_SSL */

	/* once handshake has completed, check authentication */
	if (!rc->use_cert) {
		VERBOSITY(3, (LOG_INFO, "unauthenticated remote control connection"));
#ifdef HAVE_SSL
	} else if(SSL_get_verify_result(s->ssl) == X509_V_OK) {
		X509* x = SSL_get_peer_certificate(s->ssl);
		if(!x) {
			VERBOSITY(2, (LOG_INFO, "remote control connection "
				"provided no client certificate"));
			clean_point(rc, s);
			return;
		}
		VERBOSITY(3, (LOG_INFO, "remote control connection authenticated"));
		X509_free(x);
#endif /* HAVE_SSL */
	} else {
		VERBOSITY(2, (LOG_INFO, "remote control connection failed to "
			"authenticate with client certificate"));
		clean_point(rc, s);
		return;
	}

	/* if OK start to actually handle the request */
#ifdef HAVE_SSL
	res.ssl = s->ssl;
#endif /* HAVE_SSL */
	res.fd = fd;
	handle_req(rc, s, &res);

	VERBOSITY(3, (LOG_INFO, "remote control operation completed"));
	clean_point(rc, s);
}

#ifdef BIND8_STATS
static const char*
opcode2str(int o)
{
	switch(o) {
		case OPCODE_QUERY: return "QUERY";
		case OPCODE_IQUERY: return "IQUERY";
		case OPCODE_STATUS: return "STATUS";
		case OPCODE_NOTIFY: return "NOTIFY";
		case OPCODE_UPDATE: return "UPDATE";
		default: return "OTHER";
	}
}

/** print long number */
static int
print_longnum(RES* ssl, char* desc, uint64_t x)
{
	if(x > (uint64_t)1024*1024*1024) {
		/* more than a Gb */
		size_t front = (size_t)(x / (uint64_t)1000000);
		size_t back = (size_t)(x % (uint64_t)1000000);
		return ssl_printf(ssl, "%s%lu%6.6lu\n", desc, 
			(unsigned long)front, (unsigned long)back);
	} else {
		return ssl_printf(ssl, "%s%lu\n", desc, (unsigned long)x);
	}
}

/* print one block of statistics.  n is name and d is delimiter */
static void
print_stat_block(RES* ssl, char* n, char* d, struct nsdst* st)
{
	const char* rcstr[] = {"NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN",
	    "NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET", "NXRRSET", "NOTAUTH",
	    "NOTZONE", "RCODE11", "RCODE12", "RCODE13", "RCODE14", "RCODE15",
	    "BADVERS"
	};
	size_t i;
	for(i=0; i<= 255; i++) {
		if(inhibit_zero && st->qtype[i] == 0 &&
			strncmp(rrtype_to_string(i), "TYPE", 4) == 0)
			continue;
		if(!ssl_printf(ssl, "%s%snum.type.%s=%lu\n", n, d,
			rrtype_to_string(i), (unsigned long)st->qtype[i]))
			return;
	}

	/* opcode */
	for(i=0; i<6; i++) {
		if(inhibit_zero && st->opcode[i] == 0 && i != OPCODE_QUERY)
			continue;
		if(!ssl_printf(ssl, "%s%snum.opcode.%s=%lu\n", n, d,
			opcode2str(i), (unsigned long)st->opcode[i]))
			return;
	}

	/* qclass */
	for(i=0; i<4; i++) {
		if(inhibit_zero && st->qclass[i] == 0 && i != CLASS_IN)
			continue;
		if(!ssl_printf(ssl, "%s%snum.class.%s=%lu\n", n, d,
			rrclass_to_string(i), (unsigned long)st->qclass[i]))
			return;
	}

	/* 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;

	/* dropped */
	if(!ssl_printf(ssl, "%s%snum.dropped=%lu\n", n, d,
		(unsigned long)st->dropped))
		return;
}

#ifdef USE_ZONE_STATS
static void
resize_zonestat(xfrd_state_type* xfrd, size_t num)
{
	struct nsdst** a = xalloc_array_zero(num, sizeof(struct nsdst*));
	if(xfrd->zonestat_clear_num != 0)
		memcpy(a, xfrd->zonestat_clear, xfrd->zonestat_clear_num
			* sizeof(struct nsdst*));
	free(xfrd->zonestat_clear);
	xfrd->zonestat_clear = a;
	xfrd->zonestat_clear_num = num;
}

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 */

static void
print_stats(RES* ssl, xfrd_state_type* xfrd, struct timeval* now, int clear,
	struct nsdst* st, struct nsdst** zonestats)
{
	size_t i;
	stc_type total = 0;
	struct timeval elapsed, uptime;

	/* 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;

	/* mem info, database on disksize */
	if(!print_longnum(ssl, "size.db.disk=", st->db_disk))
		return;
	if(!print_longnum(ssl, "size.db.mem=", st->db_mem))
		return;
	if(!print_longnum(ssl, "size.xfrd.mem=", region_get_mem(xfrd->region)))
		return;
	if(!print_longnum(ssl, "size.config.disk=", 
		xfrd->nsd->options->zonelist_off))
		return;
	if(!print_longnum(ssl, "size.config.mem=", region_get_mem(
		xfrd->nsd->options->region)))
		return;
	print_stat_block(ssl, "", "", st);

	/* zone statistics */
	if(!ssl_printf(ssl, "zone.primary=%lu\n",
		(unsigned long)(xfrd->notify_zones->count - xfrd->zones->count)))
		return;
	if(!ssl_printf(ssl, "zone.secondary=%lu\n", (unsigned long)xfrd->zones->count))
		return;
	if(!ssl_printf(ssl, "zone.master=%lu\n",
		(unsigned long)(xfrd->notify_zones->count - xfrd->zones->count)))
		return;
	if(!ssl_printf(ssl, "zone.slave=%lu\n", (unsigned long)xfrd->zones->count))
		return;
#ifdef USE_ZONE_STATS
	zonestat_print(ssl, xfrd, clear, zonestats); /* per-zone statistics */
#else
	(void)clear; (void)zonestats;
#endif
}

/* allocate stats temp arrays, for taking a coherent snapshot of the
 * statistics values at that time. */
static void
process_stats_alloc(xfrd_state_type* xfrd, struct nsdst** stats,
	struct nsdst** zonestats)
{
	*stats = xmallocarray(xfrd->nsd->child_count*2, sizeof(struct nsdst));
#ifdef USE_ZONE_STATS
	zonestats[0] = xmallocarray(xfrd->zonestat_safe, sizeof(struct nsdst));
	zonestats[1] = xmallocarray(xfrd->zonestat_safe, sizeof(struct nsdst));
#else
	(void)zonestats;
#endif
}

/* grab a copy of the statistics, at this particular time. */
static void
process_stats_grab(xfrd_state_type* xfrd, struct timeval* stattime,
	struct nsdst* stats, struct nsdst** zonestats)
{
	if(gettimeofday(stattime, NULL) == -1)
		log_msg(LOG_ERR, "gettimeofday: %s", strerror(errno));
	memcpy(stats, xfrd->nsd->stat_map,
		xfrd->nsd->child_count*2*sizeof(struct nsdst));
#ifdef USE_ZONE_STATS
	memcpy(zonestats[0], xfrd->nsd->zonestat[0],
		xfrd->zonestat_safe*sizeof(struct nsdst));
	memcpy(zonestats[1], xfrd->nsd->zonestat[1],
		xfrd->zonestat_safe*sizeof(struct nsdst));
#else
	(void)zonestats;
#endif
}

/* 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;

	process_stats_alloc(xfrd, &stats, zonestats);
	process_stats_grab(xfrd, &stattime, stats, zonestats);
	process_stats_add_old_new(xfrd, stats);
	process_stats_manage_clear(xfrd, stats, peek);
	process_stats_add_total(xfrd, &total, stats);
	print_stats(ssl, xfrd, &stattime, !peek, &total, zonestats);
	if(!peek) {
		xfrd->nsd->rc->stats_time = stattime;
	}

	free(stats);
#ifdef USE_ZONE_STATS
	free(zonestats[0]);
	free(zonestats[1]);
#endif

	VERBOSITY(3, (LOG_INFO, "remote control stats printed"));
}
#endif /* BIND8_STATS */

int
create_local_accept_sock(const char *path, int* noproto)
{
#ifdef HAVE_SYS_UN_H
	int s;
	struct sockaddr_un usock;

	VERBOSITY(3, (LOG_INFO, "creating unix socket %s", path));
#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
	/* this member exists on BSDs, not Linux */
	usock.sun_len = (unsigned)sizeof(usock);
#endif
	usock.sun_family = AF_LOCAL;
	/* length is 92-108, 104 on FreeBSD */
	(void)strlcpy(usock.sun_path, path, sizeof(usock.sun_path));

	if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) {
		log_msg(LOG_ERR, "Cannot create local socket %s (%s)",
			path, strerror(errno));
		return -1;
	}

	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;
	}

	if (bind(s, (struct sockaddr *)&usock,
		(socklen_t)sizeof(struct sockaddr_un)) == -1) {
		log_msg(LOG_ERR, "Cannot bind local socket %s (%s)",
			path, strerror(errno));
		goto err;
	}

	if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
		log_msg(LOG_ERR, "Cannot set non-blocking mode");
		goto err;
	}

	if (listen(s, TCP_BACKLOG) == -1) {
		log_msg(LOG_ERR, "can't listen: %s", strerror(errno));
		goto err;
	}

	(void)noproto; /*unused*/
	return s;

err:
	close(s);
	return -1;

#else
	(void)path;
	log_msg(LOG_ERR, "Local sockets are not supported");
	*noproto = 1;
	return -1;
#endif
}