/*
 * Copyright (C) 2004-2015  Internet Systems Consortium, Inc. ("ISC")
 * Copyright (C) 2000-2003  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/*! \file */

/*
 * Principal Author: DCL
 */

#include <config.h>

#include <stdlib.h>

#include <isc/app.h>
#include <isc/buffer.h>
#include <isc/commandline.h>
#include <isc/file.h>
#include <isc/log.h>
#include <isc/net.h>
#include <isc/mem.h>
#include <isc/print.h>
#include <isc/random.h>
#include <isc/socket.h>
#include <isc/stdtime.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/thread.h>
#include <isc/util.h>

#include <isccfg/namedconf.h>

#include <isccc/alist.h>
#include <isccc/base64.h>
#include <isccc/cc.h>
#include <isccc/ccmsg.h>
#include <isccc/result.h>
#include <isccc/sexpr.h>
#include <isccc/types.h>
#include <isccc/util.h>

#include <dns/name.h>

#include <bind9/getaddresses.h>

#include "util.h"

#define SERVERADDRS 10

const char *progname;
isc_boolean_t verbose;

static const char *admin_conffile;
static const char *admin_keyfile;
static const char *version = VERSION;
static const char *servername = NULL;
static isc_sockaddr_t serveraddrs[SERVERADDRS];
static isc_sockaddr_t local4, local6;
static isc_boolean_t local4set = ISC_FALSE, local6set = ISC_FALSE;
static int nserveraddrs;
static int currentaddr = 0;
static unsigned int remoteport = 0;
static isc_socketmgr_t *socketmgr = NULL;
static unsigned char databuf[2048];
static isccc_ccmsg_t ccmsg;
static isc_uint32_t algorithm;
static isccc_region_t secret;
static isc_boolean_t failed = ISC_FALSE;
static isc_boolean_t c_flag = ISC_FALSE;
static isc_mem_t *rndc_mctx;
static int sends, recvs, connects;
static char *command;
static char *args;
static char program[256];
static isc_socket_t *sock = NULL;
static isc_uint32_t serial;
static isc_boolean_t quiet = ISC_FALSE;

static void rndc_startconnect(isc_sockaddr_t *addr, isc_task_t *task);

ISC_PLATFORM_NORETURN_PRE static void
usage(int status) ISC_PLATFORM_NORETURN_POST;

