/*
 * 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 REGENTS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * \file
 *
 * This file contains the remote control functionality for the daemon.
 * The remote control can be performed using either the commandline
 * nsd-control tool, or a SSLv3/TLS capable web browser. 
 * The channel is secured using SSLv3 or 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
#include <ctype.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#ifndef USE_MINI_EVENT
#include <event.h>
#else
#include "mini_event.h"
#endif
#include "remote.h"
#include "util.h"
#include "xfrd.h"
#include "xfrd-notify.h"
#include "xfrd-tcp.h"
#include "nsd.h"
#include "options.h"
#include "difffile.h"
#include "xfrd.h"
#include "ipc.h"

#ifdef HAVE_SYS_TYPES_H
#  include <sys/types.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#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;
	/** the ssl state */
	SSL* ssl;
	/** the rc this is part of */
	struct daemon_remote* rc;
	/** stats list next item */
	struct rc_state* stats_next;
	/** stats list indicator (0 is not part of stats list, 1 is stats,
	 * 2 is stats_noreset. */
	int in_stats_list;
};

/**
 * list of events for accepting connections
 */
struct acceptlist {
	struct acceptlist* next;
	int event_added;
	struct event c;
};

/**
 * 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;
	/** 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;
	/** commpoints waiting for stats to complete (also in busy_list) */
	struct rc_state* stats_list;
	/** last time stats was reported */
	struct timeval stats_time, boot_time;
	/** the SSL context for creating new SSL streams */
	SSL_CTX* ctx;
};

/** 
 * Print fixed line of text over ssl connection in blocking mode
 * @param ssl: print to
 * @param text: the text.
 * @return false on connection failure.
 */
static int ssl_print_text(SSL* ssl, const char* text);

/** 
 * printf style printing to the ssl connection
 * @param ssl: the SSL connection to print to. Blocking.
 * @param format: printf style format string.
 * @return success or false on a network failure.
 */
static int ssl_printf(SSL* ssl, const char* format, ...)
        ATTR_FORMAT(printf, 2, 3);

