controlconf.c revision 165071
1135446Strhodes/*
2165071Sdougb * Copyright (C) 2004, 2006  Internet Systems Consortium, Inc. ("ISC")
3135446Strhodes * Copyright (C) 2001-2003  Internet Software Consortium.
4135446Strhodes *
5135446Strhodes * Permission to use, copy, modify, and distribute this software for any
6135446Strhodes * purpose with or without fee is hereby granted, provided that the above
7135446Strhodes * copyright notice and this permission notice appear in all copies.
8135446Strhodes *
9135446Strhodes * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10135446Strhodes * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11135446Strhodes * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12135446Strhodes * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13135446Strhodes * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14135446Strhodes * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15135446Strhodes * PERFORMANCE OF THIS SOFTWARE.
16135446Strhodes */
17135446Strhodes
18165071Sdougb/* $Id: controlconf.c,v 1.28.2.9.2.10 2006/02/28 06:32:53 marka Exp $ */
19135446Strhodes
20135446Strhodes#include <config.h>
21135446Strhodes
22135446Strhodes#include <isc/base64.h>
23135446Strhodes#include <isc/buffer.h>
24135446Strhodes#include <isc/event.h>
25135446Strhodes#include <isc/mem.h>
26135446Strhodes#include <isc/net.h>
27135446Strhodes#include <isc/netaddr.h>
28135446Strhodes#include <isc/random.h>
29135446Strhodes#include <isc/result.h>
30135446Strhodes#include <isc/stdtime.h>
31135446Strhodes#include <isc/string.h>
32135446Strhodes#include <isc/timer.h>
33135446Strhodes#include <isc/util.h>
34135446Strhodes
35135446Strhodes#include <isccfg/namedconf.h>
36135446Strhodes
37135446Strhodes#include <bind9/check.h>
38135446Strhodes
39135446Strhodes#include <isccc/alist.h>
40135446Strhodes#include <isccc/cc.h>
41135446Strhodes#include <isccc/ccmsg.h>
42135446Strhodes#include <isccc/events.h>
43135446Strhodes#include <isccc/result.h>
44135446Strhodes#include <isccc/sexpr.h>
45135446Strhodes#include <isccc/symtab.h>
46135446Strhodes#include <isccc/util.h>
47135446Strhodes
48135446Strhodes#include <dns/result.h>
49135446Strhodes
50135446Strhodes#include <named/config.h>
51135446Strhodes#include <named/control.h>
52135446Strhodes#include <named/log.h>
53135446Strhodes#include <named/server.h>
54135446Strhodes
55135446Strhodes/*
56135446Strhodes * Note: Listeners and connections are not locked.  All event handlers are
57135446Strhodes * executed by the server task, and all callers of exported routines must
58135446Strhodes * be running under the server task.
59135446Strhodes */
60135446Strhodes
61135446Strhodestypedef struct controlkey controlkey_t;
62135446Strhodestypedef ISC_LIST(controlkey_t) controlkeylist_t;
63135446Strhodes
64135446Strhodestypedef struct controlconnection controlconnection_t;
65135446Strhodestypedef ISC_LIST(controlconnection_t) controlconnectionlist_t;
66135446Strhodes
67135446Strhodestypedef struct controllistener controllistener_t;
68135446Strhodestypedef ISC_LIST(controllistener_t) controllistenerlist_t;
69135446Strhodes
70135446Strhodesstruct controlkey {
71135446Strhodes	char *				keyname;
72135446Strhodes	isc_region_t			secret;
73135446Strhodes	ISC_LINK(controlkey_t)		link;
74135446Strhodes};
75135446Strhodes
76135446Strhodesstruct controlconnection {
77135446Strhodes	isc_socket_t *			sock;
78135446Strhodes	isccc_ccmsg_t			ccmsg;
79135446Strhodes	isc_boolean_t			ccmsg_valid;
80135446Strhodes	isc_boolean_t			sending;
81135446Strhodes	isc_timer_t *			timer;
82135446Strhodes	unsigned char			buffer[2048];
83135446Strhodes	controllistener_t *		listener;
84135446Strhodes	isc_uint32_t			nonce;
85135446Strhodes	ISC_LINK(controlconnection_t)	link;
86135446Strhodes};
87135446Strhodes
88135446Strhodesstruct controllistener {
89135446Strhodes	ns_controls_t *			controls;
90135446Strhodes	isc_mem_t *			mctx;
91135446Strhodes	isc_task_t *			task;
92135446Strhodes	isc_sockaddr_t			address;
93135446Strhodes	isc_socket_t *			sock;
94135446Strhodes	dns_acl_t *			acl;
95135446Strhodes	isc_boolean_t			listening;
96135446Strhodes	isc_boolean_t			exiting;
97135446Strhodes	controlkeylist_t		keys;
98135446Strhodes	controlconnectionlist_t		connections;
99135446Strhodes	ISC_LINK(controllistener_t)	link;
100135446Strhodes};
101135446Strhodes
102135446Strhodesstruct ns_controls {
103135446Strhodes	ns_server_t			*server;
104135446Strhodes	controllistenerlist_t 		listeners;
105135446Strhodes	isc_boolean_t			shuttingdown;
106135446Strhodes	isccc_symtab_t			*symtab;
107135446Strhodes};
108135446Strhodes
109135446Strhodesstatic void control_newconn(isc_task_t *task, isc_event_t *event);
110135446Strhodesstatic void control_recvmessage(isc_task_t *task, isc_event_t *event);
111135446Strhodes
112135446Strhodes#define CLOCKSKEW 300
113135446Strhodes
114135446Strhodesstatic void
115135446Strhodesfree_controlkey(controlkey_t *key, isc_mem_t *mctx) {
116135446Strhodes	if (key->keyname != NULL)
117135446Strhodes		isc_mem_free(mctx, key->keyname);
118135446Strhodes	if (key->secret.base != NULL)
119135446Strhodes		isc_mem_put(mctx, key->secret.base, key->secret.length);
120135446Strhodes	isc_mem_put(mctx, key, sizeof(*key));
121135446Strhodes}
122135446Strhodes
123135446Strhodesstatic void
124135446Strhodesfree_controlkeylist(controlkeylist_t *keylist, isc_mem_t *mctx) {
125135446Strhodes	while (!ISC_LIST_EMPTY(*keylist)) {
126135446Strhodes		controlkey_t *key = ISC_LIST_HEAD(*keylist);
127135446Strhodes		ISC_LIST_UNLINK(*keylist, key, link);
128135446Strhodes		free_controlkey(key, mctx);
129135446Strhodes	}
130135446Strhodes}
131135446Strhodes
132135446Strhodesstatic void
133135446Strhodesfree_listener(controllistener_t *listener) {
134135446Strhodes	INSIST(listener->exiting);
135135446Strhodes	INSIST(!listener->listening);
136135446Strhodes	INSIST(ISC_LIST_EMPTY(listener->connections));
137135446Strhodes
138135446Strhodes	if (listener->sock != NULL)
139135446Strhodes		isc_socket_detach(&listener->sock);
140135446Strhodes
141135446Strhodes	free_controlkeylist(&listener->keys, listener->mctx);
142135446Strhodes
143135446Strhodes	if (listener->acl != NULL)
144135446Strhodes		dns_acl_detach(&listener->acl);
145135446Strhodes
146135446Strhodes	isc_mem_put(listener->mctx, listener, sizeof(*listener));
147135446Strhodes}
148135446Strhodes
149135446Strhodesstatic void
150135446Strhodesmaybe_free_listener(controllistener_t *listener) {
151135446Strhodes	if (listener->exiting &&
152135446Strhodes	    !listener->listening &&
153135446Strhodes	    ISC_LIST_EMPTY(listener->connections))
154135446Strhodes		free_listener(listener);
155135446Strhodes}
156135446Strhodes
157135446Strhodesstatic void
158135446Strhodesmaybe_free_connection(controlconnection_t *conn) {
159135446Strhodes	controllistener_t *listener = conn->listener;
160135446Strhodes
161135446Strhodes	if (conn->timer != NULL)
162135446Strhodes		isc_timer_detach(&conn->timer);
163135446Strhodes
164135446Strhodes	if (conn->ccmsg_valid) {
165135446Strhodes		isccc_ccmsg_cancelread(&conn->ccmsg);
166135446Strhodes		return;
167135446Strhodes	}
168135446Strhodes
169135446Strhodes	if (conn->sending) {
170135446Strhodes		isc_socket_cancel(conn->sock, listener->task,
171135446Strhodes				  ISC_SOCKCANCEL_SEND);
172135446Strhodes		return;
173135446Strhodes	}
174135446Strhodes
175135446Strhodes	ISC_LIST_UNLINK(listener->connections, conn, link);
176135446Strhodes	isc_mem_put(listener->mctx, conn, sizeof(*conn));
177135446Strhodes}
178135446Strhodes
179135446Strhodesstatic void
180135446Strhodesshutdown_listener(controllistener_t *listener) {
181135446Strhodes	controlconnection_t *conn;
182135446Strhodes	controlconnection_t *next;
183135446Strhodes
184135446Strhodes	if (!listener->exiting) {
185135446Strhodes		char socktext[ISC_SOCKADDR_FORMATSIZE];
186135446Strhodes
187135446Strhodes		ISC_LIST_UNLINK(listener->controls->listeners, listener, link);
188135446Strhodes
189135446Strhodes		isc_sockaddr_format(&listener->address, socktext,
190135446Strhodes				    sizeof(socktext));
191135446Strhodes		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
192135446Strhodes			      NS_LOGMODULE_CONTROL, ISC_LOG_NOTICE,
193135446Strhodes			      "stopping command channel on %s", socktext);
194135446Strhodes		listener->exiting = ISC_TRUE;
195135446Strhodes	}
196135446Strhodes
197135446Strhodes	for (conn = ISC_LIST_HEAD(listener->connections);
198135446Strhodes	     conn != NULL;
199135446Strhodes	     conn = next)
200135446Strhodes	{
201135446Strhodes		next = ISC_LIST_NEXT(conn, link);
202135446Strhodes		maybe_free_connection(conn);
203135446Strhodes	}
204135446Strhodes
205135446Strhodes	if (listener->listening)
206135446Strhodes		isc_socket_cancel(listener->sock, listener->task,
207135446Strhodes				  ISC_SOCKCANCEL_ACCEPT);
208135446Strhodes
209135446Strhodes	maybe_free_listener(listener);
210135446Strhodes}
211135446Strhodes
212135446Strhodesstatic isc_boolean_t
213135446Strhodesaddress_ok(isc_sockaddr_t *sockaddr, dns_acl_t *acl) {
214135446Strhodes	isc_netaddr_t netaddr;
215135446Strhodes	isc_result_t result;
216135446Strhodes	int match;
217135446Strhodes
218135446Strhodes	isc_netaddr_fromsockaddr(&netaddr, sockaddr);
219135446Strhodes
220135446Strhodes	result = dns_acl_match(&netaddr, NULL, acl,
221135446Strhodes			       &ns_g_server->aclenv, &match, NULL);
222135446Strhodes
223135446Strhodes	if (result != ISC_R_SUCCESS || match <= 0)
224135446Strhodes		return (ISC_FALSE);
225135446Strhodes	else
226135446Strhodes		return (ISC_TRUE);
227135446Strhodes}
228135446Strhodes
229135446Strhodesstatic isc_result_t
230135446Strhodescontrol_accept(controllistener_t *listener) {
231135446Strhodes	isc_result_t result;
232135446Strhodes	result = isc_socket_accept(listener->sock,
233135446Strhodes				   listener->task,
234135446Strhodes				   control_newconn, listener);
235135446Strhodes	if (result != ISC_R_SUCCESS)
236135446Strhodes		UNEXPECTED_ERROR(__FILE__, __LINE__,
237135446Strhodes				 "isc_socket_accept() failed: %s",
238135446Strhodes				 isc_result_totext(result));
239135446Strhodes	else
240135446Strhodes		listener->listening = ISC_TRUE;
241135446Strhodes	return (result);
242135446Strhodes}
243135446Strhodes
244135446Strhodesstatic isc_result_t
245135446Strhodescontrol_listen(controllistener_t *listener) {
246135446Strhodes	isc_result_t result;
247135446Strhodes
248135446Strhodes	result = isc_socket_listen(listener->sock, 0);
249135446Strhodes	if (result != ISC_R_SUCCESS)
250135446Strhodes		UNEXPECTED_ERROR(__FILE__, __LINE__,
251135446Strhodes				 "isc_socket_listen() failed: %s",
252135446Strhodes				 isc_result_totext(result));
253135446Strhodes	return (result);
254135446Strhodes}
255135446Strhodes
256135446Strhodesstatic void
257135446Strhodescontrol_next(controllistener_t *listener) {
258135446Strhodes	(void)control_accept(listener);
259135446Strhodes}
260135446Strhodes
261135446Strhodesstatic void
262135446Strhodescontrol_senddone(isc_task_t *task, isc_event_t *event) {
263135446Strhodes	isc_socketevent_t *sevent = (isc_socketevent_t *) event;
264135446Strhodes	controlconnection_t *conn = event->ev_arg;
265135446Strhodes	controllistener_t *listener = conn->listener;
266135446Strhodes	isc_socket_t *sock = (isc_socket_t *)sevent->ev_sender;
267135446Strhodes	isc_result_t result;
268135446Strhodes
269135446Strhodes	REQUIRE(conn->sending);
270135446Strhodes
271135446Strhodes	UNUSED(task);
272135446Strhodes
273135446Strhodes	conn->sending = ISC_FALSE;
274135446Strhodes
275135446Strhodes	if (sevent->result != ISC_R_SUCCESS &&
276135446Strhodes	    sevent->result != ISC_R_CANCELED)
277135446Strhodes	{
278135446Strhodes		char socktext[ISC_SOCKADDR_FORMATSIZE];
279135446Strhodes		isc_sockaddr_t peeraddr;
280135446Strhodes
281135446Strhodes		(void)isc_socket_getpeername(sock, &peeraddr);
282135446Strhodes		isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext));
283135446Strhodes		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
284135446Strhodes			      NS_LOGMODULE_CONTROL, ISC_LOG_WARNING,
285135446Strhodes			      "error sending command response to %s: %s",
286135446Strhodes			      socktext, isc_result_totext(sevent->result));
287135446Strhodes	}
288135446Strhodes	isc_event_free(&event);
289135446Strhodes
290135446Strhodes	result = isccc_ccmsg_readmessage(&conn->ccmsg, listener->task,
291135446Strhodes					 control_recvmessage, conn);
292135446Strhodes	if (result != ISC_R_SUCCESS) {
293135446Strhodes		isc_socket_detach(&conn->sock);
294135446Strhodes		maybe_free_connection(conn);
295135446Strhodes		maybe_free_listener(listener);
296135446Strhodes	}
297135446Strhodes}
298135446Strhodes
299135446Strhodesstatic inline void
300135446Strhodeslog_invalid(isccc_ccmsg_t *ccmsg, isc_result_t result) {
301135446Strhodes	char socktext[ISC_SOCKADDR_FORMATSIZE];
302135446Strhodes	isc_sockaddr_t peeraddr;
303135446Strhodes
304135446Strhodes	(void)isc_socket_getpeername(ccmsg->sock, &peeraddr);
305135446Strhodes	isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext));
306135446Strhodes	isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
307135446Strhodes		      NS_LOGMODULE_CONTROL, ISC_LOG_ERROR,
308135446Strhodes		      "invalid command from %s: %s",
309135446Strhodes		      socktext, isc_result_totext(result));
310135446Strhodes}
311135446Strhodes
312135446Strhodesstatic void
313135446Strhodescontrol_recvmessage(isc_task_t *task, isc_event_t *event) {
314135446Strhodes	controlconnection_t *conn;
315135446Strhodes	controllistener_t *listener;
316135446Strhodes	controlkey_t *key;
317135446Strhodes	isccc_sexpr_t *request = NULL;
318135446Strhodes	isccc_sexpr_t *response = NULL;
319135446Strhodes	isccc_region_t ccregion;
320135446Strhodes	isccc_region_t secret;
321135446Strhodes	isc_stdtime_t now;
322135446Strhodes	isc_buffer_t b;
323135446Strhodes	isc_region_t r;
324135446Strhodes	isc_uint32_t len;
325135446Strhodes	isc_buffer_t text;
326135446Strhodes	char textarray[1024];
327135446Strhodes	isc_result_t result;
328135446Strhodes	isc_result_t eresult;
329135446Strhodes	isccc_sexpr_t *_ctrl;
330135446Strhodes	isccc_time_t sent;
331135446Strhodes	isccc_time_t exp;
332135446Strhodes	isc_uint32_t nonce;
333135446Strhodes
334135446Strhodes	REQUIRE(event->ev_type == ISCCC_EVENT_CCMSG);
335135446Strhodes
336135446Strhodes	conn = event->ev_arg;
337135446Strhodes	listener = conn->listener;
338135446Strhodes	secret.rstart = NULL;
339135446Strhodes
340135446Strhodes        /* Is the server shutting down? */
341135446Strhodes        if (listener->controls->shuttingdown)
342135446Strhodes                goto cleanup;
343135446Strhodes
344135446Strhodes	if (conn->ccmsg.result != ISC_R_SUCCESS) {
345135446Strhodes		if (conn->ccmsg.result != ISC_R_CANCELED &&
346135446Strhodes		    conn->ccmsg.result != ISC_R_EOF)
347135446Strhodes			log_invalid(&conn->ccmsg, conn->ccmsg.result);
348135446Strhodes		goto cleanup;
349135446Strhodes	}
350135446Strhodes
351135446Strhodes	request = NULL;
352135446Strhodes
353135446Strhodes	for (key = ISC_LIST_HEAD(listener->keys);
354135446Strhodes	     key != NULL;
355135446Strhodes	     key = ISC_LIST_NEXT(key, link))
356135446Strhodes	{
357135446Strhodes		ccregion.rstart = isc_buffer_base(&conn->ccmsg.buffer);
358135446Strhodes		ccregion.rend = isc_buffer_used(&conn->ccmsg.buffer);
359165071Sdougb		if (secret.rstart != NULL)
360165071Sdougb			isc_mem_put(listener->mctx, secret.rstart,
361165071Sdougb				    REGION_SIZE(secret));
362135446Strhodes		secret.rstart = isc_mem_get(listener->mctx, key->secret.length);
363135446Strhodes		if (secret.rstart == NULL)
364135446Strhodes			goto cleanup;
365135446Strhodes		memcpy(secret.rstart, key->secret.base, key->secret.length);
366135446Strhodes		secret.rend = secret.rstart + key->secret.length;
367135446Strhodes		result = isccc_cc_fromwire(&ccregion, &request, &secret);
368135446Strhodes		if (result == ISC_R_SUCCESS)
369135446Strhodes			break;
370135446Strhodes		else if (result == ISCCC_R_BADAUTH) {
371135446Strhodes			/*
372135446Strhodes			 * For some reason, request is non-NULL when
373135446Strhodes			 * isccc_cc_fromwire returns ISCCC_R_BADAUTH.
374135446Strhodes			 */
375135446Strhodes			if (request != NULL)
376135446Strhodes				isccc_sexpr_free(&request);
377135446Strhodes		} else {
378135446Strhodes			log_invalid(&conn->ccmsg, result);
379135446Strhodes			goto cleanup;
380135446Strhodes		}
381135446Strhodes	}
382135446Strhodes
383135446Strhodes	if (key == NULL) {
384135446Strhodes		log_invalid(&conn->ccmsg, ISCCC_R_BADAUTH);
385135446Strhodes		goto cleanup;
386135446Strhodes	}
387135446Strhodes
388135446Strhodes	/* We shouldn't be getting a reply. */
389135446Strhodes	if (isccc_cc_isreply(request)) {
390135446Strhodes		log_invalid(&conn->ccmsg, ISC_R_FAILURE);
391135446Strhodes		goto cleanup;
392135446Strhodes	}
393135446Strhodes
394135446Strhodes	isc_stdtime_get(&now);
395135446Strhodes
396135446Strhodes	/*
397135446Strhodes	 * Limit exposure to replay attacks.
398135446Strhodes	 */
399135446Strhodes	_ctrl = isccc_alist_lookup(request, "_ctrl");
400135446Strhodes	if (_ctrl == NULL) {
401135446Strhodes		log_invalid(&conn->ccmsg, ISC_R_FAILURE);
402135446Strhodes		goto cleanup;
403135446Strhodes	}
404135446Strhodes
405135446Strhodes	if (isccc_cc_lookupuint32(_ctrl, "_tim", &sent) == ISC_R_SUCCESS) {
406135446Strhodes		if ((sent + CLOCKSKEW) < now || (sent - CLOCKSKEW) > now) {
407135446Strhodes			log_invalid(&conn->ccmsg, ISCCC_R_CLOCKSKEW);
408135446Strhodes			goto cleanup;
409135446Strhodes		}
410135446Strhodes	} else {
411135446Strhodes		log_invalid(&conn->ccmsg, ISC_R_FAILURE);
412135446Strhodes		goto cleanup;
413135446Strhodes	}
414135446Strhodes
415135446Strhodes	/*
416135446Strhodes	 * Expire messages that are too old.
417135446Strhodes	 */
418135446Strhodes	if (isccc_cc_lookupuint32(_ctrl, "_exp", &exp) == ISC_R_SUCCESS &&
419135446Strhodes	    now > exp) {
420135446Strhodes		log_invalid(&conn->ccmsg, ISCCC_R_EXPIRED);
421135446Strhodes		goto cleanup;
422135446Strhodes	}
423135446Strhodes
424135446Strhodes	/*
425135446Strhodes	 * Duplicate suppression (required for UDP).
426135446Strhodes	 */
427135446Strhodes	isccc_cc_cleansymtab(listener->controls->symtab, now);
428135446Strhodes	result = isccc_cc_checkdup(listener->controls->symtab, request, now);
429135446Strhodes	if (result != ISC_R_SUCCESS) {
430135446Strhodes		if (result == ISC_R_EXISTS)
431135446Strhodes                        result = ISCCC_R_DUPLICATE;
432135446Strhodes		log_invalid(&conn->ccmsg, result);
433135446Strhodes		goto cleanup;
434135446Strhodes	}
435135446Strhodes
436135446Strhodes	if (conn->nonce != 0 &&
437135446Strhodes	    (isccc_cc_lookupuint32(_ctrl, "_nonce", &nonce) != ISC_R_SUCCESS ||
438135446Strhodes	     conn->nonce != nonce)) {
439135446Strhodes		log_invalid(&conn->ccmsg, ISCCC_R_BADAUTH);
440135446Strhodes		goto cleanup;
441135446Strhodes	}
442135446Strhodes
443135446Strhodes	/*
444135446Strhodes	 * Establish nonce.
445135446Strhodes	 */
446135446Strhodes	while (conn->nonce == 0)
447135446Strhodes		isc_random_get(&conn->nonce);
448135446Strhodes
449135446Strhodes	isc_buffer_init(&text, textarray, sizeof(textarray));
450135446Strhodes	eresult = ns_control_docommand(request, &text);
451135446Strhodes
452135446Strhodes	result = isccc_cc_createresponse(request, now, now + 60, &response);
453135446Strhodes	if (result != ISC_R_SUCCESS)
454135446Strhodes		goto cleanup;
455135446Strhodes	if (eresult != ISC_R_SUCCESS) {
456135446Strhodes		isccc_sexpr_t *data;
457135446Strhodes
458135446Strhodes		data = isccc_alist_lookup(response, "_data");
459135446Strhodes		if (data != NULL) {
460135446Strhodes			const char *estr = isc_result_totext(eresult);
461135446Strhodes			if (isccc_cc_definestring(data, "err", estr) == NULL)
462135446Strhodes				goto cleanup;
463135446Strhodes		}
464135446Strhodes	}
465135446Strhodes
466135446Strhodes	if (isc_buffer_usedlength(&text) > 0) {
467135446Strhodes		isccc_sexpr_t *data;
468135446Strhodes
469135446Strhodes		data = isccc_alist_lookup(response, "_data");
470135446Strhodes		if (data != NULL) {
471135446Strhodes			char *str = (char *)isc_buffer_base(&text);
472135446Strhodes			if (isccc_cc_definestring(data, "text", str) == NULL)
473135446Strhodes				goto cleanup;
474135446Strhodes		}
475135446Strhodes	}
476135446Strhodes
477135446Strhodes	_ctrl = isccc_alist_lookup(response, "_ctrl");
478135446Strhodes	if (_ctrl == NULL ||
479135446Strhodes	    isccc_cc_defineuint32(_ctrl, "_nonce", conn->nonce) == NULL)
480135446Strhodes		goto cleanup;
481135446Strhodes
482135446Strhodes	ccregion.rstart = conn->buffer + 4;
483135446Strhodes	ccregion.rend = conn->buffer + sizeof(conn->buffer);
484135446Strhodes	result = isccc_cc_towire(response, &ccregion, &secret);
485135446Strhodes	if (result != ISC_R_SUCCESS)
486135446Strhodes		goto cleanup;
487135446Strhodes	isc_buffer_init(&b, conn->buffer, 4);
488135446Strhodes	len = sizeof(conn->buffer) - REGION_SIZE(ccregion);
489135446Strhodes	isc_buffer_putuint32(&b, len - 4);
490135446Strhodes	r.base = conn->buffer;
491135446Strhodes	r.length = len;
492135446Strhodes
493135446Strhodes	result = isc_socket_send(conn->sock, &r, task, control_senddone, conn);
494135446Strhodes	if (result != ISC_R_SUCCESS)
495135446Strhodes		goto cleanup;
496135446Strhodes	conn->sending = ISC_TRUE;
497135446Strhodes
498135446Strhodes	if (secret.rstart != NULL)
499135446Strhodes		isc_mem_put(listener->mctx, secret.rstart,
500135446Strhodes			    REGION_SIZE(secret));
501135446Strhodes	if (request != NULL)
502135446Strhodes		isccc_sexpr_free(&request);
503135446Strhodes	if (response != NULL)
504135446Strhodes		isccc_sexpr_free(&response);
505135446Strhodes	return;
506135446Strhodes
507135446Strhodes cleanup:
508135446Strhodes	if (secret.rstart != NULL)
509135446Strhodes		isc_mem_put(listener->mctx, secret.rstart,
510135446Strhodes			    REGION_SIZE(secret));
511135446Strhodes	isc_socket_detach(&conn->sock);
512135446Strhodes	isccc_ccmsg_invalidate(&conn->ccmsg);
513135446Strhodes	conn->ccmsg_valid = ISC_FALSE;
514135446Strhodes	maybe_free_connection(conn);
515135446Strhodes	maybe_free_listener(listener);
516135446Strhodes	if (request != NULL)
517135446Strhodes		isccc_sexpr_free(&request);
518135446Strhodes	if (response != NULL)
519135446Strhodes		isccc_sexpr_free(&response);
520135446Strhodes}
521135446Strhodes
522135446Strhodesstatic void
523135446Strhodescontrol_timeout(isc_task_t *task, isc_event_t *event) {
524135446Strhodes	controlconnection_t *conn = event->ev_arg;
525135446Strhodes
526135446Strhodes	UNUSED(task);
527135446Strhodes
528135446Strhodes	isc_timer_detach(&conn->timer);
529135446Strhodes	maybe_free_connection(conn);
530135446Strhodes
531135446Strhodes	isc_event_free(&event);
532135446Strhodes}
533135446Strhodes
534135446Strhodesstatic isc_result_t
535135446Strhodesnewconnection(controllistener_t *listener, isc_socket_t *sock) {
536135446Strhodes	controlconnection_t *conn;
537135446Strhodes	isc_interval_t interval;
538135446Strhodes	isc_result_t result;
539135446Strhodes
540135446Strhodes	conn = isc_mem_get(listener->mctx, sizeof(*conn));
541135446Strhodes	if (conn == NULL)
542135446Strhodes		return (ISC_R_NOMEMORY);
543135446Strhodes
544135446Strhodes	conn->sock = sock;
545135446Strhodes	isccc_ccmsg_init(listener->mctx, sock, &conn->ccmsg);
546135446Strhodes	conn->ccmsg_valid = ISC_TRUE;
547135446Strhodes	conn->sending = ISC_FALSE;
548135446Strhodes	conn->timer = NULL;
549135446Strhodes	isc_interval_set(&interval, 60, 0);
550135446Strhodes	result = isc_timer_create(ns_g_timermgr, isc_timertype_once,
551135446Strhodes				  NULL, &interval, listener->task,
552135446Strhodes				  control_timeout, conn, &conn->timer);
553135446Strhodes	if (result != ISC_R_SUCCESS)
554135446Strhodes		goto cleanup;
555135446Strhodes
556135446Strhodes	conn->listener = listener;
557135446Strhodes	conn->nonce = 0;
558135446Strhodes	ISC_LINK_INIT(conn, link);
559135446Strhodes
560135446Strhodes	result = isccc_ccmsg_readmessage(&conn->ccmsg, listener->task,
561135446Strhodes					 control_recvmessage, conn);
562135446Strhodes	if (result != ISC_R_SUCCESS)
563135446Strhodes		goto cleanup;
564135446Strhodes	isccc_ccmsg_setmaxsize(&conn->ccmsg, 2048);
565135446Strhodes
566135446Strhodes	ISC_LIST_APPEND(listener->connections, conn, link);
567135446Strhodes	return (ISC_R_SUCCESS);
568135446Strhodes
569135446Strhodes cleanup:
570135446Strhodes	isccc_ccmsg_invalidate(&conn->ccmsg);
571135446Strhodes	if (conn->timer != NULL)
572135446Strhodes		isc_timer_detach(&conn->timer);
573135446Strhodes	isc_mem_put(listener->mctx, conn, sizeof(*conn));
574135446Strhodes	return (result);
575135446Strhodes}
576135446Strhodes
577135446Strhodesstatic void
578135446Strhodescontrol_newconn(isc_task_t *task, isc_event_t *event) {
579135446Strhodes	isc_socket_newconnev_t *nevent = (isc_socket_newconnev_t *)event;
580135446Strhodes	controllistener_t *listener = event->ev_arg;
581135446Strhodes	isc_socket_t *sock;
582135446Strhodes	isc_sockaddr_t peeraddr;
583135446Strhodes	isc_result_t result;
584135446Strhodes
585135446Strhodes	UNUSED(task);
586135446Strhodes
587135446Strhodes	listener->listening = ISC_FALSE;
588135446Strhodes
589135446Strhodes	if (nevent->result != ISC_R_SUCCESS) {
590135446Strhodes		if (nevent->result == ISC_R_CANCELED) {
591135446Strhodes			shutdown_listener(listener);
592135446Strhodes			goto cleanup;
593135446Strhodes		}
594135446Strhodes		goto restart;
595135446Strhodes	}
596135446Strhodes
597135446Strhodes	sock = nevent->newsocket;
598135446Strhodes	(void)isc_socket_getpeername(sock, &peeraddr);
599135446Strhodes	if (!address_ok(&peeraddr, listener->acl)) {
600135446Strhodes		char socktext[ISC_SOCKADDR_FORMATSIZE];
601135446Strhodes		isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext));
602135446Strhodes		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
603135446Strhodes			      NS_LOGMODULE_CONTROL, ISC_LOG_WARNING,
604135446Strhodes			      "rejected command channel message from %s",
605135446Strhodes			      socktext);
606135446Strhodes		isc_socket_detach(&sock);
607135446Strhodes		goto restart;
608135446Strhodes	}
609135446Strhodes
610135446Strhodes	result = newconnection(listener, sock);
611135446Strhodes	if (result != ISC_R_SUCCESS) {
612135446Strhodes		char socktext[ISC_SOCKADDR_FORMATSIZE];
613135446Strhodes		isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext));
614135446Strhodes		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
615135446Strhodes			      NS_LOGMODULE_CONTROL, ISC_LOG_WARNING,
616135446Strhodes			      "dropped command channel from %s: %s",
617135446Strhodes			      socktext, isc_result_totext(result));
618135446Strhodes		isc_socket_detach(&sock);
619135446Strhodes		goto restart;
620135446Strhodes	}
621135446Strhodes
622135446Strhodes restart:
623135446Strhodes	control_next(listener);
624135446Strhodes cleanup:
625135446Strhodes	isc_event_free(&event);
626135446Strhodes}
627135446Strhodes
628135446Strhodesstatic void
629135446Strhodescontrols_shutdown(ns_controls_t *controls) {
630135446Strhodes	controllistener_t *listener;
631135446Strhodes	controllistener_t *next;
632135446Strhodes
633135446Strhodes	for (listener = ISC_LIST_HEAD(controls->listeners);
634135446Strhodes	     listener != NULL;
635135446Strhodes	     listener = next)
636135446Strhodes	{
637135446Strhodes		/*
638135446Strhodes		 * This is asynchronous.  As listeners shut down, they will
639135446Strhodes		 * call their callbacks.
640135446Strhodes		 */
641135446Strhodes		next = ISC_LIST_NEXT(listener, link);
642135446Strhodes		shutdown_listener(listener);
643135446Strhodes	}
644135446Strhodes}
645135446Strhodes
646135446Strhodesvoid
647135446Strhodesns_controls_shutdown(ns_controls_t *controls) {
648135446Strhodes	controls_shutdown(controls);
649135446Strhodes	controls->shuttingdown = ISC_TRUE;
650135446Strhodes}
651135446Strhodes
652135446Strhodesstatic isc_result_t
653165071Sdougbcfgkeylist_find(const cfg_obj_t *keylist, const char *keyname,
654165071Sdougb	        const cfg_obj_t **objp)
655165071Sdougb{
656165071Sdougb	const cfg_listelt_t *element;
657135446Strhodes	const char *str;
658165071Sdougb	const cfg_obj_t *obj;
659135446Strhodes
660135446Strhodes	for (element = cfg_list_first(keylist);
661135446Strhodes	     element != NULL;
662135446Strhodes	     element = cfg_list_next(element))
663135446Strhodes	{
664135446Strhodes		obj = cfg_listelt_value(element);
665135446Strhodes		str = cfg_obj_asstring(cfg_map_getname(obj));
666135446Strhodes		if (strcasecmp(str, keyname) == 0)
667135446Strhodes			break;
668135446Strhodes	}
669135446Strhodes	if (element == NULL)
670135446Strhodes		return (ISC_R_NOTFOUND);
671135446Strhodes	obj = cfg_listelt_value(element);
672135446Strhodes	*objp = obj;
673135446Strhodes	return (ISC_R_SUCCESS);
674135446Strhodes}
675135446Strhodes
676135446Strhodesstatic isc_result_t
677165071Sdougbcontrolkeylist_fromcfg(const cfg_obj_t *keylist, isc_mem_t *mctx,
678135446Strhodes		       controlkeylist_t *keyids)
679135446Strhodes{
680165071Sdougb	const cfg_listelt_t *element;
681135446Strhodes	char *newstr = NULL;
682135446Strhodes	const char *str;
683165071Sdougb	const cfg_obj_t *obj;
684135446Strhodes	controlkey_t *key = NULL;
685135446Strhodes
686135446Strhodes	for (element = cfg_list_first(keylist);
687135446Strhodes	     element != NULL;
688135446Strhodes	     element = cfg_list_next(element))
689135446Strhodes	{
690135446Strhodes		obj = cfg_listelt_value(element);
691135446Strhodes		str = cfg_obj_asstring(obj);
692135446Strhodes		newstr = isc_mem_strdup(mctx, str);
693135446Strhodes		if (newstr == NULL)
694135446Strhodes			goto cleanup;
695135446Strhodes		key = isc_mem_get(mctx, sizeof(*key));
696135446Strhodes		if (key == NULL)
697135446Strhodes			goto cleanup;
698135446Strhodes		key->keyname = newstr;
699135446Strhodes		key->secret.base = NULL;
700135446Strhodes		key->secret.length = 0;
701135446Strhodes		ISC_LINK_INIT(key, link);
702135446Strhodes		ISC_LIST_APPEND(*keyids, key, link);
703135446Strhodes		key = NULL;
704135446Strhodes		newstr = NULL;
705135446Strhodes	}
706135446Strhodes	return (ISC_R_SUCCESS);
707135446Strhodes
708135446Strhodes cleanup:
709135446Strhodes	if (newstr != NULL)
710135446Strhodes		isc_mem_free(mctx, newstr);
711135446Strhodes	if (key != NULL)
712135446Strhodes		isc_mem_put(mctx, key, sizeof(*key));
713135446Strhodes	free_controlkeylist(keyids, mctx);
714135446Strhodes	return (ISC_R_NOMEMORY);
715135446Strhodes}
716135446Strhodes
717135446Strhodesstatic void
718165071Sdougbregister_keys(const cfg_obj_t *control, const cfg_obj_t *keylist,
719135446Strhodes	      controlkeylist_t *keyids, isc_mem_t *mctx, const char *socktext)
720135446Strhodes{
721135446Strhodes	controlkey_t *keyid, *next;
722165071Sdougb	const cfg_obj_t *keydef;
723135446Strhodes	char secret[1024];
724135446Strhodes	isc_buffer_t b;
725135446Strhodes	isc_result_t result;
726135446Strhodes
727135446Strhodes	/*
728135446Strhodes	 * Find the keys corresponding to the keyids used by this listener.
729135446Strhodes	 */
730135446Strhodes	for (keyid = ISC_LIST_HEAD(*keyids); keyid != NULL; keyid = next) {
731135446Strhodes		next = ISC_LIST_NEXT(keyid, link);
732135446Strhodes
733135446Strhodes		result = cfgkeylist_find(keylist, keyid->keyname, &keydef);
734135446Strhodes		if (result != ISC_R_SUCCESS) {
735135446Strhodes			cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING,
736135446Strhodes				    "couldn't find key '%s' for use with "
737135446Strhodes				    "command channel %s",
738135446Strhodes				    keyid->keyname, socktext);
739135446Strhodes			ISC_LIST_UNLINK(*keyids, keyid, link);
740135446Strhodes			free_controlkey(keyid, mctx);
741135446Strhodes		} else {
742165071Sdougb			const cfg_obj_t *algobj = NULL;
743165071Sdougb			const cfg_obj_t *secretobj = NULL;
744165071Sdougb			const char *algstr = NULL;
745165071Sdougb			const char *secretstr = NULL;
746135446Strhodes
747135446Strhodes			(void)cfg_map_get(keydef, "algorithm", &algobj);
748135446Strhodes			(void)cfg_map_get(keydef, "secret", &secretobj);
749135446Strhodes			INSIST(algobj != NULL && secretobj != NULL);
750135446Strhodes
751135446Strhodes			algstr = cfg_obj_asstring(algobj);
752135446Strhodes			secretstr = cfg_obj_asstring(secretobj);
753135446Strhodes
754135446Strhodes			if (ns_config_getkeyalgorithm(algstr, NULL) !=
755135446Strhodes			    ISC_R_SUCCESS)
756135446Strhodes			{
757135446Strhodes				cfg_obj_log(control, ns_g_lctx,
758135446Strhodes					    ISC_LOG_WARNING,
759135446Strhodes					    "unsupported algorithm '%s' in "
760135446Strhodes					    "key '%s' for use with command "
761135446Strhodes					    "channel %s",
762135446Strhodes					    algstr, keyid->keyname, socktext);
763135446Strhodes				ISC_LIST_UNLINK(*keyids, keyid, link);
764135446Strhodes				free_controlkey(keyid, mctx);
765135446Strhodes				continue;
766135446Strhodes			}
767135446Strhodes
768135446Strhodes			isc_buffer_init(&b, secret, sizeof(secret));
769135446Strhodes			result = isc_base64_decodestring(secretstr, &b);
770135446Strhodes
771135446Strhodes			if (result != ISC_R_SUCCESS) {
772135446Strhodes				cfg_obj_log(keydef, ns_g_lctx, ISC_LOG_WARNING,
773135446Strhodes					    "secret for key '%s' on "
774135446Strhodes					    "command channel %s: %s",
775135446Strhodes					    keyid->keyname, socktext,
776135446Strhodes					    isc_result_totext(result));
777135446Strhodes				ISC_LIST_UNLINK(*keyids, keyid, link);
778135446Strhodes				free_controlkey(keyid, mctx);
779135446Strhodes				continue;
780135446Strhodes			}
781135446Strhodes
782135446Strhodes			keyid->secret.length = isc_buffer_usedlength(&b);
783135446Strhodes			keyid->secret.base = isc_mem_get(mctx,
784135446Strhodes							 keyid->secret.length);
785135446Strhodes			if (keyid->secret.base == NULL) {
786135446Strhodes				cfg_obj_log(keydef, ns_g_lctx, ISC_LOG_WARNING,
787135446Strhodes					   "couldn't register key '%s': "
788135446Strhodes					   "out of memory", keyid->keyname);
789135446Strhodes				ISC_LIST_UNLINK(*keyids, keyid, link);
790135446Strhodes				free_controlkey(keyid, mctx);
791135446Strhodes				break;
792135446Strhodes			}
793135446Strhodes			memcpy(keyid->secret.base, isc_buffer_base(&b),
794135446Strhodes			       keyid->secret.length);
795135446Strhodes		}
796135446Strhodes	}
797135446Strhodes}
798135446Strhodes
799135446Strhodes#define CHECK(x) \
800135446Strhodes	do { \
801135446Strhodes		 result = (x); \
802135446Strhodes		 if (result != ISC_R_SUCCESS) \
803135446Strhodes			goto cleanup; \
804135446Strhodes	} while (0)
805135446Strhodes
806135446Strhodesstatic isc_result_t
807135446Strhodesget_rndckey(isc_mem_t *mctx, controlkeylist_t *keyids) {
808135446Strhodes	isc_result_t result;
809135446Strhodes	cfg_parser_t *pctx = NULL;
810135446Strhodes	cfg_obj_t *config = NULL;
811165071Sdougb	const cfg_obj_t *key = NULL;
812165071Sdougb	const cfg_obj_t *algobj = NULL;
813165071Sdougb	const cfg_obj_t *secretobj = NULL;
814165071Sdougb	const char *algstr = NULL;
815165071Sdougb	const char *secretstr = NULL;
816135446Strhodes	controlkey_t *keyid = NULL;
817135446Strhodes	char secret[1024];
818135446Strhodes	isc_buffer_t b;
819135446Strhodes
820135446Strhodes	CHECK(cfg_parser_create(mctx, ns_g_lctx, &pctx));
821135446Strhodes	CHECK(cfg_parse_file(pctx, ns_g_keyfile, &cfg_type_rndckey, &config));
822135446Strhodes	CHECK(cfg_map_get(config, "key", &key));
823135446Strhodes
824135446Strhodes	keyid = isc_mem_get(mctx, sizeof(*keyid));
825135446Strhodes	if (keyid == NULL)
826135446Strhodes		CHECK(ISC_R_NOMEMORY);
827135446Strhodes	keyid->keyname = isc_mem_strdup(mctx,
828135446Strhodes					cfg_obj_asstring(cfg_map_getname(key)));
829135446Strhodes	keyid->secret.base = NULL;
830135446Strhodes	keyid->secret.length = 0;
831135446Strhodes	ISC_LINK_INIT(keyid, link);
832135446Strhodes	if (keyid->keyname == NULL)
833135446Strhodes		CHECK(ISC_R_NOMEMORY);
834135446Strhodes
835135446Strhodes	CHECK(bind9_check_key(key, ns_g_lctx));
836135446Strhodes
837135446Strhodes	(void)cfg_map_get(key, "algorithm", &algobj);
838135446Strhodes	(void)cfg_map_get(key, "secret", &secretobj);
839135446Strhodes	INSIST(algobj != NULL && secretobj != NULL);
840135446Strhodes
841135446Strhodes	algstr = cfg_obj_asstring(algobj);
842135446Strhodes	secretstr = cfg_obj_asstring(secretobj);
843135446Strhodes
844135446Strhodes	if (ns_config_getkeyalgorithm(algstr, NULL) != ISC_R_SUCCESS) {
845135446Strhodes		cfg_obj_log(key, ns_g_lctx,
846135446Strhodes			    ISC_LOG_WARNING,
847135446Strhodes			    "unsupported algorithm '%s' in "
848135446Strhodes			    "key '%s' for use with command "
849135446Strhodes			    "channel",
850135446Strhodes			    algstr, keyid->keyname);
851135446Strhodes		goto cleanup;
852135446Strhodes	}
853135446Strhodes
854135446Strhodes	isc_buffer_init(&b, secret, sizeof(secret));
855135446Strhodes	result = isc_base64_decodestring(secretstr, &b);
856135446Strhodes
857135446Strhodes	if (result != ISC_R_SUCCESS) {
858135446Strhodes		cfg_obj_log(key, ns_g_lctx, ISC_LOG_WARNING,
859135446Strhodes			    "secret for key '%s' on command channel: %s",
860135446Strhodes			    keyid->keyname, isc_result_totext(result));
861135446Strhodes		CHECK(result);
862135446Strhodes	}
863135446Strhodes
864135446Strhodes	keyid->secret.length = isc_buffer_usedlength(&b);
865135446Strhodes	keyid->secret.base = isc_mem_get(mctx,
866135446Strhodes					 keyid->secret.length);
867135446Strhodes	if (keyid->secret.base == NULL) {
868135446Strhodes		cfg_obj_log(key, ns_g_lctx, ISC_LOG_WARNING,
869135446Strhodes			   "couldn't register key '%s': "
870135446Strhodes			   "out of memory", keyid->keyname);
871135446Strhodes		CHECK(ISC_R_NOMEMORY);
872135446Strhodes	}
873135446Strhodes	memcpy(keyid->secret.base, isc_buffer_base(&b),
874135446Strhodes	       keyid->secret.length);
875135446Strhodes	ISC_LIST_APPEND(*keyids, keyid, link);
876135446Strhodes	keyid = NULL;
877135446Strhodes	result = ISC_R_SUCCESS;
878135446Strhodes
879135446Strhodes  cleanup:
880135446Strhodes	if (keyid != NULL)
881135446Strhodes		free_controlkey(keyid, mctx);
882135446Strhodes	if (config != NULL)
883135446Strhodes		cfg_obj_destroy(pctx, &config);
884135446Strhodes	if (pctx != NULL)
885135446Strhodes		cfg_parser_destroy(&pctx);
886135446Strhodes	return (result);
887135446Strhodes}
888135446Strhodes
889135446Strhodes/*
890135446Strhodes * Ensures that both '*global_keylistp' and '*control_keylistp' are
891135446Strhodes * valid or both are NULL.
892135446Strhodes */
893135446Strhodesstatic void
894165071Sdougbget_key_info(const cfg_obj_t *config, const cfg_obj_t *control,
895165071Sdougb	     const cfg_obj_t **global_keylistp,
896165071Sdougb	     const cfg_obj_t **control_keylistp)
897135446Strhodes{
898135446Strhodes	isc_result_t result;
899165071Sdougb	const cfg_obj_t *control_keylist = NULL;
900165071Sdougb	const cfg_obj_t *global_keylist = NULL;
901135446Strhodes
902135446Strhodes	REQUIRE(global_keylistp != NULL && *global_keylistp == NULL);
903135446Strhodes	REQUIRE(control_keylistp != NULL && *control_keylistp == NULL);
904135446Strhodes
905135446Strhodes	control_keylist = cfg_tuple_get(control, "keys");
906135446Strhodes
907135446Strhodes	if (!cfg_obj_isvoid(control_keylist) &&
908135446Strhodes	    cfg_list_first(control_keylist) != NULL) {
909135446Strhodes		result = cfg_map_get(config, "key", &global_keylist);
910135446Strhodes
911135446Strhodes		if (result == ISC_R_SUCCESS) {
912135446Strhodes			*global_keylistp = global_keylist;
913135446Strhodes			*control_keylistp = control_keylist;
914135446Strhodes		}
915135446Strhodes	}
916135446Strhodes}
917135446Strhodes
918135446Strhodesstatic void
919165071Sdougbupdate_listener(ns_controls_t *cp, controllistener_t **listenerp,
920165071Sdougb		const cfg_obj_t *control, const cfg_obj_t *config,
921165071Sdougb		isc_sockaddr_t *addr, ns_aclconfctx_t *aclconfctx,
922165071Sdougb		const char *socktext)
923135446Strhodes{
924135446Strhodes	controllistener_t *listener;
925165071Sdougb	const cfg_obj_t *allow;
926165071Sdougb	const cfg_obj_t *global_keylist = NULL;
927165071Sdougb	const cfg_obj_t *control_keylist = NULL;
928135446Strhodes	dns_acl_t *new_acl = NULL;
929135446Strhodes	controlkeylist_t keys;
930135446Strhodes	isc_result_t result = ISC_R_SUCCESS;
931135446Strhodes
932135446Strhodes	for (listener = ISC_LIST_HEAD(cp->listeners);
933135446Strhodes	     listener != NULL;
934135446Strhodes	     listener = ISC_LIST_NEXT(listener, link))
935135446Strhodes		if (isc_sockaddr_equal(addr, &listener->address))
936135446Strhodes			break;
937135446Strhodes
938135446Strhodes	if (listener == NULL) {
939135446Strhodes		*listenerp = NULL;
940135446Strhodes		return;
941135446Strhodes	}
942135446Strhodes
943135446Strhodes	/*
944135446Strhodes	 * There is already a listener for this sockaddr.
945135446Strhodes	 * Update the access list and key information.
946135446Strhodes	 *
947135446Strhodes	 * First try to deal with the key situation.  There are a few
948135446Strhodes	 * possibilities:
949135446Strhodes	 *  (a)	It had an explicit keylist and still has an explicit keylist.
950135446Strhodes	 *  (b)	It had an automagic key and now has an explicit keylist.
951135446Strhodes	 *  (c)	It had an explicit keylist and now needs an automagic key.
952135446Strhodes	 *  (d) It has an automagic key and still needs the automagic key.
953135446Strhodes	 *
954135446Strhodes	 * (c) and (d) are the annoying ones.  The caller needs to know
955135446Strhodes	 * that it should use the automagic configuration for key information
956135446Strhodes	 * in place of the named.conf configuration.
957135446Strhodes	 *
958135446Strhodes	 * XXXDCL There is one other hazard that has not been dealt with,
959135446Strhodes	 * the problem that if a key change is being caused by a control
960135446Strhodes	 * channel reload, then the response will be with the new key
961135446Strhodes	 * and not able to be decrypted by the client.
962135446Strhodes	 */
963135446Strhodes	if (control != NULL)
964135446Strhodes		get_key_info(config, control, &global_keylist,
965135446Strhodes			     &control_keylist);
966135446Strhodes
967135446Strhodes	if (control_keylist != NULL) {
968135446Strhodes		INSIST(global_keylist != NULL);
969135446Strhodes
970135446Strhodes		ISC_LIST_INIT(keys);
971135446Strhodes		result = controlkeylist_fromcfg(control_keylist,
972135446Strhodes						listener->mctx, &keys);
973135446Strhodes		if (result == ISC_R_SUCCESS) {
974135446Strhodes			free_controlkeylist(&listener->keys, listener->mctx);
975135446Strhodes			listener->keys = keys;
976135446Strhodes			register_keys(control, global_keylist, &listener->keys,
977135446Strhodes				      listener->mctx, socktext);
978135446Strhodes		}
979135446Strhodes	} else {
980135446Strhodes		free_controlkeylist(&listener->keys, listener->mctx);
981135446Strhodes		result = get_rndckey(listener->mctx, &listener->keys);
982135446Strhodes	}
983135446Strhodes
984165071Sdougb	if (result != ISC_R_SUCCESS && global_keylist != NULL) {
985135446Strhodes		/*
986135446Strhodes		 * This message might be a little misleading since the
987135446Strhodes		 * "new keys" might in fact be identical to the old ones,
988135446Strhodes		 * but tracking whether they are identical just for the
989135446Strhodes		 * sake of avoiding this message would be too much trouble.
990135446Strhodes		 */
991165071Sdougb		if (control != NULL)
992165071Sdougb			cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING,
993165071Sdougb				    "couldn't install new keys for "
994165071Sdougb				    "command channel %s: %s",
995165071Sdougb				    socktext, isc_result_totext(result));
996165071Sdougb		else
997165071Sdougb			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
998165071Sdougb				      NS_LOGMODULE_CONTROL, ISC_LOG_WARNING,
999165071Sdougb				      "couldn't install new keys for "
1000165071Sdougb				      "command channel %s: %s",
1001165071Sdougb				      socktext, isc_result_totext(result));
1002165071Sdougb	}
1003135446Strhodes
1004135446Strhodes	/*
1005135446Strhodes	 * Now, keep the old access list unless a new one can be made.
1006135446Strhodes	 */
1007135446Strhodes	if (control != NULL) {
1008135446Strhodes		allow = cfg_tuple_get(control, "allow");
1009135446Strhodes		result = ns_acl_fromconfig(allow, config, aclconfctx,
1010135446Strhodes					   listener->mctx, &new_acl);
1011135446Strhodes	} else {
1012135446Strhodes		result = dns_acl_any(listener->mctx, &new_acl);
1013135446Strhodes	}
1014135446Strhodes
1015135446Strhodes	if (result == ISC_R_SUCCESS) {
1016135446Strhodes		dns_acl_detach(&listener->acl);
1017135446Strhodes		dns_acl_attach(new_acl, &listener->acl);
1018135446Strhodes		dns_acl_detach(&new_acl);
1019135446Strhodes		/* XXXDCL say the old acl is still used? */
1020165071Sdougb	} else if (control != NULL)
1021135446Strhodes		cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING,
1022135446Strhodes			    "couldn't install new acl for "
1023135446Strhodes			    "command channel %s: %s",
1024135446Strhodes			    socktext, isc_result_totext(result));
1025165071Sdougb	else
1026165071Sdougb		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
1027165071Sdougb			      NS_LOGMODULE_CONTROL, ISC_LOG_WARNING,
1028165071Sdougb			      "couldn't install new acl for "
1029165071Sdougb			      "command channel %s: %s",
1030165071Sdougb			      socktext, isc_result_totext(result));
1031135446Strhodes
1032135446Strhodes	*listenerp = listener;
1033135446Strhodes}
1034135446Strhodes
1035135446Strhodesstatic void
1036135446Strhodesadd_listener(ns_controls_t *cp, controllistener_t **listenerp,
1037165071Sdougb	     const cfg_obj_t *control, const cfg_obj_t *config,
1038165071Sdougb	     isc_sockaddr_t *addr, ns_aclconfctx_t *aclconfctx,
1039165071Sdougb	     const char *socktext)
1040135446Strhodes{
1041135446Strhodes	isc_mem_t *mctx = cp->server->mctx;
1042135446Strhodes	controllistener_t *listener;
1043165071Sdougb	const cfg_obj_t *allow;
1044165071Sdougb	const cfg_obj_t *global_keylist = NULL;
1045165071Sdougb	const cfg_obj_t *control_keylist = NULL;
1046135446Strhodes	dns_acl_t *new_acl = NULL;
1047135446Strhodes	isc_result_t result = ISC_R_SUCCESS;
1048135446Strhodes
1049135446Strhodes	listener = isc_mem_get(mctx, sizeof(*listener));
1050135446Strhodes	if (listener == NULL)
1051135446Strhodes		result = ISC_R_NOMEMORY;
1052135446Strhodes
1053135446Strhodes	if (result == ISC_R_SUCCESS) {
1054135446Strhodes		listener->controls = cp;
1055135446Strhodes		listener->mctx = mctx;
1056135446Strhodes		listener->task = cp->server->task;
1057135446Strhodes		listener->address = *addr;
1058135446Strhodes		listener->sock = NULL;
1059135446Strhodes		listener->listening = ISC_FALSE;
1060135446Strhodes		listener->exiting = ISC_FALSE;
1061135446Strhodes		listener->acl = NULL;
1062135446Strhodes		ISC_LINK_INIT(listener, link);
1063135446Strhodes		ISC_LIST_INIT(listener->keys);
1064135446Strhodes		ISC_LIST_INIT(listener->connections);
1065135446Strhodes
1066135446Strhodes		/*
1067135446Strhodes		 * Make the acl.
1068135446Strhodes		 */
1069135446Strhodes		if (control != NULL) {
1070135446Strhodes			allow = cfg_tuple_get(control, "allow");
1071135446Strhodes			result = ns_acl_fromconfig(allow, config, aclconfctx,
1072135446Strhodes						   mctx, &new_acl);
1073135446Strhodes		} else {
1074135446Strhodes			result = dns_acl_any(mctx, &new_acl);
1075135446Strhodes		}
1076135446Strhodes	}
1077135446Strhodes
1078135446Strhodes	if (result == ISC_R_SUCCESS) {
1079135446Strhodes		dns_acl_attach(new_acl, &listener->acl);
1080135446Strhodes		dns_acl_detach(&new_acl);
1081135446Strhodes
1082135446Strhodes		if (config != NULL)
1083135446Strhodes			get_key_info(config, control, &global_keylist,
1084135446Strhodes				     &control_keylist);
1085135446Strhodes
1086135446Strhodes		if (control_keylist != NULL) {
1087135446Strhodes			result = controlkeylist_fromcfg(control_keylist,
1088135446Strhodes							listener->mctx,
1089135446Strhodes							&listener->keys);
1090135446Strhodes			if (result == ISC_R_SUCCESS)
1091135446Strhodes				register_keys(control, global_keylist,
1092135446Strhodes					      &listener->keys,
1093135446Strhodes					      listener->mctx, socktext);
1094135446Strhodes		} else
1095135446Strhodes			result = get_rndckey(mctx, &listener->keys);
1096135446Strhodes
1097135446Strhodes		if (result != ISC_R_SUCCESS && control != NULL)
1098135446Strhodes			cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING,
1099135446Strhodes				    "couldn't install keys for "
1100135446Strhodes				    "command channel %s: %s",
1101135446Strhodes				    socktext, isc_result_totext(result));
1102135446Strhodes	}
1103135446Strhodes
1104135446Strhodes	if (result == ISC_R_SUCCESS) {
1105135446Strhodes		int pf = isc_sockaddr_pf(&listener->address);
1106135446Strhodes		if ((pf == AF_INET && isc_net_probeipv4() != ISC_R_SUCCESS) ||
1107135446Strhodes		    (pf == AF_INET6 && isc_net_probeipv6() != ISC_R_SUCCESS))
1108135446Strhodes			result = ISC_R_FAMILYNOSUPPORT;
1109135446Strhodes	}
1110135446Strhodes
1111135446Strhodes	if (result == ISC_R_SUCCESS)
1112135446Strhodes		result = isc_socket_create(ns_g_socketmgr,
1113135446Strhodes					   isc_sockaddr_pf(&listener->address),
1114135446Strhodes					   isc_sockettype_tcp,
1115135446Strhodes					   &listener->sock);
1116135446Strhodes
1117135446Strhodes	if (result == ISC_R_SUCCESS)
1118135446Strhodes		result = isc_socket_bind(listener->sock,
1119135446Strhodes					 &listener->address);
1120135446Strhodes
1121135446Strhodes	if (result == ISC_R_SUCCESS)
1122135446Strhodes		result = control_listen(listener);
1123135446Strhodes
1124135446Strhodes	if (result == ISC_R_SUCCESS)
1125135446Strhodes		result = control_accept(listener);
1126135446Strhodes
1127135446Strhodes	if (result == ISC_R_SUCCESS) {
1128135446Strhodes		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
1129135446Strhodes			      NS_LOGMODULE_CONTROL, ISC_LOG_NOTICE,
1130135446Strhodes			      "command channel listening on %s", socktext);
1131135446Strhodes		*listenerp = listener;
1132135446Strhodes
1133135446Strhodes	} else {
1134135446Strhodes		if (listener != NULL) {
1135135446Strhodes			listener->exiting = ISC_TRUE;
1136135446Strhodes			free_listener(listener);
1137135446Strhodes		}
1138135446Strhodes
1139135446Strhodes		if (control != NULL)
1140135446Strhodes			cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING,
1141135446Strhodes				    "couldn't add command channel %s: %s",
1142135446Strhodes				    socktext, isc_result_totext(result));
1143135446Strhodes		else
1144135446Strhodes			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
1145135446Strhodes				      NS_LOGMODULE_CONTROL, ISC_LOG_NOTICE,
1146135446Strhodes				      "couldn't add command channel %s: %s",
1147135446Strhodes				      socktext, isc_result_totext(result));
1148135446Strhodes
1149135446Strhodes		*listenerp = NULL;
1150135446Strhodes	}
1151135446Strhodes
1152135446Strhodes	/* XXXDCL return error results? fail hard? */
1153135446Strhodes}
1154135446Strhodes
1155135446Strhodesisc_result_t
1156165071Sdougbns_controls_configure(ns_controls_t *cp, const cfg_obj_t *config,
1157135446Strhodes		      ns_aclconfctx_t *aclconfctx)
1158135446Strhodes{
1159135446Strhodes	controllistener_t *listener;
1160135446Strhodes	controllistenerlist_t new_listeners;
1161165071Sdougb	const cfg_obj_t *controlslist = NULL;
1162165071Sdougb	const cfg_listelt_t *element, *element2;
1163135446Strhodes	char socktext[ISC_SOCKADDR_FORMATSIZE];
1164135446Strhodes
1165135446Strhodes	ISC_LIST_INIT(new_listeners);
1166135446Strhodes
1167135446Strhodes	/*
1168135446Strhodes	 * Get the list of named.conf 'controls' statements.
1169135446Strhodes	 */
1170135446Strhodes	(void)cfg_map_get(config, "controls", &controlslist);
1171135446Strhodes
1172135446Strhodes	/*
1173135446Strhodes	 * Run through the new control channel list, noting sockets that
1174135446Strhodes	 * are already being listened on and moving them to the new list.
1175135446Strhodes	 *
1176135446Strhodes	 * Identifying duplicate addr/port combinations is left to either
1177135446Strhodes	 * the underlying config code, or to the bind attempt getting an
1178135446Strhodes	 * address-in-use error.
1179135446Strhodes	 */
1180135446Strhodes	if (controlslist != NULL) {
1181135446Strhodes		for (element = cfg_list_first(controlslist);
1182135446Strhodes		     element != NULL;
1183135446Strhodes		     element = cfg_list_next(element)) {
1184165071Sdougb			const cfg_obj_t *controls;
1185165071Sdougb			const cfg_obj_t *inetcontrols = NULL;
1186135446Strhodes
1187135446Strhodes			controls = cfg_listelt_value(element);
1188135446Strhodes			(void)cfg_map_get(controls, "inet", &inetcontrols);
1189135446Strhodes			if (inetcontrols == NULL)
1190135446Strhodes				continue;
1191135446Strhodes
1192135446Strhodes			for (element2 = cfg_list_first(inetcontrols);
1193135446Strhodes			     element2 != NULL;
1194135446Strhodes			     element2 = cfg_list_next(element2)) {
1195165071Sdougb				const cfg_obj_t *control;
1196165071Sdougb				const cfg_obj_t *obj;
1197165071Sdougb				isc_sockaddr_t addr;
1198135446Strhodes
1199135446Strhodes				/*
1200135446Strhodes				 * The parser handles BIND 8 configuration file
1201135446Strhodes				 * syntax, so it allows unix phrases as well
1202135446Strhodes				 * inet phrases with no keys{} clause.
1203135446Strhodes				 *
1204135446Strhodes				 * "unix" phrases have been reported as
1205135446Strhodes				 * unsupported by the parser.
1206135446Strhodes				 */
1207135446Strhodes				control = cfg_listelt_value(element2);
1208135446Strhodes
1209135446Strhodes				obj = cfg_tuple_get(control, "address");
1210165071Sdougb				addr = *cfg_obj_assockaddr(obj);
1211165071Sdougb				if (isc_sockaddr_getport(&addr) == 0)
1212165071Sdougb					isc_sockaddr_setport(&addr,
1213135446Strhodes							     NS_CONTROL_PORT);
1214135446Strhodes
1215165071Sdougb				isc_sockaddr_format(&addr, socktext,
1216135446Strhodes						    sizeof(socktext));
1217135446Strhodes
1218135446Strhodes				isc_log_write(ns_g_lctx,
1219135446Strhodes					      NS_LOGCATEGORY_GENERAL,
1220135446Strhodes					      NS_LOGMODULE_CONTROL,
1221135446Strhodes					      ISC_LOG_DEBUG(9),
1222135446Strhodes					      "processing control channel %s",
1223135446Strhodes					      socktext);
1224135446Strhodes
1225135446Strhodes				update_listener(cp, &listener, control, config,
1226165071Sdougb						&addr, aclconfctx, socktext);
1227135446Strhodes
1228135446Strhodes				if (listener != NULL)
1229135446Strhodes					/*
1230135446Strhodes					 * Remove the listener from the old
1231135446Strhodes					 * list, so it won't be shut down.
1232135446Strhodes					 */
1233135446Strhodes					ISC_LIST_UNLINK(cp->listeners,
1234135446Strhodes							listener, link);
1235135446Strhodes				else
1236135446Strhodes					/*
1237135446Strhodes					 * This is a new listener.
1238135446Strhodes					 */
1239135446Strhodes					add_listener(cp, &listener, control,
1240165071Sdougb						     config, &addr, aclconfctx,
1241135446Strhodes						     socktext);
1242135446Strhodes
1243135446Strhodes				if (listener != NULL)
1244135446Strhodes					ISC_LIST_APPEND(new_listeners,
1245135446Strhodes							listener, link);
1246135446Strhodes			}
1247135446Strhodes		}
1248135446Strhodes	} else {
1249135446Strhodes		int i;
1250135446Strhodes
1251135446Strhodes		for (i = 0; i < 2; i++) {
1252135446Strhodes			isc_sockaddr_t addr;
1253135446Strhodes
1254135446Strhodes			if (i == 0) {
1255135446Strhodes				struct in_addr localhost;
1256135446Strhodes
1257135446Strhodes				if (isc_net_probeipv4() != ISC_R_SUCCESS)
1258135446Strhodes					continue;
1259135446Strhodes				localhost.s_addr = htonl(INADDR_LOOPBACK);
1260135446Strhodes				isc_sockaddr_fromin(&addr, &localhost, 0);
1261135446Strhodes			} else {
1262135446Strhodes				if (isc_net_probeipv6() != ISC_R_SUCCESS)
1263135446Strhodes					continue;
1264135446Strhodes				isc_sockaddr_fromin6(&addr,
1265135446Strhodes						     &in6addr_loopback, 0);
1266135446Strhodes			}
1267135446Strhodes			isc_sockaddr_setport(&addr, NS_CONTROL_PORT);
1268135446Strhodes
1269135446Strhodes			isc_sockaddr_format(&addr, socktext, sizeof(socktext));
1270135446Strhodes
1271135446Strhodes			update_listener(cp, &listener, NULL, NULL,
1272135446Strhodes					&addr, NULL, socktext);
1273135446Strhodes
1274135446Strhodes			if (listener != NULL)
1275135446Strhodes				/*
1276135446Strhodes				 * Remove the listener from the old
1277135446Strhodes				 * list, so it won't be shut down.
1278135446Strhodes				 */
1279135446Strhodes				ISC_LIST_UNLINK(cp->listeners,
1280135446Strhodes						listener, link);
1281135446Strhodes			else
1282135446Strhodes				/*
1283135446Strhodes				 * This is a new listener.
1284135446Strhodes				 */
1285135446Strhodes				add_listener(cp, &listener, NULL, NULL,
1286135446Strhodes					     &addr, NULL, socktext);
1287135446Strhodes
1288135446Strhodes			if (listener != NULL)
1289135446Strhodes				ISC_LIST_APPEND(new_listeners,
1290135446Strhodes						listener, link);
1291135446Strhodes		}
1292135446Strhodes	}
1293135446Strhodes
1294135446Strhodes	/*
1295135446Strhodes	 * ns_control_shutdown() will stop whatever is on the global
1296135446Strhodes	 * listeners list, which currently only has whatever sockaddrs
1297135446Strhodes	 * were in the previous configuration (if any) that do not
1298135446Strhodes	 * remain in the current configuration.
1299135446Strhodes	 */
1300135446Strhodes	controls_shutdown(cp);
1301135446Strhodes
1302135446Strhodes	/*
1303135446Strhodes	 * Put all of the valid listeners on the listeners list.
1304135446Strhodes	 * Anything already on listeners in the process of shutting
1305135446Strhodes	 * down will be taken care of by listen_done().
1306135446Strhodes	 */
1307135446Strhodes	ISC_LIST_APPENDLIST(cp->listeners, new_listeners, link);
1308135446Strhodes	return (ISC_R_SUCCESS);
1309135446Strhodes}
1310135446Strhodes
1311135446Strhodesisc_result_t
1312135446Strhodesns_controls_create(ns_server_t *server, ns_controls_t **ctrlsp) {
1313135446Strhodes	isc_mem_t *mctx = server->mctx;
1314135446Strhodes	isc_result_t result;
1315135446Strhodes	ns_controls_t *controls = isc_mem_get(mctx, sizeof(*controls));
1316135446Strhodes
1317135446Strhodes	if (controls == NULL)
1318135446Strhodes		return (ISC_R_NOMEMORY);
1319135446Strhodes	controls->server = server;
1320135446Strhodes	ISC_LIST_INIT(controls->listeners);
1321135446Strhodes	controls->shuttingdown = ISC_FALSE;
1322135446Strhodes	controls->symtab = NULL;
1323135446Strhodes	result = isccc_cc_createsymtab(&controls->symtab);
1324135446Strhodes	if (result != ISC_R_SUCCESS) {
1325135446Strhodes		isc_mem_put(server->mctx, controls, sizeof(*controls));
1326135446Strhodes		return (result);
1327135446Strhodes	}
1328135446Strhodes	*ctrlsp = controls;
1329135446Strhodes	return (ISC_R_SUCCESS);
1330135446Strhodes}
1331135446Strhodes
1332135446Strhodesvoid
1333135446Strhodesns_controls_destroy(ns_controls_t **ctrlsp) {
1334135446Strhodes	ns_controls_t *controls = *ctrlsp;
1335135446Strhodes
1336135446Strhodes	REQUIRE(ISC_LIST_EMPTY(controls->listeners));
1337135446Strhodes
1338135446Strhodes	isccc_symtab_destroy(&controls->symtab);
1339135446Strhodes	isc_mem_put(controls->server->mctx, controls, sizeof(*controls));
1340135446Strhodes	*ctrlsp = NULL;
1341135446Strhodes}
1342