static void
usage(int status) {
	fprintf(stderr, "\
Usage: %s [-b address] [-c config] [-s server] [-p port]\n\
	[-k key-file ] [-y key] [-V] command\n\
\n\
command is one of the following:\n\
\n\
  addzone zone [class [view]] { zone-options }\n\
		Add zone to given view. Requires new-zone-file option.\n\
  delzone [-clean] zone [class [view]]\n\
		Removes zone from given view. Requires new-zone-file option.\n\
  dumpdb [-all|-cache|-zones|-adb|-bad|-fail] [view ...]\n\
		Dump cache(s) to the dump file (named_dump.db).\n\
  flush 	Flushes all of the server's caches.\n\
  flush [view]	Flushes the server's cache for a view.\n\
  flushname name [view]\n\
		Flush the given name from the server's cache(s)\n\
  flushtree name [view]\n\
		Flush all names under the given name from the server's cache(s)\n\
  freeze	Suspend updates to all dynamic zones.\n\
  freeze zone [class [view]]\n\
		Suspend updates to a dynamic zone.\n\
  halt		Stop the server without saving pending updates.\n\
  halt -p	Stop the server without saving pending updates reporting\n\
		process id.\n\
  loadkeys zone [class [view]]\n\
		Update keys without signing immediately.\n\
  notify zone [class [view]]\n\
		Resend NOTIFY messages for the zone.\n\
  notrace	Set debugging level to 0.\n\
  querylog newstate\n\
		Enable / disable query logging.\n\
  reconfig	Reload configuration file and new zones only.\n\
  recursing	Dump the queries that are currently recursing (named.recursing)\n\
  refresh zone [class [view]]\n\
		Schedule immediate maintenance for a zone.\n\
  reload	Reload configuration file and zones.\n\
  reload zone [class [view]]\n\
		Reload a single zone.\n\
  retransfer zone [class [view]]\n\
		Retransfer a single zone without checking serial number.\n\
  scan		Scan available network interfaces for changes.\n\
  secroots [view ...]\n\
		Write security roots to the secroots file.\n\
  sign zone [class [view]]\n\
		Update zone keys, and sign as needed.\n\
  signing -clear all zone [class [view]]\n\
		Remove the private records for all keys that have\n\
		finished signing the given zone.\n\
  signing -clear <keyid>/<algorithm> zone [class [view]]\n\
		Remove the private record that indicating the given key\n\
		has finished signing the given zone.\n\
  signing -list zone [class [view]]\n\
		List the private records showing the state of DNSSEC\n\
		signing in the given zone.\n\
  signing -nsec3param hash flags iterations salt zone [class [view]]\n\
		Add NSEC3 chain to zone if already signed.\n\
		Prime zone with NSEC3 chain if not yet signed.\n\
  signing -nsec3param none zone [class [view]]\n\
		Remove NSEC3 chains from zone.\n\
  stats		Write server statistics to the statistics file.\n\
  status	Display status of the server.\n\
  stop		Save pending updates to master files and stop the server.\n\
  stop -p	Save pending updates to master files and stop the server\n\
		reporting process id.\n\
  sync [-clean]	Dump changes to all dynamic zones to disk, and optionally\n\
		remove their journal files.\n\
  sync [-clean] zone [class [view]]\n\
		Dump a single zone's changes to disk, and optionally\n\
		remove its journal file.\n\
  thaw		Enable updates to all dynamic zones and reload them.\n\
  thaw zone [class [view]]\n\
		Enable updates to a frozen dynamic zone and reload it.\n\
  trace		Increment debugging level by one.\n\
  trace level	Change the debugging level.\n\
  tsig-delete keyname [view]\n\
		Delete a TKEY-negotiated TSIG key.\n\
  tsig-list	List all currently active TSIG keys, including both statically\n\
		configured and TKEY-negotiated keys.\n\
  validation newstate [view]\n\
		Enable / disable DNSSEC validation.\n\
  zonestatus zone [class [view]]\n\
		Display the current status of a zone.\n\
\n\
Version: %s\n",
		progname, version);

	exit(status);
}

static void
get_addresses(const char *host, in_port_t port) {
	isc_result_t result;
	int found = 0, count;

	if (*host == '/') {
		result = isc_sockaddr_frompath(&serveraddrs[nserveraddrs],
					       host);
		if (result == ISC_R_SUCCESS)
			nserveraddrs++;
	} else {
		count = SERVERADDRS - nserveraddrs;
		result = bind9_getaddresses(host, port,
					    &serveraddrs[nserveraddrs],
					    count, &found);
		nserveraddrs += found;
	}
	if (result != ISC_R_SUCCESS)
		fatal("couldn't get address for '%s': %s",
		      host, isc_result_totext(result));
	INSIST(nserveraddrs > 0);
}

static void
rndc_senddone(isc_task_t *task, isc_event_t *event) {
	isc_socketevent_t *sevent = (isc_socketevent_t *)event;

	UNUSED(task);

	sends--;
	if (sevent->result != ISC_R_SUCCESS)
		fatal("send failed: %s", isc_result_totext(sevent->result));
	isc_event_free(&event);
	if (sends == 0 && recvs == 0) {
		isc_socket_detach(&sock);
		isc_task_shutdown(task);
		RUNTIME_CHECK(isc_app_shutdown() == ISC_R_SUCCESS);
	}
}