/**
 * Read until \n is encountered
 * If SSL signals EOF, the string up to then is returned (without \n).
 * @param ssl: the SSL 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(SSL* ssl, 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);


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


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

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

struct daemon_remote*
daemon_remote_create(nsd_options_t* cfg)
{
	char* s_cert;
	char* s_key;
	struct daemon_remote* rc = (struct daemon_remote*)xalloc_zero(
		sizeof(*rc));
	rc->max_active = 10;
	assert(cfg->control_enable);

	/* init SSL library */
	ERR_load_crypto_strings();
	ERR_load_SSL_strings();
	OpenSSL_add_all_algorithms();
	(void)SSL_library_init();

	if(!RAND_status()) {
		/* try to seed it */
		unsigned char buf[256];
		unsigned int v, seed=(unsigned)time(NULL) ^ (unsigned)getpid();
		size_t i;
		v = seed;
		for(i=0; i<256/sizeof(v); i++) {
			memmove(buf+i*sizeof(v), &v, sizeof(v));
			v = v*seed + (unsigned int)i;
		}
		RAND_seed(buf, 256);
		log_msg(LOG_WARNING, "warning: no entropy, seeding openssl PRNG with time");
	}

	rc->ctx = SSL_CTX_new(SSLv23_server_method());
	if(!rc->ctx) {
		log_crypto_err("could not SSL_CTX_new");
		free(rc);
		return NULL;
	}
	/* no SSLv2 because has defects */
	if(!(SSL_CTX_set_options(rc->ctx, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2)){
		log_crypto_err("could not set SSL_OP_NO_SSLv2");
		daemon_remote_delete(rc);
		return NULL;
	}
	s_cert = cfg->server_cert_file;
	s_key = cfg->server_key_file;
	VERBOSITY(2, (LOG_INFO, "setup SSL certificates"));
	if (!SSL_CTX_use_certificate_file(rc->ctx,s_cert,SSL_FILETYPE_PEM)) {
		log_msg(LOG_ERR, "Error for server-cert-file: %s", s_cert);
		log_crypto_err("Error in SSL_CTX use_certificate_file");
		goto setup_error;
	}
	if(!SSL_CTX_use_PrivateKey_file(rc->ctx,s_key,SSL_FILETYPE_PEM)) {
		log_msg(LOG_ERR, "Error for server-key-file: %s", s_key);
		log_crypto_err("Error in SSL_CTX use_PrivateKey_file");
		goto setup_error;
	}
	if(!SSL_CTX_check_private_key(rc->ctx)) {
		log_msg(LOG_ERR, "Error for server-key-file: %s", s_key);
		log_crypto_err("Error in SSL_CTX check_private_key");
		goto setup_error;
	}
	if(!SSL_CTX_load_verify_locations(rc->ctx, s_cert, NULL)) {
		log_crypto_err("Error setting up SSL_CTX verify locations");
	setup_error:
		daemon_remote_delete(rc);
		return NULL;
	}
	SSL_CTX_set_client_CA_list(rc->ctx, SSL_load_client_CA_file(s_cert));
	SSL_CTX_set_verify(rc->ctx, SSL_VERIFY_PEER, NULL);

	/* and try to open the ports */
	if(!daemon_remote_open_ports(rc, cfg)) {
		log_msg(LOG_ERR, "could not open remote control port");
		goto setup_error;
	}

	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);
		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);
		if(p->ssl)
			SSL_free(p->ssl);
		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);
	if(rc->ctx) {
		SSL_CTX_free(rc->ctx);
	}
	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));
		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));
		return -1;
	}
	/* Listen to it... */
	if (listen(s, TCP_BACKLOG_REMOTE) == -1) {
		log_msg(LOG_ERR, "can't listen: %s", strerror(errno));
		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, const char* ip, int nr, int noproto_is_err)
{
	struct addrinfo hints;
	struct addrinfo* res;
	struct acceptlist* hl;
	int noproto;
	int fd, r;
	char port[15];
	snprintf(port, sizeof(port), "%d", nr);
	port[sizeof(port)-1]=0;
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
	if((r = getaddrinfo(ip, port, &hints, &res)) != 0 || !res) {
                log_msg(LOG_ERR, "control interface %s:%s getaddrinfo: %s %s",
			ip?ip:"default", 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->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, nsd_options_t* cfg)
{
	assert(cfg->control_enable && cfg->control_port);
	if(cfg->control_interface) {
		ip_address_option_t* p;
		for(p = cfg->control_interface; p; p = p->next) {
			if(!add_open(rc, p->address, cfg->control_port, 1)) {
				return 0;
			}
		}
	} else {
		/* defaults */
		if(cfg->do_ip6 && !add_open(rc, "::1", cfg->control_port, 0)) {
			return 0;
		}
		if(cfg->do_ip4 &&
			!add_open(rc, "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;
		event_set(&p->c, fd, EV_PERSIST|EV_READ, remote_accept_callback,
			rc);
		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 daemon_remote *rc = (struct daemon_remote*)arg;
	struct sockaddr_storage addr;
	socklen_t addrlen;
	int newfd;
	struct rc_state* n;

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

	/* perform the accept */
	addrlen = sizeof(addr);
	newfd = accept(fd, (struct sockaddr*)&addr, &addrlen);
	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;
	}
	if (fcntl(newfd, F_SETFL, O_NONBLOCK) == -1) {
		log_msg(LOG_ERR, "fcntl failed: %s", strerror(errno));
		goto close_exit;
	}

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

	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");
	if(event_add(&n->c, &n->tval) != 0)
		log_msg(LOG_ERR, "remote_accept: cannot add event");
	n->event_added = 1;

	if(2 <= verbosity) {
		char s[128];
		addr2str(&addr, s, sizeof(s));
		VERBOSITY(2, (LOG_INFO, "new control connection from %s", s));
	}

	n->shake_state = rc_hs_read;
	n->ssl = SSL_new(rc->ctx);
	if(!n->ssl) {
		log_crypto_err("could not SSL_new");
		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");
		event_del(&n->c);
		SSL_free(n->ssl);
		free(n);
		goto close_exit;
	}

	n->rc = rc;
	n->stats_next = NULL;
	n->in_stats_list = 0;
	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;
}

/** delete from stats list */
static void
stats_list_remove_elem(struct rc_state** list, struct rc_state* todel)
{
	while(*list) {
		if( (*list) == todel) {
			*list = (*list)->stats_next;
			return;
		}
		list = &(*list)->stats_next;
	}
}

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

static int
ssl_print_text(SSL* ssl, const char* text)
{
	int r;
	if(!ssl) 
		return 0;
	ERR_clear_error();
	if((r=SSL_write(ssl, text, (int)strlen(text))) <= 0) {
		if(SSL_get_error(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;
	}
	return 1;
}

/** print text over the ssl connection */
static int
ssl_print_vmsg(SSL* 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(SSL* 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(SSL* ssl, char* buf, size_t max)
{
	int r;
	size_t len = 0;
	if(!ssl)
		return 0;
	while(len < max) {
		ERR_clear_error();
		if((r=SSL_read(ssl, buf+len, 1)) <= 0) {
			if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) {
				buf[len] = 0;
				return 1;
			}
			log_crypto_err("could not SSL_read");
			return 0;
		}
		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(*str) ) 
		str++;
	return str;
}

/** send the OK to the control client */
static void
send_ok(SSL* ssl)
{
	(void)ssl_printf(ssl, "ok\n");
}

/** get zone argument (if any) or NULL, false on error */
static int
get_zone_arg(SSL* ssl, xfrd_state_t* xfrd, char* arg,
	zone_options_t** 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) {
		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) {
		ssl_printf(ssl, "error zone %s not configured\n", arg);
		return 0;
	}
	return 1;
}

/** do the stop command */
static void
do_stop(SSL* ssl, xfrd_state_t* 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(SSL* ssl, xfrd_state_t* xfrd)
{
	xfrd_set_reload_now(xfrd);
	send_ok(ssl);
}

/** do the reload command */
static void
do_reload(SSL* ssl, xfrd_state_t* xfrd, char* arg)
{
	zone_options_t* 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(SSL* ssl, xfrd_state_t* xfrd, char* arg)
{
	zone_options_t* 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(SSL* ssl, xfrd_state_t* xfrd, char* arg)
{
	zone_options_t* zo;
	if(!get_zone_arg(ssl, xfrd, arg, &zo))
		return;
	if(zo) {
		struct notify_zone_t* n = (struct notify_zone_t*)rbtree_search(
			xfrd->notify_zones, (const dname_type*)zo->node.key);
		if(n) {
			xfrd_notify_start(n);
			send_ok(ssl);
		} else {
			ssl_printf(ssl, "error zone does not have notify\n");
		}
	} else {
		struct notify_zone_t* n;
		RBTREE_FOR(n, struct notify_zone_t*, xfrd->notify_zones) {
			xfrd_notify_start(n);
		}
		send_ok(ssl);
	}
}

/** do the transfer command */
static void
do_transfer(SSL* ssl, xfrd_state_t* xfrd, char* arg)
{
	zone_options_t* zo;
	xfrd_zone_t* zone;
	if(!get_zone_arg(ssl, xfrd, arg, &zo))
		return;
	if(zo) {
		zone = (xfrd_zone_t*)rbtree_search(xfrd->zones, (const
			dname_type*)zo->node.key);
		if(zone) {
			xfrd_handle_notify_and_start_xfr(zone, NULL);
			send_ok(ssl);
		} else {
			ssl_printf(ssl, "error zone not slave\n");
		}
	} else {
		RBTREE_FOR(zone, xfrd_zone_t*, xfrd->zones) {
			xfrd_handle_notify_and_start_xfr(zone, NULL);
		}
		ssl_printf(ssl, "ok, %u zones\n", (unsigned)xfrd->zones->count);
	}
}

/** force transfer a zone */
static void
force_transfer_zone(xfrd_zone_t* 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;
	xfrd_handle_notify_and_start_xfr(zone, NULL);
}

/** do the force transfer command */
static void
do_force_transfer(SSL* ssl, xfrd_state_t* xfrd, char* arg)
{
	zone_options_t* zo;
	xfrd_zone_t* zone;
	if(!get_zone_arg(ssl, xfrd, arg, &zo))
		return;
	if(zo) {
		zone = (xfrd_zone_t*)rbtree_search(xfrd->zones, (const
			dname_type*)zo->node.key);
		if(zone) {
			force_transfer_zone(zone);
			send_ok(ssl);
		} else {
			ssl_printf(ssl, "error zone not slave\n");
		}
	} else {
		RBTREE_FOR(zone, xfrd_zone_t*, xfrd->zones) {
			force_transfer_zone(zone);
		}
		ssl_printf(ssl, "ok, %u zones\n", (unsigned)xfrd->zones->count);
	}
}

static int
print_soa_status(SSL* ssl, const char* str, xfrd_soa_t* 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(SSL* ssl, xfrd_state_t* xfrd, zone_options_t* zo)
{
	xfrd_zone_t* xz = (xfrd_zone_t*)rbtree_search(xfrd->zones,
		(const dname_type*)zo->node.key);
	struct notify_zone_t* nz = (struct notify_zone_t*)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(nz) {
		if(nz->is_waiting) {
			if(!ssl_printf(ssl, "	notify: \"waiting-for-fd\"\n"))
				return 0;
		} else if(nz->notify_send_enable) {
			if(!ssl_printf(ssl, "	notify: \"sent try %d "
				"to %s with serial %u\"\n", nz->notify_retry,
				nz->notify_current->ip_address_spec,
				(unsigned)ntohl(nz->current_soa->serial)))
				return 0;
		}
	}
	if(!xz) {
		if(!ssl_printf(ssl, "	state: master\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;
	}

	/* 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(SSL* ssl, xfrd_state_t* xfrd, char* arg)
{
	zone_options_t* zo;
	if(!get_zone_arg(ssl, xfrd, arg, &zo))
		return;
	if(zo) (void)print_zonestatus(ssl, xfrd, zo);
	else {
		RBTREE_FOR(zo, zone_options_t*,
			xfrd->nsd->options->zone_options) {
			if(!print_zonestatus(ssl, xfrd, zo))
				return;
		}
	}
}

/** do the verbosity command */
static void
do_verbosity(SSL* ssl, char* str)
{
	int val = atoi(str);
	if(strcmp(str, "") == 0) {
		ssl_printf(ssl, "verbosity %d\n", verbosity);
		return;
	}
	if(val == 0 && strcmp(str, "0") != 0) {
		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(SSL* ssl, char* arg, char** arg2)
{
	char* as = strrchr(arg, ' ');
	if(as) {
		as[0]=0;
		*arg2 = as+1;
		while(isspace(*as) && as > arg)
			as--;
		as[0]=0;
		return 1;
	}
	ssl_printf(ssl, "error could not find next argument "
		"after %s\n", arg);
	return 0;
}

/** do the status command */
static void
do_status(SSL* ssl, xfrd_state_t* 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(struct daemon_remote* rc, int peek, struct rc_state* rs)
{
#ifdef BIND8_STATS
	/* queue up to get stats after a reload is done (to gather statistics
	 * from the servers) */
	assert(!rs->in_stats_list);
	if(peek) rs->in_stats_list = 2;
	else	rs->in_stats_list = 1;
	rs->stats_next = rc->stats_list;
	rc->stats_list = rs;
	/* block the tcp waiting for the reload */
	event_del(&rs->c);
	rs->event_added = 0;
	/* force a reload */
	xfrd_set_reload_now(xfrd);
#else
	(void)rc; (void)peek;
	(void)ssl_printf(rs->ssl, "error no stats enabled at compile time\n");
#endif /* BIND8_STATS */
}

/** do the addzone command */
static void
do_addzone(SSL* ssl, xfrd_state_t* xfrd, char* arg)
{
	zone_options_t* zopt;
	char* arg2 = NULL;
	if(!find_arg2(ssl, arg, &arg2))
		return;

	/* 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 does not exist\n");
		return;
	}

	/* 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;
	}
	/* make addzone task and schedule reload */
	task_new_add_zone(xfrd->nsd->task[xfrd->nsd->mytask],
		xfrd->last_task, arg, arg2);
	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);
	}

	send_ok(ssl);
}

/** do the delzone command */
static void
do_delzone(SSL* ssl, xfrd_state_t* xfrd, char* arg)
{
	const dname_type* dname;
	zone_options_t* zopt;

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

	/* 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 */
		if(!ssl_printf(ssl, "warning zone %s not present\n", arg))
			return;
		send_ok(ssl);
		return;
	}

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

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

	region_recycle(xfrd->region, (void*)dname, dname_total_size(dname));
	send_ok(ssl);
}

/** remove TSIG key from config and add task so that reload does too */
static void remove_key(xfrd_state_t* 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_t* xfrd, key_options_t* 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_t* xfrd, nsd_options_t* newopt)
{
	nsd_options_t* oldopt = xfrd->nsd->options;
	key_options_t* k;
	/* find deleted keys */
	k = (key_options_t*)rbtree_first(oldopt->keys);
	while((rbnode_t*)k != RBTREE_NULL) {
		key_options_t* next = (key_options_t*)rbtree_next(
			(rbnode_t*)k);
		if(!key_options_find(newopt, k->name))
			remove_key(xfrd, k->name);
		k = next;
	}
	/* find added or changed keys */
	RBTREE_FOR(k, key_options_t*, newopt->keys) {
		key_options_t* 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_t* 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_t* xfrd, const char* pname)
{
	/* dname and find the zone for the implicit pattern */
	zone_options_t* 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 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_t* xfrd, const char* pname)
{
	/* add to our zonelist */
	zone_options_t* 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);
	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_t* 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_t* xfrd, pattern_options_t* 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_t* xfrd, nsd_options_t* newopt)
{
	/* if masterlist changed:
	 *   interrupt slave zone (UDP or TCP) transfers.
	 *   slave zones reset master to start of list.
	 */
	xfrd_zone_t* xz;
	struct notify_zone_t* nz;
	RBTREE_FOR(xz, xfrd_zone_t*, xfrd->zones) {
		pattern_options_t* oldp = xz->zone_options->pattern;
		pattern_options_t* 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 = 0; /* 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_t*, xfrd->notify_zones) {
		pattern_options_t* oldp = nz->options->pattern;
		pattern_options_t* 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_t* xfrd)
{
	struct notify_zone_t* nz;
	RBTREE_FOR(nz, struct notify_zone_t*, 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);
			}
		}
	}
}

/** check if patterns have changed */
static void
repat_patterns(xfrd_state_t* xfrd, nsd_options_t* 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).
	 */
	nsd_options_t* oldopt = xfrd->nsd->options;
	pattern_options_t* p;
	int search_zones = 0;

	repat_interrupt_zones(xfrd, newopt);
	/* find deleted patterns */
	p = (pattern_options_t*)rbtree_first(oldopt->patterns);
	while((rbnode_t*)p != RBTREE_NULL) {
		pattern_options_t* next = (pattern_options_t*)rbtree_next(
			(rbnode_t*)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, pattern_options_t*, newopt->patterns) {
		pattern_options_t* 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) {
						zone_options_t* 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) {
		zone_options_t* 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, zone_options_t*, oldopt->zone_options) {
			pattern_options_t* 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_t* xfrd, nsd_options_t* 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_t* xfrd, nsd_options_t* 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)
{
	SSL** ssl = (SSL**)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(SSL* ssl, xfrd_state_t* xfrd)
{
	region_type* region = region_create(xalloc, free);
	nsd_options_t* 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) {
			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;
	}

	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);
	send_ok(ssl);
	region_destroy(region);
}

/** do the serverpid command: printout pid of server process */
static void
do_serverpid(SSL* ssl, xfrd_state_t* xfrd)
{
	(void)ssl_printf(ssl, "%u\n", (unsigned)xfrd->reload_pid);
}

/** 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, SSL* ssl, char* cmd, struct rc_state* rs)
{
	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(rc, 1, rs);
	} else if(cmdcmp(p, "stats", 5)) {
		do_stats(rc, 0, rs);
	} 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, "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 {
		(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, SSL* ssl)
{
	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 */
	ERR_clear_error();
	if((r=SSL_read(ssl, magic, (int)sizeof(magic)-1)) <= 0) {
		if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN)
			return;
		log_crypto_err("could not SSL_read");
		return;
	}
	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(ssl, 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));
		ssl_printf(ssl, "error version mismatch\n");
		return;
	}
	VERBOSITY(2, (LOG_INFO, "control cmd: %s", buf));

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

static void
remote_control_callback(int fd, short event, void* arg)
{
	struct rc_state* s = (struct rc_state*)arg;
	struct daemon_remote* rc = s->rc;
	int r;
	if( (event&EV_TIMEOUT) ) {
		log_msg(LOG_ERR, "remote control timed out");
		clean_point(rc, s);
		return;
	}
	/* (continue to) setup the SSL connection */
	ERR_clear_error();
	r = SSL_do_handshake(s->ssl);
	if(r != 1) {
		int r2 = SSL_get_error(s->ssl, r);
		if(r2 == SSL_ERROR_WANT_READ) {
			if(s->shake_state == rc_hs_read) {
				/* try again later */
				return;
			}
			s->shake_state = rc_hs_read;
			event_del(&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");
			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;
			event_del(&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");
			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);
			return;
		}
	}
	s->shake_state = rc_none;

	/* once handshake has completed, check authentication */
	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);
	} 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 */
	handle_req(rc, s, s->ssl);

	if(!s->in_stats_list) {
		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(SSL* 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%u%6.6u\n", desc, 
			(unsigned)front, (unsigned)back);
	} else {
		return ssl_printf(ssl, "%s%u\n", desc, (unsigned)x);
	}
}

static void
print_stats(SSL* ssl, xfrd_state_t* xfrd, struct timeval* now)
{
	const char* rcstr[] = {"NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN",
	    "NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET", "NXRRSET", "NOTAUTH",
	    "NOTZONE", "RCODE11", "RCODE12", "RCODE13", "RCODE14", "RCODE15",
	    "BADVERS"
	};
	size_t i;
	stc_t 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=%u\n", (int)i,
			(unsigned)xfrd->nsd->children[i].query_count))
			return;
		total += xfrd->nsd->children[i].query_count;
	}
	if(!ssl_printf(ssl, "num.queries=%u\n", (unsigned)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=%u.%6.6u\n",
		(unsigned)uptime.tv_sec, (unsigned)uptime.tv_usec))
		return;
	if(!ssl_printf(ssl, "time.elapsed=%u.%6.6u\n",
		(unsigned)elapsed.tv_sec, (unsigned)elapsed.tv_usec))
		return;

	/* mem info, database on disksize */
	if(!print_longnum(ssl, "size.db.disk=", xfrd->nsd->st.db_disk))
		return;
	if(!print_longnum(ssl, "size.db.mem=", xfrd->nsd->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;

	for(i=0; i<= 255; i++) {
		if(inhibit_zero && xfrd->nsd->st.qtype[i] == 0 &&
			strncmp(rrtype_to_string(i), "TYPE", 4) == 0)
			continue;
		if(!ssl_printf(ssl, "num.type.%s=%u\n", 
			rrtype_to_string(i), (unsigned)xfrd->nsd->st.qtype[i]))
			return;
	}

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

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

	/* rcode */
	for(i=0; i<17; i++) {
		if(inhibit_zero && xfrd->nsd->st.rcode[i] == 0 &&
			i > RCODE_YXDOMAIN) /* NSD does not use larger */
			continue;
		if(!ssl_printf(ssl, "num.rcode.%s=%u\n", rcstr[i],
			(unsigned)xfrd->nsd->st.rcode[i]))
			return;
	}

	/* edns */
	if(!ssl_printf(ssl, "num.edns=%u\n", (unsigned)xfrd->nsd->st.edns))
		return;

	/* ednserr */
	if(!ssl_printf(ssl, "num.ednserr=%u\n",
		(unsigned)xfrd->nsd->st.ednserr))
		return;

	/* qudp */
	if(!ssl_printf(ssl, "num.udp=%u\n", (unsigned)xfrd->nsd->st.qudp))
		return;
	/* qudp6 */
	if(!ssl_printf(ssl, "num.udp6=%u\n", (unsigned)xfrd->nsd->st.qudp6))
		return;
	/* ctcp */
	if(!ssl_printf(ssl, "num.tcp=%u\n", (unsigned)xfrd->nsd->st.ctcp))
		return;
	/* ctcp6 */
	if(!ssl_printf(ssl, "num.tcp6=%u\n", (unsigned)xfrd->nsd->st.ctcp6))
		return;

	/* nona */
	if(!ssl_printf(ssl, "num.answer_wo_aa=%u\n",
		(unsigned)xfrd->nsd->st.nona))
		return;

	/* rxerr */
	if(!ssl_printf(ssl, "num.rxerr=%u\n", (unsigned)xfrd->nsd->st.rxerr))
		return;

	/* txerr */
	if(!ssl_printf(ssl, "num.txerr=%u\n", (unsigned)xfrd->nsd->st.txerr))
		return;

	/* number of requested-axfr, number of times axfr served to clients */
	if(!ssl_printf(ssl, "num.raxfr=%u\n", (unsigned)xfrd->nsd->st.raxfr))
		return;

	/* truncated */
	if(!ssl_printf(ssl, "num.truncated=%u\n",
		(unsigned)xfrd->nsd->st.truncated))
		return;

	/* dropped */
	if(!ssl_printf(ssl, "num.dropped=%u\n",
		(unsigned)xfrd->nsd->st.dropped))
		return;

	/* zone statistics */
	if(!ssl_printf(ssl, "zone.master=%u\n",
		(unsigned)(xfrd->notify_zones->count - xfrd->zones->count)))
		return;
	if(!ssl_printf(ssl, "zone.slave=%u\n", (unsigned)xfrd->zones->count))
		return;
}

static void
clear_stats(xfrd_state_t* xfrd)
{
	size_t i;
	uint64_t dbd = xfrd->nsd->st.db_disk;
	uint64_t dbm = xfrd->nsd->st.db_mem;
	for(i=0; i<xfrd->nsd->child_count; i++) {
		xfrd->nsd->children[i].query_count = 0;
	}
	memset(&xfrd->nsd->st, 0, sizeof(struct nsdst));
	xfrd->nsd->st.db_disk = dbd;
	xfrd->nsd->st.db_mem = dbm;
}

void
daemon_remote_process_stats(struct daemon_remote* rc)
{
	struct rc_state* s;
	struct timeval now;
	if(!rc) return;
	if(gettimeofday(&now, NULL) == -1)
		log_msg(LOG_ERR, "gettimeofday: %s", strerror(errno));
	/* pop one and give it stats */
	while((s = rc->stats_list)) {
		assert(s->in_stats_list);
		print_stats(s->ssl, rc->xfrd, &now);
		if(s->in_stats_list == 1) {
			clear_stats(rc->xfrd);
			rc->stats_time = now;
		}
		VERBOSITY(3, (LOG_INFO, "remote control stats printed"));
		rc->stats_list = s->next;
		s->in_stats_list = 0;
		clean_point(rc, s);
	}
}
#endif /* BIND8_STATS */

#endif /* HAVE_SSL */
