controlconf.c revision ed3418751ebdf7de397df76753dae97851d2bdf9
/*
* Copyright (C) 2001 Internet Software Consortium.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
* INTERNET SOFTWARE CONSORTIUM 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.
*/
/* $Id: controlconf.c,v 1.16 2001/07/05 18:39:14 bwelling Exp $ */
#include <config.h>
#include <isc/fsaccess.h>
#include <dns/keyvalues.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;
unsigned char buffer[2048];
};
struct controllistener {
isc_task_t * task;
isc_socket_t * sock;
};
static struct {
char name[64];
char secret[192];
#define NS_AUTOKEY_BITS 128
#define NS_AUTOKEY_NAME "control_autokey"
struct ns_controls {
};
static void
}
static void
while (!ISC_LIST_EMPTY(*keylist)) {
}
}
static void
}
static void
}
static void
if (conn->ccmsg_valid) {
return;
}
return;
}
}
static void
char socktext[ISC_SOCKADDR_FORMATSIZE];
sizeof(socktext));
"stopping command channel on %s", socktext);
}
}
}
if (destroy)
}
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;
char textarray[1024];
goto cleanup;
}
{
goto cleanup;
if (result == ISC_R_SUCCESS)
break;
else if (result == ISCCC_R_BADAUTH) {
/*
* For some reason, request is non-NULL when
* isccc_cc_fromwire returns ISCCC_R_BADAUTH.
*/
} else {
goto cleanup;
}
}
goto cleanup;
}
/* We shouldn't be getting a reply. */
if (isccc_cc_isreply(request)) {
goto cleanup;
}
if (result != ISC_R_SUCCESS)
goto cleanup;
if (eresult != ISC_R_SUCCESS) {
goto cleanup;
}
}
if (isc_buffer_usedlength(&text) > 0) {
goto cleanup;
}
}
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
return;
}
static void
}
static isc_result_t
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS)
goto cleanup;
if (result != ISC_R_SUCCESS)
goto cleanup;
return (ISC_R_SUCCESS);
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;
}
}
void
{
/*
* This is asynchronous. As listeners shut down, they will
* call their callbacks.
*/
}
}
static isc_result_t
const char *str;
{
break;
}
return (ISC_R_NOTFOUND);
return (ISC_R_SUCCESS);
}
static isc_result_t
{
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 {
"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;
}
}
}
}
static isc_result_t
unsigned char key_rawsecret[32];
unsigned char key_txtsecret[32];
/*
* First generate a secret. The fourth parameter non-zero means
* that pseudorandom data is ok; good entropy is not required.
*/
if (result == ISC_R_SUCCESS) {
sizeof(key_rawsecret));
}
if (result == ISC_R_SUCCESS) {
sizeof(key_txtsecret));
}
if (result == ISC_R_SUCCESS) {
len);
/*
* Make a random name for the key and generate the config
* file statement for it.
*/
}
dst_key_free(&key);
if (result != ISC_R_SUCCESS)
"could not generate control channel key: %s",
return (result);
}
static void
unsigned int len;
"key \"%s\" {\n"
"\talgorithm hmac-md5;\n"
"\tsecret \"%s\";\n"
"};\n",
}
static isc_result_t
unsigned int len;
char cfg_data[512];
if (result == ISC_R_SUCCESS) {
/*
* Fake up a configuration with a dummy inet control
* to grab the keylist tuple.
*/
"controls { inet 127.0.0.1 allow { localhost; }"
" keys { %s; }; };",
}
if (result == ISC_R_SUCCESS)
&cfg_type_namedconf, &cfg);
if (result == ISC_R_SUCCESS) {
} else {
"could not parse autogenerated "
"control channel key: %s",
}
return (result);
}
static void
finalize_automagic_key(void) {
unsigned int fsaccess;
int i;
(void)isc_file_remove(ns_g_autorndckeyfile);
/*
* An automagic key was parsed, so some channel needed it.
* Try to write the rndc.conf file.
*/
char cfg_data[512];
char nettext[ISC_NETADDR_FORMATSIZE];
if (result == ISC_R_SUCCESS) {
fsaccess = 0;
&fsaccess);
fsaccess);
if (result != ISC_R_SUCCESS) {
"could not set owner-only "
"access on %s: %s: "
"server control key might be "
"exposed to local users",
}
sizeof(cfg_data));
if (i != EOF)
"\tdefault-server %s;\n"
"\tdefault-port %hu;\n"
"\tdefault-key \"%s\";\n"
"};\n",
if (i == EOF)
"could not write %s",
if (result != ISC_R_SUCCESS)
"error closing %s: %s",
}
}
}
static void
{
if (cfg_obj_isvoid(control_keylist) ||
if (result == ISC_R_SUCCESS) {
/*
* All of these should succeed.
*/
(void)cfg_map_get(cfg_listelt_value
"inet", &inet);
}
} else {
}
if (result == ISC_R_SUCCESS) {
} else
"no key statements for use by control channel");
}
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. For this reason,
* the automagic key is not regenerated on each reload.
*/
if (control_keylist != NULL) {
}
if (result == ISC_R_SUCCESS) {
if (! explicit_key)
} else if (global_keylist != NULL)
/*
* 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",
/*
* Now, keep the old access list unless a new one can be made.
*/
if (result == ISC_R_SUCCESS) {
} else
/* XXXDCL say the old acl is still used? */
"couldn't install new acl for "
"command channel %s: %s",
}
static void
{
if (result == ISC_R_SUCCESS) {
/*
* Make the acl.
*/
&new_acl);
}
if (result == ISC_R_SUCCESS) {
if (control_keylist != NULL)
if (result == ISC_R_SUCCESS) {
if (! explicit_key)
} else
"couldn't install keys for "
"command channel %s: %s",
}
if (result == ISC_R_SUCCESS) {
}
if (result == ISC_R_SUCCESS)
if (result == ISC_R_SUCCESS)
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",
}
/* 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.
*
* "unix" phrases have been reported as
* unsupported by the parser.
*/
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.
*/
socktext);
}
}
/*
* 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().
*/
} else {
if (result == ISC_R_SUCCESS)
}
return (ISC_R_SUCCESS);
}
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
void
}