static void
rndc_recvdone(isc_task_t *task, isc_event_t *event) {
	isccc_sexpr_t *response = NULL;
	isccc_sexpr_t *data;
	isccc_region_t source;
	char *errormsg = NULL;
	char *textmsg = NULL;
	isc_result_t result;

	recvs--;

	if (ccmsg.result == ISC_R_EOF)
		fatal("connection to remote host closed\n"
		      "This may indicate that\n"
		      "* the remote server is using an older version of"
		      " the command protocol,\n"
		      "* this host is not authorized to connect,\n"
		      "* the clocks are not synchronized, or\n"
		      "* the key is invalid.");

	if (ccmsg.result != ISC_R_SUCCESS)
		fatal("recv failed: %s", isc_result_totext(ccmsg.result));

	source.rstart = isc_buffer_base(&ccmsg.buffer);
	source.rend = isc_buffer_used(&ccmsg.buffer);

	DO("parse message",
	   isccc_cc_fromwire(&source, &response, algorithm, &secret));

	data = isccc_alist_lookup(response, "_data");
	if (data == NULL)
		fatal("no data section in response");
	result = isccc_cc_lookupstring(data, "err", &errormsg);
	if (result == ISC_R_SUCCESS) {
		failed = ISC_TRUE;
		fprintf(stderr, "%s: '%s' failed: %s\n",
			progname, command, errormsg);
	}
	else if (result != ISC_R_NOTFOUND)
		fprintf(stderr, "%s: parsing response failed: %s\n",
			progname, isc_result_totext(result));

	result = isccc_cc_lookupstring(data, "text", &textmsg);
	if (result == ISC_R_SUCCESS) {
		if ((!quiet || failed) && strlen(textmsg) != 0U)
			fprintf(failed ? stderr : stdout, "%s\n", textmsg);
	} else if (result != ISC_R_NOTFOUND)
		fprintf(stderr, "%s: parsing response failed: %s\n",
			progname, isc_result_totext(result));

	isc_event_free(&event);
	isccc_sexpr_free(&response);
	if (sends == 0 && recvs == 0) {
		isc_socket_detach(&sock);
		isc_task_shutdown(task);
		RUNTIME_CHECK(isc_app_shutdown() == ISC_R_SUCCESS);
	}
}

static void
rndc_recvnonce(isc_task_t *task, isc_event_t *event) {
	isccc_sexpr_t *response = NULL;
	isccc_sexpr_t *_ctrl;
	isccc_region_t source;
	isc_result_t result;
	isc_uint32_t nonce;
	isccc_sexpr_t *request = NULL;
	isccc_time_t now;
	isc_region_t r;
	isccc_sexpr_t *data;
	isccc_region_t message;
	isc_uint32_t len;
	isc_buffer_t b;

	recvs--;

	if (ccmsg.result == ISC_R_EOF)
		fatal("connection to remote host closed\n"
		      "This may indicate that\n"
		      "* the remote server is using an older version of"
		      " the command protocol,\n"
		      "* this host is not authorized to connect,\n"
		      "* the clocks are not synchronized,\n"
		      "* the key signing algorithm is incorrect, or\n"
		      "* the key is invalid.");

	if (ccmsg.result != ISC_R_SUCCESS)
		fatal("recv failed: %s", isc_result_totext(ccmsg.result));

	source.rstart = isc_buffer_base(&ccmsg.buffer);
	source.rend = isc_buffer_used(&ccmsg.buffer);

	DO("parse message",
	   isccc_cc_fromwire(&source, &response, algorithm, &secret));

	_ctrl = isccc_alist_lookup(response, "_ctrl");
	if (_ctrl == NULL)
		fatal("_ctrl section missing");
	nonce = 0;
	if (isccc_cc_lookupuint32(_ctrl, "_nonce", &nonce) != ISC_R_SUCCESS)
		nonce = 0;

	isc_stdtime_get(&now);

	DO("create message", isccc_cc_createmessage(1, NULL, NULL, ++serial,
						    now, now + 60, &request));
	data = isccc_alist_lookup(request, "_data");
	if (data == NULL)
		fatal("_data section missing");
	if (isccc_cc_definestring(data, "type", args) == NULL)
		fatal("out of memory");
	if (nonce != 0) {
		_ctrl = isccc_alist_lookup(request, "_ctrl");
		if (_ctrl == NULL)
			fatal("_ctrl section missing");
		if (isccc_cc_defineuint32(_ctrl, "_nonce", nonce) == NULL)
			fatal("out of memory");
	}
	message.rstart = databuf + 4;
	message.rend = databuf + sizeof(databuf);
	DO("render message",
	   isccc_cc_towire(request, &message, algorithm, &secret));
	len = sizeof(databuf) - REGION_SIZE(message);
	isc_buffer_init(&b, databuf, 4);
	isc_buffer_putuint32(&b, len - 4);
	r.length = len;
	r.base = databuf;

	isccc_ccmsg_cancelread(&ccmsg);
	DO("schedule recv", isccc_ccmsg_readmessage(&ccmsg, task,
						    rndc_recvdone, NULL));
	recvs++;
	DO("send message", isc_socket_send(sock, &r, task, rndc_senddone,
					   NULL));
	sends++;

	isc_event_free(&event);
	isccc_sexpr_free(&response);
	return;
}

