Commit 326bcfa0 authored by Mark Andrews's avatar Mark Andrews

rndc.key support

parent faf2b80f
......@@ -15,7 +15,7 @@
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: controlconf.c,v 1.17 2001/07/23 01:01:31 bwelling Exp $ */
/* $Id: controlconf.c,v 1.18 2001/08/03 05:56:19 marka Exp $ */
#include <config.h>
......@@ -100,17 +100,6 @@ struct controllistener {
ISC_LINK(controllistener_t) link;
};
static struct {
char name[64];
char secret[192];
cfg_parser_t *parser;
cfg_obj_t *config;
isc_sockaddr_t address; /* Last channel that needed automagic. */
} automagic_key;
#define NS_AUTOKEY_BITS 128
#define NS_AUTOKEY_NAME "control_autokey"
struct ns_controls {
ns_server_t *server;
controllistenerlist_t listeners;
......@@ -647,7 +636,7 @@ controlkeylist_fromcfg(cfg_obj_t *keylist, isc_mem_t *mctx,
static void
register_keys(cfg_obj_t *control, cfg_obj_t *keylist,
controlkeylist_t *keyids, isc_mem_t *mctx, char *socktext)
controlkeylist_t *keyids, isc_mem_t *mctx, const char *socktext)
{
controlkey_t *keyid, *next;
cfg_obj_t *keydef;
......@@ -725,288 +714,119 @@ register_keys(cfg_obj_t *control, cfg_obj_t *keylist,
}
}
#define CHECK(x) \
do { \
result = (x); \
if (result != ISC_R_SUCCESS) \
goto cleanup; \
} while (0)
static isc_result_t
make_automagic_key(isc_mem_t *mctx) {
unsigned char key_rawsecret[32];
unsigned char key_txtsecret[32];
isc_buffer_t key_rawbuffer;
isc_buffer_t key_txtbuffer;
isc_region_t key_rawregion;
isc_uint32_t key_id;
get_rndckey(isc_mem_t *mctx, controlkeylist_t *keyids) {
isc_result_t result;
dst_key_t *key = NULL;
/*
* First generate a secret. The fourth parameter non-zero means
* that pseudorandom data is ok; good entropy is not required.
*/
result = dst_key_generate(dns_rootname, DST_ALG_HMACMD5,
NS_AUTOKEY_BITS, 1, 0, DNS_KEYPROTO_ANY,
dns_rdataclass_in, mctx, &key);
if (result == ISC_R_SUCCESS) {
isc_buffer_init(&key_rawbuffer, &key_rawsecret,
sizeof(key_rawsecret));
result = dst_key_tobuffer(key, &key_rawbuffer);
}
if (result == ISC_R_SUCCESS) {
isc_buffer_init(&key_txtbuffer, &key_txtsecret,
sizeof(key_txtsecret));
isc_buffer_usedregion(&key_rawbuffer, &key_rawregion);
result = isc_base64_totext(&key_rawregion, -1, "",
&key_txtbuffer);
}
if (result == ISC_R_SUCCESS) {
unsigned int len = isc_buffer_usedlength(&key_txtbuffer);
INSIST(len < sizeof(automagic_key.secret));
memcpy(automagic_key.secret, isc_buffer_base(&key_txtbuffer),
len);
automagic_key.secret[len] = '\0';
cfg_parser_t *pctx = NULL;
cfg_obj_t *config = NULL;
cfg_obj_t *key = NULL;
cfg_obj_t *algobj = NULL;
cfg_obj_t *secretobj = NULL;
char *algstr = NULL;
char *secretstr = NULL;
controlkey_t *keyid = NULL;
char secret[1024];
isc_buffer_t b;
/*
* Make a random name for the key and generate the config
* file statement for it.
*/
isc_random_get(&key_id);
len = snprintf(automagic_key.name, sizeof(automagic_key.name),
NS_AUTOKEY_NAME ".%u", key_id);
INSIST(len < sizeof(automagic_key.name));
CHECK(cfg_parser_create(mctx, ns_g_lctx, &pctx));
CHECK(cfg_parse_file(pctx, ns_g_keyfile, &cfg_type_rndckey, &config));
CHECK(cfg_map_get(config, "key", &key));
keyid = isc_mem_get(mctx, sizeof(*keyid));
if (keyid == NULL)
CHECK(ISC_R_NOMEMORY);
keyid->keyname = isc_mem_strdup(mctx,
cfg_obj_asstring(cfg_map_getname(key)));
keyid->secret.base = NULL;
keyid->secret.length = 0;
ISC_LINK_INIT(keyid, link);
if (keyid->keyname == NULL)
CHECK(ISC_R_NOMEMORY);
(void)cfg_map_get(key, "algorithm", &algobj);
(void)cfg_map_get(key, "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(key, ns_g_lctx,
ISC_LOG_WARNING,
"unsupported algorithm '%s' in "
"key '%s' for use with command "
"channel",
algstr, keyid->keyname);
goto cleanup;
}
if (key != NULL)
dst_key_free(&key);
if (result != ISC_R_SUCCESS)
isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_CONTROL, ISC_LOG_WARNING,
"could not generate control channel key: %s",
isc_result_totext(result));
return (result);
}
static void
format_automagic_keycfg(isc_buffer_t *conf) {
unsigned int len;
len = snprintf(isc_buffer_base(conf), isc_buffer_length(conf),
"key \"%s\" {\n"
"\talgorithm hmac-md5;\n"
"\tsecret \"%s\";\n"
"};\n",
automagic_key.name, automagic_key.secret);
INSIST(len < isc_buffer_length(conf));
isc_buffer_add(conf, len);
}
static isc_result_t
parse_automagic_key(isc_mem_t *mctx) {
unsigned int len;
char cfg_data[512];
isc_buffer_t cfg_buffer;
isc_result_t result = ISC_R_SUCCESS;
cfg_obj_t *cfg = NULL;
cfg_parser_t *parser = NULL;
isc_buffer_init(&b, secret, sizeof(secret));
result = isc_base64_decodestring(secretstr, &b);
if (automagic_key.name[0] == '\0')
result = make_automagic_key(mctx);
if (result == ISC_R_SUCCESS) {
/*
* Fake up a configuration with a dummy inet control
* to grab the keylist tuple.
*/
isc_buffer_init(&cfg_buffer, cfg_data, sizeof(cfg_data));
format_automagic_keycfg(&cfg_buffer);
len = snprintf(isc_buffer_used(&cfg_buffer),
isc_buffer_availablelength(&cfg_buffer),
"controls { inet 127.0.0.1 allow { localhost; }"
" keys { %s; }; };",
automagic_key.name);
INSIST(len < isc_buffer_availablelength(&cfg_buffer));
isc_buffer_add(&cfg_buffer, len);
result = cfg_parser_create(mctx, ns_g_lctx, &parser);
if (result != ISC_R_SUCCESS) {
cfg_obj_log(key, ns_g_lctx, ISC_LOG_WARNING,
"secret for key '%s' on command channel: %s",
keyid->keyname, isc_result_totext(result));
CHECK(result);
}
if (result == ISC_R_SUCCESS)
result = cfg_parse_buffer(parser, &cfg_buffer,
&cfg_type_namedconf, &cfg);
if (result == ISC_R_SUCCESS) {
automagic_key.parser = parser;
automagic_key.config = cfg;
} else {
isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_CONTROL, ISC_LOG_WARNING,
"could not parse autogenerated "
"control channel key: %s",
isc_result_totext(result));
if (parser != NULL)
cfg_parser_destroy(&parser);
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(key, ns_g_lctx, ISC_LOG_WARNING,
"couldn't register key '%s': "
"out of memory", keyid->keyname);
CHECK(ISC_R_NOMEMORY);
}
memcpy(keyid->secret.base, isc_buffer_base(&b),
keyid->secret.length);
ISC_LIST_APPEND(*keyids, keyid, link);
keyid = NULL;
result = ISC_R_SUCCESS;
cleanup:
if (keyid != NULL)
free_controlkey(keyid, mctx);
if (config != NULL)
cfg_obj_destroy(pctx, &config);
if (pctx != NULL)
cfg_parser_destroy(&pctx);
return (result);
}
static void
finalize_automagic_key(void) {
unsigned int fsaccess;
int i;
FILE *fp;
isc_result_t result;
(void)isc_file_remove(ns_g_autorndckeyfile);
if (automagic_key.parser != NULL) {
/*
* 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];
isc_buffer_t cfg_buffer;
isc_netaddr_t netaddr;
result = isc_stdio_open(ns_g_autorndckeyfile, "w", &fp);
if (result == ISC_R_SUCCESS) {
fsaccess = 0;
isc_fsaccess_add(ISC_FSACCESS_OWNER, ISC_FSACCESS_READ,
&fsaccess);
result = isc_fsaccess_set(ns_g_autorndckeyfile,
fsaccess);
if (result != ISC_R_SUCCESS) {
isc_log_write(ns_g_lctx,
NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_CONTROL,
ISC_LOG_WARNING,
"could not set owner-only "
"access on %s: %s: "
"server control key might be "
"exposed to local users",
ns_g_autorndckeyfile,
isc_result_totext(result));
}
isc_buffer_init(&cfg_buffer, cfg_data,
sizeof(cfg_data));
format_automagic_keycfg(&cfg_buffer);
isc_netaddr_fromsockaddr(&netaddr,
&automagic_key.address);
isc_netaddr_format(&netaddr, nettext, sizeof(nettext));
i = fputs(isc_buffer_base(&cfg_buffer), fp);
if (i != EOF)
i = fprintf(fp, "options {\n"
"\tdefault-server %s;\n"
"\tdefault-port %hu;\n"
"\tdefault-key \"%s\";\n"
"};\n",
nettext,
isc_sockaddr_getport(
&automagic_key.address),
automagic_key.name);
if (i == EOF)
isc_log_write(ns_g_lctx,
NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_CONTROL,
ISC_LOG_WARNING,
"could not write %s",
ns_g_autorndckeyfile);
result = isc_stdio_close(fp);
if (result != ISC_R_SUCCESS)
isc_log_write(ns_g_lctx,
NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_CONTROL,
ISC_LOG_WARNING,
"error closing %s: %s",
ns_g_autorndckeyfile,
isc_result_totext(result));
}
cfg_obj_destroy(automagic_key.parser, &automagic_key.config);
cfg_parser_destroy(&automagic_key.parser);
}
}
static void
get_key_info(isc_mem_t *mctx, cfg_obj_t *config, cfg_obj_t *control,
cfg_obj_t **global_keylistp, cfg_obj_t **control_keylistp,
isc_boolean_t *explicit_key)
get_key_info(cfg_obj_t *config, cfg_obj_t *control,
cfg_obj_t **global_keylistp, cfg_obj_t **control_keylistp)
{
cfg_obj_t *control_keylist = NULL;
cfg_obj_t *global_keylist = NULL;
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(global_keylistp != NULL && *global_keylistp == NULL);
REQUIRE(control_keylistp != NULL && *control_keylistp == NULL);
control_keylist = cfg_tuple_get(control, "keys");
if (cfg_obj_isvoid(control_keylist) ||
cfg_list_first(control_keylist) == NULL) {
cfg_obj_t *controls = NULL;
cfg_obj_t *inet = NULL;
if (automagic_key.parser == NULL)
result = parse_automagic_key(mctx);
if (result == ISC_R_SUCCESS) {
config = automagic_key.config;
/*
* All of these should succeed.
*/
(void)cfg_map_get(config, "key", &global_keylist);
INSIST(global_keylist != NULL);
(void)cfg_map_get(config, "controls", &controls);
INSIST(controls != NULL);
(void)cfg_map_get(cfg_listelt_value
(cfg_list_first(controls)),
"inet", &inet);
INSIST(inet != NULL);
control_keylist =
cfg_tuple_get(cfg_listelt_value
(cfg_list_first(inet)), "keys");
INSIST(control_keylist != NULL);
}
*explicit_key = ISC_FALSE;
} else {
result = cfg_map_get(config, "key", &global_keylist);
*explicit_key = config != automagic_key.config
? ISC_TRUE : ISC_FALSE;
}
if (!cfg_obj_isvoid(control_keylist) &&
cfg_list_first(control_keylist) != NULL)
cfg_map_get(config, "key", &global_keylist);
if (result == ISC_R_SUCCESS) {
*global_keylistp = global_keylist;
*control_keylistp = control_keylist;
} else
cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING,
"no key statements for use by control channel");
*global_keylistp = global_keylist;
*control_keylistp = control_keylist;
}
static void
update_listener(ns_controls_t *cp,
controllistener_t **listenerp, cfg_obj_t *control,
cfg_obj_t *config, isc_sockaddr_t *addr,
ns_aclconfctx_t *aclconfctx, char *socktext)
ns_aclconfctx_t *aclconfctx, const char *socktext)
{
controllistener_t *listener;
cfg_obj_t *allow;
......@@ -1014,7 +834,6 @@ update_listener(ns_controls_t *cp,
cfg_obj_t *control_keylist = NULL;
dns_acl_t *new_acl = NULL;
controlkeylist_t keys;
isc_boolean_t explicit_key;
isc_result_t result = ISC_R_SUCCESS;
for (listener = ISC_LIST_HEAD(cp->listeners);
......@@ -1049,8 +868,7 @@ update_listener(ns_controls_t *cp,
* and not able to be decrypted by the client. For this reason,
* the automagic key is not regenerated on each reload.
*/
get_key_info(listener->mctx, config, control,
&global_keylist, &control_keylist, &explicit_key);
get_key_info(config, control, &global_keylist, &control_keylist);
if (control_keylist != NULL) {
INSIST(global_keylist != NULL);
......@@ -1058,18 +876,18 @@ update_listener(ns_controls_t *cp,
ISC_LIST_INIT(keys);
result = controlkeylist_fromcfg(control_keylist,
listener->mctx, &keys);
}
if (result == ISC_R_SUCCESS) {
if (result) {
free_controlkeylist(&listener->keys, listener->mctx);
listener->keys = keys;
register_keys(control, global_keylist, &listener->keys,
listener->mctx, socktext);
}
} else {
free_controlkeylist(&listener->keys, listener->mctx);
listener->keys = keys;
register_keys(control, global_keylist, &listener->keys,
listener->mctx, socktext);
if (! explicit_key)
automagic_key.address = listener->address;
result = get_rndckey(listener->mctx, &listener->keys);
}
} else if (global_keylist != NULL)
if (result != ISC_R_SUCCESS && global_keylist != NULL)
/*
* This message might be a little misleading since the
* "new keys" might in fact be identical to the old ones,
......@@ -1085,9 +903,20 @@ update_listener(ns_controls_t *cp,
/*
* Now, 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 (control != NULL) {
allow = cfg_tuple_get(control, "allow");
result = ns_acl_fromconfig(allow, config, aclconfctx,
listener->mctx, &new_acl);
} else {
/* allow { any; } */
result = dns_acl_create(listener->mctx, 1, &new_acl);
if (result == ISC_R_SUCCESS) {
new_acl->elements->type = dns_aclelementtype_any;
new_acl->elements->negative = ISC_FALSE;
new_acl->length = 1;
}
}
if (result == ISC_R_SUCCESS) {
dns_acl_detach(&listener->acl);
dns_acl_attach(new_acl, &listener->acl);
......@@ -1105,7 +934,7 @@ update_listener(ns_controls_t *cp,
static void
add_listener(ns_controls_t *cp, controllistener_t **listenerp,
cfg_obj_t *control, cfg_obj_t *config, isc_sockaddr_t *addr,
ns_aclconfctx_t *aclconfctx, char *socktext)
ns_aclconfctx_t *aclconfctx, const char *socktext)
{
isc_mem_t *mctx = cp->server->mctx;
controllistener_t *listener;
......@@ -1113,7 +942,6 @@ add_listener(ns_controls_t *cp, controllistener_t **listenerp,
cfg_obj_t *global_keylist = NULL;
cfg_obj_t *control_keylist = NULL;
dns_acl_t *new_acl = NULL;
isc_boolean_t explicit_key;
isc_result_t result = ISC_R_SUCCESS;
listener = isc_mem_get(mctx, sizeof(*listener));
......@@ -1136,30 +964,42 @@ add_listener(ns_controls_t *cp, controllistener_t **listenerp,
/*
* Make the acl.
*/
allow = cfg_tuple_get(control, "allow");
result = ns_acl_fromconfig(allow, config, aclconfctx, mctx,
&new_acl);
if (control != NULL) {
allow = cfg_tuple_get(control, "allow");
result = ns_acl_fromconfig(allow, config, aclconfctx,
mctx, &new_acl);
} else {
/* allow { any; } */
result = dns_acl_create(mctx, 1, &new_acl);
if (result == ISC_R_SUCCESS) {
new_acl->elements->type =
dns_aclelementtype_any;
new_acl->elements->negative = ISC_FALSE;
new_acl->length = 1;
}
}
}
if (result == ISC_R_SUCCESS) {
dns_acl_attach(new_acl, &listener->acl);
dns_acl_detach(&new_acl);
get_key_info(listener->mctx, config, control,
&global_keylist, &control_keylist, &explicit_key);
if (config != NULL)
get_key_info(config, control, &global_keylist,
&control_keylist);
if (control_keylist != NULL)
if (control_keylist != NULL) {
result = controlkeylist_fromcfg(control_keylist,
listener->mctx,
&listener->keys);
if (result == ISC_R_SUCCESS) {
register_keys(control, global_keylist, &listener->keys,
listener->mctx, socktext);
if (! explicit_key)
automagic_key.address = listener->address;
if (result == ISC_R_SUCCESS)
register_keys(control, global_keylist,
&listener->keys,
listener->mctx, socktext);
} else
result = get_rndckey(mctx, &listener->keys);
if (result != ISC_R_SUCCESS && control != NULL)
cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING,
"couldn't install keys for "
"command channel %s: %s",
......@@ -1201,9 +1041,15 @@ add_listener(ns_controls_t *cp, controllistener_t **listenerp,
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));
if (control != NULL)
cfg_obj_log(control, ns_g_lctx, ISC_LOG_WARNING,
"couldn't add command channel %s: %s",
socktext, isc_result_totext(result));
else
isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_CONTROL, ISC_LOG_NOTICE,
"couldn't add command channel %s: %s",
socktext, isc_result_totext(result));
*listenerp = NULL;
}
......@@ -1304,34 +1150,62 @@ ns_controls_configure(ns_controls_t *cp, cfg_obj_t *config,
listener, link);
}
}
finalize_automagic_key();
/*
* 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.
*/
ns_controls_shutdown(cp);
/*
* 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(cp->listeners, new_listeners, link);
} else {
isc_result_t result;
int i;
for (i = 0 ; i < 2; i++) {
isc_sockaddr_t addr;
if (i == 1) {
if (isc_net_probeipv4() != ISC_R_SUCCESS)
continue;
isc_sockaddr_any(&addr);
} else {
if (isc_net_probeipv6() != ISC_R_SUCCESS)
continue;
isc_sockaddr_any6(&addr);
}
isc_sockaddr_setport(&addr, NS_CONTROL_PORT);
result = parse_automagic_key(cp->server->mctx);
isc_sockaddr_format(&addr, socktext, sizeof(socktext));
update_listener(cp, &listener, NULL, NULL,
&addr, NULL, socktext);
if (result == ISC_R_SUCCESS)
ns_controls_configure(cp, automagic_key.config,
aclconfctx);
if (listener != NULL)
/*
* Remove the listener from the old