From 1b4e6163bed546ca7f8ad186f3eabfebacc36bc1 Mon Sep 17 00:00:00 2001 From: Brian Wellington Date: Tue, 27 Mar 2001 00:45:13 +0000 Subject: [PATCH] oops, missed a few files. --- bin/named/control.c | 102 ++++ bin/named/controlconf.c | 957 ++++++++++++++++++++++++++++++ bin/named/include/named/control.h | 35 ++ 3 files changed, 1094 insertions(+) create mode 100644 bin/named/control.c create mode 100644 bin/named/controlconf.c create mode 100644 bin/named/include/named/control.h diff --git a/bin/named/control.c b/bin/named/control.c new file mode 100644 index 0000000000..f82979f55c --- /dev/null +++ b/bin/named/control.c @@ -0,0 +1,102 @@ +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +static isc_boolean_t +command_compare(const char *text, const char *command) { + if (strncasecmp(text, command, strlen(command)) == 0 && + (text[strlen(command)] == 0 || text[strlen(command)] == ' ')) + return (ISC_TRUE); + return (ISC_FALSE); +} + +/* + * This is the function that is called to process an incoming command when a + * message is received. It is called once for each name/value pair in the + * message's object value list or something. + */ +isc_result_t +ns_control_docommand(isccc_sexpr_t *message) { + isccc_sexpr_t *data; + char *command; + isc_result_t result; + + data = isccc_alist_lookup(message, "_data"); + if (data == NULL) { + /* + * No data section. + */ + return (ISC_R_FAILURE); + } + + result = isccc_cc_lookupstring(data, "type", &command); + if (result != ISC_R_SUCCESS) { + /* + * We have no idea what this is. + */ + return (result); + } + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_DEBUG(1), + "received control channel command '%s'", + command); + + /* + * Compare the 'command' parameter against all known control commands. + */ + if (command_compare(command, NS_COMMAND_RELOAD)) { + result = ns_server_reloadcommand(ns_g_server, command); + } else if (command_compare(command, NS_COMMAND_REFRESH)) { + result = ns_server_refreshcommand(ns_g_server, command); + } else if (command_compare(command, NS_COMMAND_HALT)) { + ns_server_flushonshutdown(ns_g_server, ISC_FALSE); + isc_app_shutdown(); + result = ISC_R_SUCCESS; + } else if (command_compare(command, NS_COMMAND_STOP)) { + ns_server_flushonshutdown(ns_g_server, ISC_TRUE); + isc_app_shutdown(); + result = ISC_R_SUCCESS; + } else if (command_compare(command, NS_COMMAND_RELOADCONFIG) || + command_compare(command, NS_COMMAND_RELOADZONES)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "unimplemented channel command '%s'", + command); + result = ISC_R_NOTIMPLEMENTED; + } else if (command_compare(command, NS_COMMAND_DUMPSTATS)) { + result = ns_server_dumpstats(ns_g_server); + } else if (command_compare(command, NS_COMMAND_QUERYLOG)) { + result = ns_server_togglequerylog(ns_g_server); + } else if (command_compare(command, NS_COMMAND_DUMPDB)) { + ns_server_dumpdb(ns_g_server); + result = ISC_R_SUCCESS; + } else if (command_compare(command, NS_COMMAND_TRACE)) { + result = ns_server_setdebuglevel(ns_g_server, command); + } else if (command_compare(command, NS_COMMAND_NOTRACE)) { + ns_g_debuglevel = 0; + isc_log_setdebuglevel(ns_g_lctx, ns_g_debuglevel); + result = ISC_R_SUCCESS; + } else { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "unknown control channel command '%s'", + command); + result = ISC_R_NOTIMPLEMENTED; + } + + return (result); +} diff --git a/bin/named/controlconf.c b/bin/named/controlconf.c new file mode 100644 index 0000000000..3c8c73dcc0 --- /dev/null +++ b/bin/named/controlconf.c @@ -0,0 +1,957 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +/* + * Note: Listeners and connections are not locked. All event handlers are + * executed by the server task, and all callers of exported routines must + * be running under the server task. + */ + +typedef struct controlkey controlkey_t; +typedef ISC_LIST(controlkey_t) controlkeylist_t; + +typedef struct controlconnection controlconnection_t; +typedef ISC_LIST(controlconnection_t) controlconnectionlist_t; + +typedef struct controllistener controllistener_t; +typedef ISC_LIST(controllistener_t) controllistenerlist_t; + +struct controlkey { + char * keyname; + isc_region_t secret; + ISC_LINK(controlkey_t) link; +}; + +struct controlconnection { + isc_socket_t * sock; + isccc_ccmsg_t ccmsg; + isc_boolean_t ccmsg_valid; + isc_boolean_t sending; + isc_timer_t * timer; + unsigned char buffer[2048]; + controllistener_t * listener; + ISC_LINK(controlconnection_t) link; +}; + +struct controllistener { + isc_mem_t * mctx; + isc_task_t * task; + isc_sockaddr_t address; + isc_socket_t * sock; + dns_acl_t * acl; + isc_boolean_t listening; + isc_boolean_t exiting; + controlkeylist_t keys; + controlconnectionlist_t connections; + ISC_LINK(controllistener_t) link; +}; + +static controllistenerlist_t listeners; +static isc_mutex_t listeners_lock; +static isc_once_t once = ISC_ONCE_INIT; + +static void control_newconn(isc_task_t *task, isc_event_t *event); + +static void +initialize_mutex(void) { + RUNTIME_CHECK(isc_mutex_init(&listeners_lock) == ISC_R_SUCCESS); +} + +static void +free_controlkey(controlkey_t *key, isc_mem_t *mctx) { + if (key->keyname != NULL) + isc_mem_free(mctx, key->keyname); + if (key->secret.base != NULL) + isc_mem_put(mctx, key->secret.base, key->secret.length); + isc_mem_put(mctx, key, sizeof(*key)); +} + +static void +free_controlkeylist(controlkeylist_t *keylist, isc_mem_t *mctx) { + while (!ISC_LIST_EMPTY(*keylist)) { + controlkey_t *key = ISC_LIST_HEAD(*keylist); + ISC_LIST_UNLINK(*keylist, key, link); + free_controlkey(key, mctx); + } +} + +static void +free_listener(controllistener_t *listener) { + INSIST(listener->exiting); + INSIST(!listener->listening); + INSIST(ISC_LIST_EMPTY(listener->connections)); + + if (listener->sock != NULL) + isc_socket_detach(&listener->sock); + + free_controlkeylist(&listener->keys, listener->mctx); + + if (listener->acl != NULL) + dns_acl_detach(&listener->acl); + + isc_mem_put(listener->mctx, listener, sizeof(*listener)); +} + +static void +maybe_free_listener(controllistener_t *listener) { + if (listener->exiting && + !listener->listening && + ISC_LIST_EMPTY(listener->connections)) + free_listener(listener); +} + +static void +maybe_free_connection(controlconnection_t *conn) { + controllistener_t *listener = conn->listener; + + if (conn->timer != NULL) + isc_timer_detach(&conn->timer); + + if (conn->ccmsg_valid) { + isccc_ccmsg_cancelread(&conn->ccmsg); + return; + } + + if (conn->sending) { + isc_socket_cancel(conn->sock, listener->task, + ISC_SOCKCANCEL_SEND); + return; + } + + ISC_LIST_UNLINK(listener->connections, conn, link); + isc_mem_put(listener->mctx, conn, sizeof(*conn)); +} + +static void +shutdown_listener(controllistener_t *listener) { + isc_boolean_t destroy = ISC_TRUE; + + listener->exiting = ISC_TRUE; + + if (!ISC_LIST_EMPTY(listener->connections)) { + controlconnection_t *conn; + for (conn = ISC_LIST_HEAD(listener->connections); + conn != NULL; + conn = ISC_LIST_NEXT(conn, link)) + maybe_free_connection(conn); + destroy = ISC_FALSE; + } + + if (listener->sock != NULL) { + isc_socket_cancel(listener->sock, listener->task, + ISC_SOCKCANCEL_ACCEPT); + destroy = ISC_FALSE; + } + + if (destroy) + free_listener(listener); +} + +static isc_boolean_t +address_ok(isc_sockaddr_t *sockaddr, dns_acl_t *acl) { + isc_netaddr_t netaddr; + isc_result_t result; + int match; + + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + + result = dns_acl_match(&netaddr, NULL, acl, + &ns_g_server->aclenv, &match, NULL); + + if (result != ISC_R_SUCCESS || match <= 0) + return (ISC_FALSE); + else + return (ISC_TRUE); +} + +static isc_result_t +control_accept(controllistener_t *listener) { + isc_result_t result; + result = isc_socket_accept(listener->sock, + listener->task, + control_newconn, listener); + if (result != ISC_R_SUCCESS) + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_socket_accept() failed: %s", + isc_result_totext(result)); + else + listener->listening = ISC_TRUE; + return (result); +} + +static isc_result_t +control_listen(controllistener_t *listener) { + isc_result_t result; + + result = isc_socket_listen(listener->sock, 0); + if (result != ISC_R_SUCCESS) + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_socket_listen() failed: %s", + isc_result_totext(result)); + return (result); +} + +static void +control_next(controllistener_t *listener) { + (void)control_accept(listener); +} + +static void +control_senddone(isc_task_t *task, isc_event_t *event) { + isc_socketevent_t *sevent = (isc_socketevent_t *) event; + controlconnection_t *conn = event->ev_arg; + controllistener_t *listener = conn->listener; + isc_socket_t *sock = (isc_socket_t *)sevent->ev_sender; + + REQUIRE(conn->sending); + + UNUSED(task); + + conn->sending = ISC_FALSE; + + if (sevent->result != ISC_R_SUCCESS && + sevent->result != ISC_R_CANCELED) + { + char socktext[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t peeraddr; + + (void)isc_socket_getpeername(sock, &peeraddr); + isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "error sending command response to %s: %s", + socktext, isc_result_totext(sevent->result)); + } + isc_event_free(&event); + isc_socket_detach(&conn->sock); + maybe_free_connection(conn); + maybe_free_listener(listener); +} + +static inline void +log_invalid(isccc_ccmsg_t *ccmsg, isc_result_t result) { + char socktext[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t peeraddr; + + (void)isc_socket_getpeername(ccmsg->sock, &peeraddr); + isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_ERROR, + "invalid command from %s: %s", + socktext, isc_result_totext(result)); +} + +static void +control_recvmessage(isc_task_t *task, isc_event_t *event) { + controlconnection_t *conn; + controllistener_t *listener; + controlkey_t *key; + isccc_sexpr_t *request = NULL; + isccc_sexpr_t *response = NULL; + isccc_region_t ccregion; + isccc_region_t secret; + isc_stdtime_t now; + isc_buffer_t b; + isc_region_t r; + isc_uint32_t len; + isc_result_t result; + isc_result_t eresult; + + REQUIRE(event->ev_type == ISCCC_EVENT_CCMSG); + + conn = event->ev_arg; + listener = conn->listener; + key = ISC_LIST_HEAD(listener->keys); + + if (conn->ccmsg.result != ISC_R_SUCCESS) { + if (conn->ccmsg.result != ISC_R_CANCELED) + log_invalid(&conn->ccmsg, conn->ccmsg.result); + goto cleanup; + } + + ccregion.rstart = isc_buffer_base(&conn->ccmsg.buffer); + ccregion.rend = isc_buffer_used(&conn->ccmsg.buffer); + request = NULL; + secret.rstart = key->secret.base; + secret.rend = key->secret.base + key->secret.length; + result = isccc_cc_fromwire(&ccregion, &request, &secret); + if (result != ISC_R_SUCCESS) { + log_invalid(&conn->ccmsg, result); + goto cleanup; + } + + /* We shouldn't be getting a reply. */ + if (isccc_cc_isreply(request)) { + log_invalid(&conn->ccmsg, ISC_R_FAILURE); + goto cleanup; + } + + eresult = ns_control_docommand(request); + + isc_stdtime_get(&now); + result = isccc_cc_createresponse(request, now, now + 60, &response); + if (result != ISC_R_SUCCESS) + goto cleanup; + if (eresult != ISC_R_SUCCESS) { + isccc_sexpr_t *data; + + data = isccc_alist_lookup(response, "_data"); + if (data != NULL) { + const char *estr = isc_result_totext(eresult); + if (isccc_cc_definestring(data, "err", estr) == NULL) + goto cleanup; + } + } + + ccregion.rstart = conn->buffer + 4; + ccregion.rend = conn->buffer + sizeof(conn->buffer); + result = isccc_cc_towire(response, &ccregion, &secret); + if (result != ISC_R_SUCCESS) + goto cleanup; + isc_buffer_init(&b, conn->buffer, 4); + len = sizeof(conn->buffer) - REGION_SIZE(ccregion); + isc_buffer_putuint32(&b, len - 4); + r.base = conn->buffer; + r.length = len; + + result = isc_socket_send(conn->sock, &r, task, control_senddone, conn); + if (result != ISC_R_SUCCESS) + goto cleanup; + conn->sending = ISC_TRUE; + + isccc_ccmsg_invalidate(&conn->ccmsg); + conn->ccmsg_valid = ISC_FALSE; + if (request != NULL) + isccc_sexpr_free(&request); + if (request != NULL) + isccc_sexpr_free(&response); + return; + + cleanup: + isc_socket_detach(&conn->sock); + isccc_ccmsg_invalidate(&conn->ccmsg); + conn->ccmsg_valid = ISC_FALSE; + maybe_free_connection(conn); + maybe_free_listener(listener); + if (request != NULL) + isccc_sexpr_free(&request); + if (request != NULL) + isccc_sexpr_free(&response); +} + +static void +control_timeout(isc_task_t *task, isc_event_t *event) { + controlconnection_t *conn = event->ev_arg; + + UNUSED(task); + + isc_timer_detach(&conn->timer); + maybe_free_connection(conn); + + isc_event_free(&event); +} + +static isc_result_t +newconnection(controllistener_t *listener, isc_socket_t *sock) { + controlconnection_t *conn; + isc_interval_t interval; + isc_result_t result; + + conn = isc_mem_get(listener->mctx, sizeof(*conn)); + if (conn == NULL) + return (ISC_R_NOMEMORY); + + conn->sock = sock; + isccc_ccmsg_init(listener->mctx, sock, &conn->ccmsg); + conn->ccmsg_valid = ISC_TRUE; + conn->sending = ISC_FALSE; + conn->timer = NULL; + isc_interval_set(&interval, 60, 0); + result = isc_timer_create(ns_g_timermgr, isc_timertype_once, + NULL, &interval, listener->task, + control_timeout, conn, &conn->timer); + if (result != ISC_R_SUCCESS) + goto cleanup; + + conn->listener = listener; + ISC_LINK_INIT(conn, link); + + result = isccc_ccmsg_readmessage(&conn->ccmsg, listener->task, + control_recvmessage, conn); + if (result != ISC_R_SUCCESS) + goto cleanup; + isccc_ccmsg_setmaxsize(&conn->ccmsg, 2048); + + ISC_LIST_APPEND(listener->connections, conn, link); + return (ISC_R_SUCCESS); + + cleanup: + isccc_ccmsg_invalidate(&conn->ccmsg); + if (conn->timer != NULL) + isc_timer_detach(&conn->timer); + isc_mem_put(listener->mctx, conn, sizeof(*conn)); + return (result); +} + +static void +control_newconn(isc_task_t *task, isc_event_t *event) { + isc_socket_newconnev_t *nevent = (isc_socket_newconnev_t *)event; + controllistener_t *listener = event->ev_arg; + isc_socket_t *sock; + isc_sockaddr_t peeraddr; + isc_result_t result; + + UNUSED(task); + + if (nevent->result != ISC_R_SUCCESS) { + if (nevent->result == ISC_R_CANCELED) { + isc_socket_detach(&listener->sock); + listener->listening = ISC_FALSE; + shutdown_listener(listener); + goto cleanup; + } + goto restart; + } + + sock = nevent->newsocket; + (void)isc_socket_getpeername(sock, &peeraddr); + if (!address_ok(&peeraddr, listener->acl)) { + char socktext[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "rejected command channel message from %s", + socktext); + goto restart; + } + + result = newconnection(listener, sock); + if (result != ISC_R_SUCCESS) { + char socktext[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_format(&peeraddr, socktext, sizeof(socktext)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_WARNING, + "dropped command channel from %s: %s", + socktext, isc_result_totext(result)); + goto restart; + } + + restart: + control_next(listener); + cleanup: + isc_event_free(&event); +} + +void +ns_control_shutdown(isc_boolean_t exiting) { + controllistener_t *listener; + controllistener_t *next; + + RUNTIME_CHECK(isc_once_do(&once, initialize_mutex) == ISC_R_SUCCESS); + + if (exiting) { + /* + * When not exiting, this function is called from + * ns_control_configure(), which already holds the lock. + */ + LOCK(&listeners_lock); + } + + + for (listener = ISC_LIST_HEAD(listeners); + listener != NULL; + listener = next) + { + /* + * This is asynchronous. As listeners shut down, they will + * call their callbacks. + */ + next = ISC_LIST_NEXT(listener, link); + ISC_LIST_UNLINK(listeners, listener, link); + shutdown_listener(listener); + } + + if (exiting) + UNLOCK(&listeners_lock); +} + +static isc_result_t +cfgkeylist_find(cfg_obj_t *keylist, const char *keyname, cfg_obj_t **objp) { + cfg_listelt_t *element; + const char *str; + cfg_obj_t *obj; + + for (element = cfg_list_first(keylist); + element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(cfg_map_getname(obj)); + if (strcasecmp(str, keyname) == 0) + break; + } + if (element == NULL) + return (ISC_R_NOTFOUND); + obj = cfg_listelt_value(element); + *objp = obj; + return (ISC_R_SUCCESS); +} + +static isc_result_t +controlkeylist_fromcfg(cfg_obj_t *keylist, isc_mem_t *mctx, + controlkeylist_t *keyids) +{ + cfg_listelt_t *element; + char *newstr = NULL; + const char *str; + cfg_obj_t *obj; + controlkey_t *key = NULL; + + for (element = cfg_list_first(keylist); + element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(obj); + newstr = isc_mem_strdup(mctx, str); + if (newstr == NULL) + goto cleanup; + key = isc_mem_get(mctx, sizeof(*key)); + if (key == NULL) + goto cleanup; + key->keyname = newstr; + key->secret.base = NULL; + key->secret.length = 0; + ISC_LINK_INIT(key, link); + ISC_LIST_APPEND(*keyids, key, link); + key = NULL; + newstr = NULL; + } + return (ISC_R_SUCCESS); + + cleanup: + if (newstr != NULL) + isc_mem_free(mctx, newstr); + if (key != NULL) + isc_mem_put(mctx, key, sizeof(*key)); + free_controlkeylist(keyids, mctx); + return (ISC_R_NOMEMORY); +} + +static void +register_keys(cfg_obj_t *control, cfg_obj_t *keylist, + controlkeylist_t *keyids, isc_mem_t *mctx, char *socktext) +{ + controlkey_t *keyid, *next; + cfg_obj_t *keydef; + char secret[1024]; + isc_buffer_t b; + isc_result_t result; + + /* + * Find the keys corresponding to the keyids used by this listener. + */ + for (keyid = ISC_LIST_HEAD(*keyids); keyid != NULL; keyid = next) { + next = ISC_LIST_NEXT(keyid, link); + + result = cfgkeylist_find(keylist, keyid->keyname, &keydef); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING, + "couldn't find key %s for use with " + "command channel %s", + keyid->keyname, socktext); + ISC_LIST_UNLINK(*keyids, keyid, link); + free_controlkey(keyid, mctx); + } else { + cfg_obj_t *algobj = NULL; + cfg_obj_t *secretobj = NULL; + char *algstr = NULL; + char *secretstr = NULL; + + (void)cfg_map_get(keydef, "algorithm", &algobj); + (void)cfg_map_get(keydef, "secret", &secretobj); + INSIST(algobj != NULL && secretobj != NULL); + + algstr = cfg_obj_asstring(algobj); + secretstr = cfg_obj_asstring(secretobj); + + if (strcasecmp(algstr, "hmac-md5") != 0) { + cfg_obj_log(control, ns_g_lctx, + ISC_LOG_WARNING, + "unsupported algorithm '%s' in " + "key '%s' for use with command " + "channel %s", + algstr, keyid->keyname, socktext); + ISC_LIST_UNLINK(*keyids, keyid, link); + free_controlkey(keyid, mctx); + continue; + } + + isc_buffer_init(&b, secret, sizeof(secret)); + result = isc_base64_decodestring(secretstr, &b); + + if (result != ISC_R_SUCCESS) { + cfg_obj_log(keydef, ns_g_lctx, ISC_LOG_WARNING, + "secret for key '%s' on " + "command channel %s: %s", + keyid->keyname, socktext, + isc_result_totext(result)); + ISC_LIST_UNLINK(*keyids, keyid, link); + free_controlkey(keyid, mctx); + continue; + } + + keyid->secret.length = isc_buffer_usedlength(&b); + keyid->secret.base = isc_mem_get(mctx, + keyid->secret.length); + if (keyid->secret.base == NULL) { + cfg_obj_log(keydef, ns_g_lctx, ISC_LOG_WARNING, + "couldn't register key '%s': " + "out of memory", keyid->keyname); + ISC_LIST_UNLINK(*keyids, keyid, link); + free_controlkey(keyid, mctx); + break; + } + memcpy(keyid->secret.base, isc_buffer_base(&b), + keyid->secret.length); + } + } +} + +static void +update_listener(controllistener_t **listenerp, cfg_obj_t *control, + cfg_obj_t *config, isc_sockaddr_t *addr, + ns_aclconfctx_t *aclconfctx, char *socktext) +{ + controllistener_t *listener; + cfg_obj_t *allow; + cfg_obj_t *keylist; + dns_acl_t *new_acl = NULL; + controlkeylist_t keys; + isc_result_t result; + + for (listener = ISC_LIST_HEAD(listeners); + listener != NULL; + listener = ISC_LIST_NEXT(listener, link)) + if (isc_sockaddr_equal(addr, &listener->address)) + break; + + if (listener == NULL) { + *listenerp = NULL; + return; + } + + /* + * There is already a listener for this sockaddr. + * Update the access list and key information. + * + * First, keep the old access list unless a new one can be made. + */ + allow = cfg_tuple_get(control, "allow"); + result = ns_acl_fromconfig(allow, config, aclconfctx, + listener->mctx, &new_acl); + if (result == ISC_R_SUCCESS) { + dns_acl_detach(&listener->acl); + dns_acl_attach(new_acl, &listener->acl); + dns_acl_detach(&new_acl); + } else + /* XXXDCL say the old acl is still used? */ + cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING, + "couldn't install new acl for " + "command channel %s: %s", + socktext, isc_result_totext(result)); + + keylist = cfg_tuple_get(control, "keys"); + ISC_LIST_INIT(keys); + result = controlkeylist_fromcfg(keylist, listener->mctx, &keys); + if (result != ISC_R_SUCCESS) + cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING, + "couldn't install new keys for " + "command channel %s: %s", + socktext, isc_result_totext(result)); + else { + free_controlkeylist(&listener->keys, listener->mctx); + listener->keys = keys; + } + + *listenerp = listener; +} + +static void +add_listener(isc_mem_t *mctx, controllistener_t **listenerp, + cfg_obj_t *control, cfg_obj_t *config, isc_sockaddr_t *addr, + ns_aclconfctx_t *aclconfctx, char *socktext) +{ + controllistener_t *listener; + cfg_obj_t *allow; + cfg_obj_t *keys; + dns_acl_t *new_acl = NULL; + isc_result_t result = ISC_R_SUCCESS; + + listener = isc_mem_get(mctx, sizeof(*listener)); + if (listener == NULL) + result = ISC_R_NOMEMORY; + + if (result == ISC_R_SUCCESS) { + listener->mctx = mctx; + listener->task = ns_g_server->task; + listener->address = *addr; + listener->sock = NULL; + listener->listening = ISC_FALSE; + listener->exiting = ISC_FALSE; + listener->acl = NULL; + ISC_LINK_INIT(listener, link); + ISC_LIST_INIT(listener->keys); + ISC_LIST_INIT(listener->connections); + + /* + * Make the acl. + */ + allow = cfg_tuple_get(control, "allow"); + result = ns_acl_fromconfig(allow, config, aclconfctx, mctx, + &new_acl); + } + + if (result == ISC_R_SUCCESS) { + dns_acl_attach(new_acl, &listener->acl); + dns_acl_detach(&new_acl); + + keys = cfg_tuple_get(control, "keys"); + result = controlkeylist_fromcfg(keys, listener->mctx, + &listener->keys); + if (result != ISC_R_SUCCESS) + cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING, + "couldn't install new keys for " + "command channel %s: %s", + socktext, isc_result_totext(result)); + } + + if (result == ISC_R_SUCCESS) { + int pf = isc_sockaddr_pf(&listener->address); + if ((pf == AF_INET && isc_net_probeipv4() != ISC_R_SUCCESS) || + (pf == AF_INET6 && isc_net_probeipv6() != ISC_R_SUCCESS)) + result = ISC_R_FAMILYNOSUPPORT; + } + + if (result == ISC_R_SUCCESS) + result = isc_socket_create(ns_g_socketmgr, + isc_sockaddr_pf(&listener->address), + isc_sockettype_tcp, + &listener->sock); + + if (result == ISC_R_SUCCESS) + result = isc_socket_bind(listener->sock, + &listener->address); + + if (result == ISC_R_SUCCESS) + result = control_listen(listener); + + if (result == ISC_R_SUCCESS) + result = control_accept(listener); + + if (result == ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, ISC_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_NOTICE, + "command channel listening on %s", socktext); + *listenerp = listener; + + } else { + if (listener != NULL) { + listener->exiting = ISC_TRUE; + free_listener(listener); + } + + cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING, + "couldn't add command channel %s: %s", + socktext, isc_result_totext(result)); + + *listenerp = NULL; + } + + /* XXXDCL return error results? fail hard? */ +} + +isc_result_t +ns_control_configure(isc_mem_t *mctx, cfg_obj_t *config, + ns_aclconfctx_t *aclconfctx) +{ + controllistener_t *listener; + controllistenerlist_t new_listeners; + cfg_obj_t *controlslist = NULL; + cfg_obj_t *keylist = NULL; + cfg_listelt_t *element, *element2; + char socktext[ISC_SOCKADDR_FORMATSIZE]; + + RUNTIME_CHECK(isc_once_do(&once, initialize_mutex) == ISC_R_SUCCESS); + + ISC_LIST_INIT(new_listeners); + + /* + * Get te list of named.conf 'controls' statements. + */ + (void)cfg_map_get(config, "controls", &controlslist); + + LOCK(&listeners_lock); + /* + * Run through the new control channel list, noting sockets that + * are already being listened on and moving them to the new list. + * + * Identifying duplicates addr/port combinations is left to either + * the underlying config code, or to the bind attempt getting an + * address-in-use error. + */ + if (controlslist != NULL) { + (void)cfg_map_get(config, "key", &keylist); + if (keylist == NULL) + cfg_obj_log(controlslist, ns_g_lctx, ISC_LOG_WARNING, + "no key statements for use by " + "control channels"); + + for (element = cfg_list_first(controlslist); + element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *controls; + cfg_obj_t *inetcontrols = NULL; + + controls = cfg_listelt_value(element); + (void)cfg_map_get(controls, "inet", &inetcontrols); + if (inetcontrols == NULL) + continue; + + for (element2 = cfg_list_first(inetcontrols); + element2 != NULL; + element2 = cfg_list_next(element2)) + { + cfg_obj_t *control; + cfg_obj_t *obj; + isc_sockaddr_t *addr; + + /* + * The parser handles BIND 8 configuration file + * syntax, so it allows unix phrases as well + * inet phrases with no keys{} clause. + * + * "unix" phrases have been reported as + * unsupported by the parser. + * + * The keylist == NULL case was already warned + * about a few lines above. + */ + control = cfg_listelt_value(element2); + + obj = cfg_tuple_get(control, "address"); + addr = cfg_obj_assockaddr(obj); + if (isc_sockaddr_getport(addr) == 0) + isc_sockaddr_setport(addr, + NS_CONTROL_PORT); + + isc_sockaddr_format(addr, socktext, + sizeof(socktext)); + + obj = cfg_tuple_get(control, "keys"); + + if (cfg_obj_isvoid(obj)) { + cfg_obj_log(obj, ns_g_lctx, + ISC_LOG_ERROR, + "no keys clause in " + "control channel %s", + socktext); + continue; + } + + if (cfg_list_first(obj) == NULL) { + cfg_obj_log(obj, ns_g_lctx, + ISC_LOG_ERROR, + "no keys specified in " + "control channel %s", + socktext); + continue; + } + + if (keylist == NULL) + continue; + + isc_log_write(ns_g_lctx, + ISC_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, + ISC_LOG_DEBUG(9), + "processing control channel %s", + socktext); + + update_listener(&listener, control, config, + addr, aclconfctx, socktext); + + if (listener != NULL) + /* + * Remove the listener from the old + * list, so it won't be shut down. + */ + ISC_LIST_UNLINK(listeners, listener, + link); + else + /* + * This is a new listener. + */ + add_listener(mctx, &listener, control, + config, addr, aclconfctx, + socktext); + + if (listener != NULL) { + register_keys(control, keylist, + &listener->keys, + listener->mctx, + socktext); + + ISC_LIST_APPEND(new_listeners, + listener, link); + } + } + } + } + + /* + * ns_control_shutdown() will stop whatever is on the global listeners + * list, which currently only has whatever sockaddr was in the previous + * configuration (if any) that does not remain in the current + * configuration. + */ + ns_control_shutdown(ISC_FALSE); + + /* + * Put all of the valid listeners on the listeners list. + * Anything already on listeners in the process of shutting down + * will be taken care of by listen_done(). + */ + ISC_LIST_APPENDLIST(listeners, new_listeners, link); + + UNLOCK(&listeners_lock); + + return (ISC_R_SUCCESS); +} diff --git a/bin/named/include/named/control.h b/bin/named/include/named/control.h new file mode 100644 index 0000000000..9aace44daa --- /dev/null +++ b/bin/named/include/named/control.h @@ -0,0 +1,35 @@ +#ifndef NAMED_CONTROL_H +#define NAMED_CONTROL_H 1 + +#include + +#include + +#define NS_CONTROL_PORT 953 + +#define NS_COMMAND_STOP "stop" +#define NS_COMMAND_HALT "halt" +#define NS_COMMAND_RELOAD "reload" +#define NS_COMMAND_RELOADCONFIG "reload-config" +#define NS_COMMAND_RELOADZONES "reload-zones" +#define NS_COMMAND_REFRESH "refresh" +#define NS_COMMAND_DUMPSTATS "stats" +#define NS_COMMAND_QUERYLOG "querylog" +#define NS_COMMAND_DUMPDB "dumpdb" +#define NS_COMMAND_TRACE "trace" +#define NS_COMMAND_NOTRACE "notrace" + +isc_result_t +ns_control_init(void); + +isc_result_t +ns_control_configure(isc_mem_t *mctx, cfg_obj_t *config, + ns_aclconfctx_t *aclconfctx); + +void +ns_control_shutdown(isc_boolean_t exiting); + +isc_result_t +ns_control_docommand(isccc_sexpr_t *message); + +#endif /* NAMED_CONTROL_H */