static void
rndc_connected(isc_task_t *task, isc_event_t *event) {
	char socktext[ISC_SOCKADDR_FORMATSIZE];
	isc_socketevent_t *sevent = (isc_socketevent_t *)event;
	isccc_sexpr_t *request = NULL;
	isccc_sexpr_t *data;
	isccc_time_t now;
	isccc_region_t message;
	isc_region_t r;
	isc_uint32_t len;
	isc_buffer_t b;
	isc_result_t result;

	connects--;

	if (sevent->result != ISC_R_SUCCESS) {
		isc_sockaddr_format(&serveraddrs[currentaddr], socktext,
				    sizeof(socktext));
		if (sevent->result != ISC_R_CANCELED &&
		    ++currentaddr < nserveraddrs)
		{
			notify("connection failed: %s: %s", socktext,
			       isc_result_totext(sevent->result));
			isc_socket_detach(&sock);
			isc_event_free(&event);
			rndc_startconnect(&serveraddrs[currentaddr], task);
			return;
		} else
			fatal("connect failed: %s: %s", socktext,
			      isc_result_totext(sevent->result));
	}

	isc_stdtime_get(&now);
	DO("create message", isccc_cc_createmessage(1, NULL, NULL, ++serial,
						    now, now + 60, &request));
	data = isccc_alist_lookup(request, "_data");
	if (data == NULL)
		fatal("_data section missing");
	if (isccc_cc_definestring(data, "type", "null") == NULL)
		fatal("out of memory");
	message.rstart = databuf + 4;
	message.rend = databuf + sizeof(databuf);
	DO("render message",
	   isccc_cc_towire(request, &message, algorithm, &secret));
	len = sizeof(databuf) - REGION_SIZE(message);
	isc_buffer_init(&b, databuf, 4);
	isc_buffer_putuint32(&b, len - 4);
	r.length = len;
	r.base = databuf;

	isccc_ccmsg_init(rndc_mctx, sock, &ccmsg);
	isccc_ccmsg_setmaxsize(&ccmsg, 1024 * 1024);

	DO("schedule recv", isccc_ccmsg_readmessage(&ccmsg, task,
						    rndc_recvnonce, NULL));
	recvs++;
	DO("send message", isc_socket_send(sock, &r, task, rndc_senddone,
					   NULL));
	sends++;
	isc_event_free(&event);
}

static void
rndc_startconnect(isc_sockaddr_t *addr, isc_task_t *task) {
	isc_result_t result;
	int pf;
	isc_sockettype_t type;

	char socktext[ISC_SOCKADDR_FORMATSIZE];

	isc_sockaddr_format(addr, socktext, sizeof(socktext));

	notify("using server %s (%s)", servername, socktext);

	pf = isc_sockaddr_pf(addr);
	if (pf == AF_INET || pf == AF_INET6)
		type = isc_sockettype_tcp;
	else
		type = isc_sockettype_unix;
	DO("create socket", isc_socket_create(socketmgr, pf, type, &sock));
	switch (isc_sockaddr_pf(addr)) {
	case AF_INET:
		DO("bind socket", isc_socket_bind(sock, &local4, 0));
		break;
	case AF_INET6:
		DO("bind socket", isc_socket_bind(sock, &local6, 0));
		break;
	default:
		break;
	}
	DO("connect", isc_socket_connect(sock, addr, task, rndc_connected,
					 NULL));
	connects++;
}

