controlconf.c revision 19d80ce5844e00a021643759adcbe27c11b485a0
/*
* Copyright (C) 2004-2008, 2011-2016 Internet Systems Consortium, Inc. ("ISC")
* Copyright (C) 2001-2003 Internet Software Consortium.
*
* 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 */
#include <config.h>
#include <isccfg/namedconf.h>
/*
* 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 struct controlconnection controlconnection_t;
typedef struct controllistener controllistener_t;
struct controlkey {
char * keyname;
};
struct controlconnection {
isc_socket_t * sock;
isc_timer_t * timer;
};
struct controllistener {
isc_task_t * task;
isc_socket_t * sock;
};
struct ns_controls {
};
#define CLOCKSKEW 300
static void
}
static void
while (!ISC_LIST_EMPTY(*keylist)) {
}
}
static void
}
static void
}
static void
if (conn->ccmsg_valid) {
return;
}
return;
}
#ifdef ENABLE_AFL
if (ns_g_fuzz_type == ns_fuzz_rndc) {
}
#endif
}
static void
char socktext[ISC_SOCKADDR_FORMATSIZE];
sizeof(socktext));
"stopping command channel on %s", socktext);
}
{
}
}
static isc_boolean_t
int match;
return (ISC_FALSE);
else
return (ISC_TRUE);
}
static isc_result_t
if (result != ISC_R_SUCCESS)
"isc_socket_accept() failed: %s",
else
return (result);
}
static isc_result_t
if (result != ISC_R_SUCCESS)
"isc_socket_listen() failed: %s",
return (result);
}
static void
(void)control_accept(listener);
}
static void
{
char socktext[ISC_SOCKADDR_FORMATSIZE];
"error sending command response to %s: %s",
}
if (result != ISC_R_SUCCESS) {
}
}
static inline void
char socktext[ISC_SOCKADDR_FORMATSIZE];
"invalid command from %s: %s",
}
static void
isc_buffer_t b;
isc_region_t r;
/* Is the server shutting down? */
goto cleanup;
goto cleanup;
}
{
goto cleanup;
if (result == ISC_R_SUCCESS)
break;
if (result != ISCCC_R_BADAUTH) {
goto cleanup;
}
}
goto cleanup;
}
/* We shouldn't be getting a reply. */
if (isccc_cc_isreply(request)) {
goto cleanup_request;
}
/*
* Limit exposure to replay attacks.
*/
if (!isccc_alist_alistp(_ctrl)) {
goto cleanup_request;
}
goto cleanup_request;
}
} else {
goto cleanup_request;
}
/*
* Expire messages that are too old.
*/
goto cleanup_request;
}
/*
* Duplicate suppression (required for UDP).
*/
if (result != ISC_R_SUCCESS) {
if (result == ISC_R_EXISTS)
goto cleanup_request;
}
goto cleanup_request;
}
if (result != ISC_R_SUCCESS)
goto cleanup_request;
/*
* Establish nonce.
*/
} else
if (result != ISC_R_SUCCESS)
goto cleanup_request;
goto cleanup_response;
}
if (eresult != ISC_R_SUCCESS) {
goto cleanup_response;
}
}
if (isc_buffer_usedlength(text) > 0) {
goto cleanup_response;
}
}
goto cleanup_response;
if (result != ISC_R_SUCCESS)
goto cleanup_response;
}
/* Skip the length field (4 bytes) */
if (result != ISC_R_SUCCESS)
goto cleanup_response;
if (result != ISC_R_SUCCESS)
goto cleanup_response;
return;
}
static void
}
static isc_result_t
return (ISC_R_NOMEMORY);
/* Set a 32 KiB upper limit on incoming message. */
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
return (ISC_R_SUCCESS);
#ifdef ENABLE_AFL
if (ns_g_fuzz_type == ns_fuzz_rndc) {
}
#endif
return (result);
}
static void
goto cleanup;
}
goto restart;
}
char socktext[ISC_SOCKADDR_FORMATSIZE];
"rejected command channel message from %s",
socktext);
goto restart;
}
if (result != ISC_R_SUCCESS) {
char socktext[ISC_SOCKADDR_FORMATSIZE];
"dropped command channel from %s: %s",
goto restart;
}
}
static void
{
/*
* This is asynchronous. As listeners shut down, they will
* call their callbacks.
*/
}
}
void
}
static isc_result_t
{
const cfg_listelt_t *element;
const char *str;
{
break;
}
return (ISC_R_NOTFOUND);
return (ISC_R_SUCCESS);
}
static isc_result_t
{
const cfg_listelt_t *element;
const char *str;
{
goto cleanup;
goto cleanup;
}
return (ISC_R_SUCCESS);
return (ISC_R_NOMEMORY);
}
static void
{
char secret[1024];
isc_buffer_t b;
/*
* Find the keys corresponding to the keyids used by this listener.
*/
if (result != ISC_R_SUCCESS) {
"couldn't find key '%s' for use with "
"command channel %s",
} else {
unsigned int algtype;
{
"unsupported algorithm '%s' in "
"key '%s' for use with command "
"channel %s",
continue;
}
if (result != ISC_R_SUCCESS) {
"secret for key '%s' on "
"command channel %s: %s",
continue;
}
"couldn't register key '%s': "
break;
}
}
}
}
#define CHECK(x) \
do { \
result = (x); \
if (result != ISC_R_SUCCESS) \
goto cleanup; \
} while (0)
static isc_result_t
char secret[1024];
unsigned int algtype;
isc_buffer_t b;
"configuring command channel from '%s'",
if (! isc_file_exists(ns_g_keyfile))
return (ISC_R_FILENOTFOUND);
"unsupported algorithm '%s' in "
"key '%s' for use with command "
"channel",
goto cleanup;
}
if (result != ISC_R_SUCCESS) {
"secret for key '%s' on command channel: %s",
goto cleanup;
}
"couldn't register key '%s': "
}
return (result);
}
/*
* Ensures that both '*global_keylistp' and '*control_keylistp' are
* valid or both are NULL.
*/
static void
const cfg_obj_t **global_keylistp,
const cfg_obj_t **control_keylistp)
{
if (!cfg_obj_isvoid(control_keylist) &&
if (result == ISC_R_SUCCESS) {
}
}
}
static void
{
break;
return;
}
/*
* There is already a listener for this sockaddr.
* Update the access list and key information.
*
* First try to deal with the key situation. There are a few
* possibilities:
* (a) It had an explicit keylist and still has an explicit keylist.
* (b) It had an automagic key and now has an explicit keylist.
* (c) It had an explicit keylist and now needs an automagic key.
* (d) It has an automagic key and still needs the automagic key.
*
* (c) and (d) are the annoying ones. The caller needs to know
* that it should use the automagic configuration for key information
* in place of the named.conf configuration.
*
* XXXDCL There is one other hazard that has not been dealt with,
* the problem that if a key change is being caused by a control
* channel reload, then the response will be with the new key
* and not able to be decrypted by the client.
*/
if (control_keylist != NULL) {
if (result == ISC_R_SUCCESS) {
}
} else {
}
/*
* This message might be a little misleading since the
* "new keys" might in fact be identical to the old ones,
* but tracking whether they are identical just for the
* sake of avoiding this message would be too much trouble.
*/
"couldn't install new keys for "
"command channel %s: %s",
else
"couldn't install new keys for "
"command channel %s: %s",
}
/*
* Now, keep the old access list unless a new one can be made.
*/
&new_acl);
} else {
}
if (!cfg_obj_isvoid(readonly))
}
if (result == ISC_R_SUCCESS) {
/* XXXDCL say the old acl is still used? */
"couldn't install new acl for "
"command channel %s: %s",
else
"couldn't install new acl for "
"command channel %s: %s",
if (result == ISC_R_SUCCESS) {
"couldn't update ownership/permission for "
"command channel %s", socktext);
}
}
static void
{
if (result == ISC_R_SUCCESS) {
/*
* Make the acl.
*/
aclconfctx, mctx, 0,
&new_acl);
} else {
}
}
if (!cfg_obj_isvoid(readonly))
}
if (result == ISC_R_SUCCESS) {
if (control_keylist != NULL) {
if (result == ISC_R_SUCCESS)
} else
"couldn't install keys for "
"command channel %s: %s",
}
if (result == ISC_R_SUCCESS) {
#ifdef ISC_PLATFORM_HAVESYSUNH
#endif
}
if (result == ISC_R_SUCCESS)
if (result == ISC_R_SUCCESS)
#ifndef ISC_ALLOW_MAPPED
if (result == ISC_R_SUCCESS)
#endif
if (result == ISC_R_SUCCESS)
"perm"));
"owner"));
"group"));
}
if (result == ISC_R_SUCCESS)
if (result == ISC_R_SUCCESS)
if (result == ISC_R_SUCCESS) {
"command channel listening on %s", socktext);
} else {
}
"couldn't add command channel %s: %s",
else
"couldn't add command channel %s: %s",
}
/* XXXDCL return error results? fail hard? */
}
{
char socktext[ISC_SOCKADDR_FORMATSIZE];
/*
* Get the list of named.conf 'controls' statements.
*/
/*
* Run through the new control channel list, noting sockets that
* are already being listened on and moving them to the new list.
*
* the underlying config code, or to the bind attempt getting an
* address-in-use error.
*/
if (controlslist != NULL) {
if (inetcontrols == NULL)
continue;
/*
* The parser handles BIND 8 configuration file
* syntax, so it allows unix phrases as well
* inet phrases with no keys{} clause.
*/
if (isc_sockaddr_getport(&addr) == 0)
sizeof(socktext));
ISC_LOG_DEBUG(9),
"processing control channel %s",
socktext);
/*
* Remove the listener from the old
* list, so it won't be shut down.
*/
else
/*
* This is a new listener.
*/
}
}
if (unixcontrols == NULL)
continue;
/*
* The parser handles BIND 8 configuration file
* syntax, so it allows unix phrases as well
* inet phrases with no keys{} clause.
*/
if (result != ISC_R_SUCCESS) {
ISC_LOG_DEBUG(9),
"control channel '%s': %s",
continue;
}
ISC_LOG_DEBUG(9),
"processing control channel '%s'",
&addr, aclconfctx,
/*
* Remove the listener from the old
* list, so it won't be shut down.
*/
else
/*
* This is a new listener.
*/
}
}
} else {
int i;
for (i = 0; i < 2; i++) {
if (i == 0) {
if (isc_net_probeipv4() != ISC_R_SUCCESS)
continue;
} else {
if (isc_net_probeipv6() != ISC_R_SUCCESS)
continue;
&in6addr_loopback, 0);
}
/*
* Remove the listener from the old
* list, so it won't be shut down.
*/
else
/*
* This is a new listener.
*/
}
}
/*
* ns_control_shutdown() will stop whatever is on the global
* listeners list, which currently only has whatever sockaddrs
* were in the previous configuration (if any) that do not
* remain in the current configuration.
*/
/*
* 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().
*/
return (ISC_R_SUCCESS);
}
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS) {
return (result);
}
return (ISC_R_SUCCESS);
}
void
}