static void
rndc_start(isc_task_t *task, isc_event_t *event) {
	isc_event_free(&event);

	currentaddr = 0;
	rndc_startconnect(&serveraddrs[currentaddr], task);
}

static void
parse_config(isc_mem_t *mctx, isc_log_t *log, const char *keyname,
	     cfg_parser_t **pctxp, cfg_obj_t **configp)
{
	isc_result_t result;
	const char *conffile = admin_conffile;
	const cfg_obj_t *addresses = NULL;
	const cfg_obj_t *defkey = NULL;
	const cfg_obj_t *options = NULL;
	const cfg_obj_t *servers = NULL;
	const cfg_obj_t *server = NULL;
	const cfg_obj_t *keys = NULL;
	const cfg_obj_t *key = NULL;
	const cfg_obj_t *defport = NULL;
	const cfg_obj_t *secretobj = NULL;
	const cfg_obj_t *algorithmobj = NULL;
	cfg_obj_t *config = NULL;
	const cfg_obj_t *address = NULL;
	const cfg_listelt_t *elt;
	const char *secretstr;
	const char *algorithmstr;
	static char secretarray[1024];
	const cfg_type_t *conftype = &cfg_type_rndcconf;
	isc_boolean_t key_only = ISC_FALSE;
	const cfg_listelt_t *element;

	if (! isc_file_exists(conffile)) {
		conffile = admin_keyfile;
		conftype = &cfg_type_rndckey;

		if (c_flag)
			fatal("%s does not exist", admin_conffile);

		if (! isc_file_exists(conffile))
			fatal("neither %s nor %s was found",
			      admin_conffile, admin_keyfile);
		key_only = ISC_TRUE;
	} else if (! c_flag && isc_file_exists(admin_keyfile)) {
		fprintf(stderr, "WARNING: key file (%s) exists, but using "
			"default configuration file (%s)\n",
			admin_keyfile, admin_conffile);
	}

	DO("create parser", cfg_parser_create(mctx, log, pctxp));

	/*
	 * The parser will output its own errors, so DO() is not used.
	 */
	result = cfg_parse_file(*pctxp, conffile, conftype, &config);
	if (result != ISC_R_SUCCESS)
		fatal("could not load rndc configuration");

	if (!key_only)
		(void)cfg_map_get(config, "options", &options);

	if (key_only && servername == NULL)
		servername = "127.0.0.1";
	else if (servername == NULL && options != NULL) {
		const cfg_obj_t *defserverobj = NULL;
		(void)cfg_map_get(options, "default-server", &defserverobj);
		if (defserverobj != NULL)
			servername = cfg_obj_asstring(defserverobj);
	}

	if (servername == NULL)
		fatal("no server specified and no default");

	if (!key_only) {
		(void)cfg_map_get(config, "server", &servers);
		if (servers != NULL) {
			for (elt = cfg_list_first(servers);
			     elt != NULL;
			     elt = cfg_list_next(elt))
			{
				const char *name;
				server = cfg_listelt_value(elt);
				name = cfg_obj_asstring(cfg_map_getname(server));
				if (strcasecmp(name, servername) == 0)
					break;
				server = NULL;
			}
		}
	}

	/*
	 * Look for the name of the key to use.
	 */
	if (keyname != NULL)
		;		/* Was set on command line, do nothing. */
	else if (server != NULL) {
		DO("get key for server", cfg_map_get(server, "key", &defkey));
		keyname = cfg_obj_asstring(defkey);
	} else if (options != NULL) {
		DO("get default key", cfg_map_get(options, "default-key",
						  &defkey));
		keyname = cfg_obj_asstring(defkey);
	} else if (!key_only)
		fatal("no key for server and no default");

	/*
	 * Get the key's definition.
	 */
	if (key_only)
		DO("get key", cfg_map_get(config, "key", &key));
	else {
		DO("get config key list", cfg_map_get(config, "key", &keys));
		for (elt = cfg_list_first(keys);
		     elt != NULL;
		     elt = cfg_list_next(elt))
		{
			key = cfg_listelt_value(elt);
			if (strcasecmp(cfg_obj_asstring(cfg_map_getname(key)),
				       keyname) == 0)
				break;
		}
		if (elt == NULL)
			fatal("no key definition for name %s", keyname);
	}
	(void)cfg_map_get(key, "secret", &secretobj);
	(void)cfg_map_get(key, "algorithm", &algorithmobj);
	if (secretobj == NULL || algorithmobj == NULL)
		fatal("key must have algorithm and secret");

	secretstr = cfg_obj_asstring(secretobj);
	algorithmstr = cfg_obj_asstring(algorithmobj);

	if (strcasecmp(algorithmstr, "hmac-md5") == 0)
		algorithm = ISCCC_ALG_HMACMD5;
	else if (strcasecmp(algorithmstr, "hmac-sha1") == 0)
		algorithm = ISCCC_ALG_HMACSHA1;
	else if (strcasecmp(algorithmstr, "hmac-sha224") == 0)
		algorithm = ISCCC_ALG_HMACSHA224;
	else if (strcasecmp(algorithmstr, "hmac-sha256") == 0)
		algorithm = ISCCC_ALG_HMACSHA256;
	else if (strcasecmp(algorithmstr, "hmac-sha384") == 0)
		algorithm = ISCCC_ALG_HMACSHA384;
	else if (strcasecmp(algorithmstr, "hmac-sha512") == 0)
		algorithm = ISCCC_ALG_HMACSHA512;
	else
		fatal("unsupported algorithm: %s", algorithmstr);

	secret.rstart = (unsigned char *)secretarray;
	secret.rend = (unsigned char *)secretarray + sizeof(secretarray);
	DO("decode base64 secret", isccc_base64_decode(secretstr, &secret));
	secret.rend = secret.rstart;
	secret.rstart = (unsigned char *)secretarray;

	/*
	 * Find the port to connect to.
	 */
	if (remoteport != 0)
		;		/* Was set on command line, do nothing. */
	else {
		if (server != NULL)
			(void)cfg_map_get(server, "port", &defport);
		if (defport == NULL && options != NULL)
			(void)cfg_map_get(options, "default-port", &defport);
	}
	if (defport != NULL) {
		remoteport = cfg_obj_asuint32(defport);
		if (remoteport > 65535 || remoteport == 0)
			fatal("port %u out of range", remoteport);
	} else if (remoteport == 0)
		remoteport = NS_CONTROL_PORT;

	if (server != NULL)
		result = cfg_map_get(server, "addresses", &addresses);
	else
		result = ISC_R_NOTFOUND;
	if (result == ISC_R_SUCCESS) {
		for (element = cfg_list_first(addresses);
		     element != NULL;
		     element = cfg_list_next(element))
		{
			isc_sockaddr_t sa;

			address = cfg_listelt_value(element);
			if (!cfg_obj_issockaddr(address)) {
				unsigned int myport;
				const char *name;
				const cfg_obj_t *obj;

				obj = cfg_tuple_get(address, "name");
				name = cfg_obj_asstring(obj);
				obj = cfg_tuple_get(address, "port");
				if (cfg_obj_isuint32(obj)) {
					myport = cfg_obj_asuint32(obj);
					if (myport > ISC_UINT16_MAX ||
					    myport == 0)
						fatal("port %u out of range",
						      myport);
				} else
					myport = remoteport;
				if (nserveraddrs < SERVERADDRS)
					get_addresses(name, (in_port_t) myport);
				else
					fprintf(stderr, "too many address: "
						"%s: dropped\n", name);
				continue;
			}
			sa = *cfg_obj_assockaddr(address);
			if (isc_sockaddr_getport(&sa) == 0)
				isc_sockaddr_setport(&sa, remoteport);
			if (nserveraddrs < SERVERADDRS)
				serveraddrs[nserveraddrs++] = sa;
			else {
				char socktext[ISC_SOCKADDR_FORMATSIZE];

				isc_sockaddr_format(&sa, socktext,
						    sizeof(socktext));
				fprintf(stderr,
					"too many address: %s: dropped\n",
					socktext);
			}
		}
	}

	if (!local4set && server != NULL) {
		address = NULL;
		cfg_map_get(server, "source-address", &address);
		if (address != NULL) {
			local4 = *cfg_obj_assockaddr(address);
			local4set = ISC_TRUE;
		}
	}
	if (!local4set && options != NULL) {
		address = NULL;
		cfg_map_get(options, "default-source-address", &address);
		if (address != NULL) {
			local4 = *cfg_obj_assockaddr(address);
			local4set = ISC_TRUE;
		}
	}

	if (!local6set && server != NULL) {
		address = NULL;
		cfg_map_get(server, "source-address-v6", &address);
		if (address != NULL) {
			local6 = *cfg_obj_assockaddr(address);
			local6set = ISC_TRUE;
		}
	}
	if (!local6set && options != NULL) {
		address = NULL;
		cfg_map_get(options, "default-source-address-v6", &address);
		if (address != NULL) {
			local6 = *cfg_obj_assockaddr(address);
			local6set = ISC_TRUE;
		}
	}

	*configp = config;
}

int
main(int argc, char **argv) {
	isc_result_t result = ISC_R_SUCCESS;
	isc_boolean_t show_final_mem = ISC_FALSE;
	isc_taskmgr_t *taskmgr = NULL;
	isc_task_t *task = NULL;
	isc_log_t *log = NULL;
	isc_logconfig_t *logconfig = NULL;
	isc_logdestination_t logdest;
	cfg_parser_t *pctx = NULL;
	cfg_obj_t *config = NULL;
	const char *keyname = NULL;
	struct in_addr in;
	struct in6_addr in6;
	char *p;
	size_t argslen;
	int ch;
	int i;

	result = isc_file_progname(*argv, program, sizeof(program));
	if (result != ISC_R_SUCCESS)
		memmove(program, "rndc", 5);
	progname = program;

	admin_conffile = RNDC_CONFFILE;
	admin_keyfile = RNDC_KEYFILE;

	isc_sockaddr_any(&local4);
	isc_sockaddr_any6(&local6);

	result = isc_app_start();
	if (result != ISC_R_SUCCESS)
		fatal("isc_app_start() failed: %s", isc_result_totext(result));

	isc_commandline_errprint = ISC_FALSE;

	while ((ch = isc_commandline_parse(argc, argv, "b:c:hk:Mmp:qs:Vy:"))
	       != -1) {
		switch (ch) {
		case 'b':
			if (inet_pton(AF_INET, isc_commandline_argument,
				      &in) == 1) {
				isc_sockaddr_fromin(&local4, &in, 0);
				local4set = ISC_TRUE;
			} else if (inet_pton(AF_INET6, isc_commandline_argument,
					     &in6) == 1) {
				isc_sockaddr_fromin6(&local6, &in6, 0);
				local6set = ISC_TRUE;
			}
			break;

		case 'c':
			admin_conffile = isc_commandline_argument;
			c_flag = ISC_TRUE;
			break;

		case 'k':
			admin_keyfile = isc_commandline_argument;
			break;

		case 'M':
			isc_mem_debugging = ISC_MEM_DEBUGTRACE;
			break;

		case 'm':
			show_final_mem = ISC_TRUE;
			break;

		case 'p':
			remoteport = atoi(isc_commandline_argument);
			if (remoteport > 65535 || remoteport == 0)
				fatal("port '%s' out of range",
				      isc_commandline_argument);
			break;

		case 'q':
			quiet = ISC_TRUE;
			break;

		case 's':
			servername = isc_commandline_argument;
			break;

		case 'V':
			verbose = ISC_TRUE;
			break;

		case 'y':
			keyname = isc_commandline_argument;
			break;

		case '?':
			if (isc_commandline_option != '?') {
				fprintf(stderr, "%s: invalid argument -%c\n",
					program, isc_commandline_option);
				usage(1);
			}
			/* FALLTHROUGH */
		case 'h':
			usage(0);
			break;
		default:
			fprintf(stderr, "%s: unhandled option -%c\n",
				program, isc_commandline_option);
			exit(1);
		}
	}

	argc -= isc_commandline_index;
	argv += isc_commandline_index;

	if (argc < 1)
		usage(1);

	isc_random_get(&serial);

	DO("create memory context", isc_mem_create(0, 0, &rndc_mctx));
	DO("create socket manager", isc_socketmgr_create(rndc_mctx, &socketmgr));
	DO("create task manager", isc_taskmgr_create(rndc_mctx, 1, 0, &taskmgr));
	DO("create task", isc_task_create(taskmgr, 0, &task));

	DO("create logging context", isc_log_create(rndc_mctx, &log, &logconfig));
	isc_log_setcontext(log);
	DO("setting log tag", isc_log_settag(logconfig, progname));
	logdest.file.stream = stderr;
	logdest.file.name = NULL;
	logdest.file.versions = ISC_LOG_ROLLNEVER;
	logdest.file.maximum_size = 0;
	DO("creating log channel",
	   isc_log_createchannel(logconfig, "stderr",
				 ISC_LOG_TOFILEDESC, ISC_LOG_INFO, &logdest,
				 ISC_LOG_PRINTTAG|ISC_LOG_PRINTLEVEL));
	DO("enabling log channel", isc_log_usechannel(logconfig, "stderr",
						      NULL, NULL));

	parse_config(rndc_mctx, log, keyname, &pctx, &config);

	isccc_result_register();

	command = *argv;

	/*
	 * Convert argc/argv into a space-delimited command string
	 * similar to what the user might enter in interactive mode
	 * (if that were implemented).
	 */
	argslen = 0;
	for (i = 0; i < argc; i++)
		argslen += strlen(argv[i]) + 1;

	args = isc_mem_get(rndc_mctx, argslen);
	if (args == NULL)
		DO("isc_mem_get", ISC_R_NOMEMORY);

	p = args;
	for (i = 0; i < argc; i++) {
		size_t len = strlen(argv[i]);
		memmove(p, argv[i], len);
		p += len;
		*p++ = ' ';
	}

	p--;
	*p++ = '\0';
	INSIST(p == args + argslen);

	notify("%s", command);

	if (strcmp(command, "restart") == 0)
		fatal("'%s' is not implemented", command);

	if (nserveraddrs == 0)
		get_addresses(servername, (in_port_t) remoteport);

	DO("post event", isc_app_onrun(rndc_mctx, task, rndc_start, NULL));

	result = isc_app_run();
	if (result != ISC_R_SUCCESS)
		fatal("isc_app_run() failed: %s", isc_result_totext(result));

	if (connects > 0 || sends > 0 || recvs > 0)
		isc_socket_cancel(sock, task, ISC_SOCKCANCEL_ALL);

	isc_task_detach(&task);
	isc_taskmgr_destroy(&taskmgr);
	isc_socketmgr_destroy(&socketmgr);
	isc_log_destroy(&log);
	isc_log_setcontext(NULL);

	cfg_obj_destroy(pctx, &config);
	cfg_parser_destroy(&pctx);

	isc_mem_put(rndc_mctx, args, argslen);
	isccc_ccmsg_invalidate(&ccmsg);

	dns_name_destroy();

	if (show_final_mem)
		isc_mem_stats(rndc_mctx, stderr);

	isc_mem_destroy(&rndc_mctx);

	if (failed)
		return (1);

	return (0);
}
