diff options
Diffstat (limited to 'contrib/bind9/bin/named/server.c')
-rw-r--r-- | contrib/bind9/bin/named/server.c | 2454 |
1 files changed, 2184 insertions, 270 deletions
diff --git a/contrib/bind9/bin/named/server.c b/contrib/bind9/bin/named/server.c index bc7fc17c3296..5bbf94b9b604 100644 --- a/contrib/bind9/bin/named/server.c +++ b/contrib/bind9/bin/named/server.c @@ -15,7 +15,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ -/* $Id: server.c,v 1.520.12.21 2011-01-14 23:45:49 tbox Exp $ */ +/* $Id: server.c,v 1.599.8.4 2011-02-16 19:46:12 each Exp $ */ /*! \file */ @@ -23,6 +23,10 @@ #include <stdlib.h> #include <unistd.h> +#include <limits.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> #include <isc/app.h> #include <isc/base64.h> @@ -36,7 +40,9 @@ #include <isc/portset.h> #include <isc/print.h> #include <isc/resource.h> +#include <isc/sha2.h> #include <isc/socket.h> +#include <isc/stat.h> #include <isc/stats.h> #include <isc/stdio.h> #include <isc/string.h> @@ -57,9 +63,11 @@ #ifdef DLZ #include <dns/dlz.h> #endif +#include <dns/dns64.h> #include <dns/forward.h> #include <dns/journal.h> #include <dns/keytable.h> +#include <dns/keyvalues.h> #include <dns/lib.h> #include <dns/master.h> #include <dns/masterdump.h> @@ -102,6 +110,10 @@ #include <stdlib.h> #endif +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + /*% * Check an operation for failure. Assumes that the function * using it has a 'result' variable and a 'cleanup' label. @@ -143,6 +155,14 @@ fatal(msg, result); \ } while (0) \ +/*% + * Maximum ADB size for views that share a cache. Use this limit to suppress + * the total of memory footprint, which should be the main reason for sharing + * a cache. Only effective when a finite max-cache-size is specified. + * This is currently defined to be 8MB. + */ +#define MAX_ADB_SIZE_FOR_CACHESHARE 8388608 + struct ns_dispatch { isc_sockaddr_t addr; unsigned int dispatchgen; @@ -150,6 +170,14 @@ struct ns_dispatch { ISC_LINK(struct ns_dispatch) link; }; +struct ns_cache { + dns_cache_t *cache; + dns_view_t *primaryview; + isc_boolean_t needflush; + isc_boolean_t adbsizeadjusted; + ISC_LINK(ns_cache_t) link; +}; + struct dumpcontext { isc_mem_t *mctx; isc_boolean_t dumpcache; @@ -176,6 +204,17 @@ struct zonelistentry { ISC_LINK(struct zonelistentry) link; }; +/*% + * Configuration context to retain for each view that allows + * new zones to be added at runtime + */ +struct cfg_context { + isc_mem_t * mctx; + cfg_obj_t * config; + cfg_parser_t * parser; + cfg_aclconfctx_t actx; +}; + /* * These zones should not leak onto the Internet. */ @@ -230,8 +269,8 @@ static const struct { { NULL, ISC_FALSE } }; -static void -fatal(const char *msg, isc_result_t result); +ISC_PLATFORM_NORETURN_PRE static void +fatal(const char *msg, isc_result_t result) ISC_PLATFORM_NORETURN_POST; static void ns_server_reload(isc_task_t *task, isc_event_t *event); @@ -256,19 +295,25 @@ configure_alternates(const cfg_obj_t *config, dns_view_t *view, static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, - cfg_aclconfctx_t *aclconf); + cfg_aclconfctx_t *aclconf, isc_boolean_t added); + +static isc_result_t +add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx); static void end_reserved_dispatches(ns_server_t *server, isc_boolean_t all); +static void +cfgctx_destroy(void **cfgp); + /*% * Configure a single view ACL at '*aclp'. Get its configuration from * 'vconfig' (for per-view configuration) and maybe from 'config' */ static isc_result_t configure_view_acl(const cfg_obj_t *vconfig, const cfg_obj_t *config, - const char *aclname, cfg_aclconfctx_t *actx, - isc_mem_t *mctx, dns_acl_t **aclp) + const char *aclname, const char *acltuplename, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, dns_acl_t **aclp) { isc_result_t result; const cfg_obj_t *maps[3]; @@ -294,13 +339,21 @@ configure_view_acl(const cfg_obj_t *vconfig, const cfg_obj_t *config, */ return (ISC_R_SUCCESS); + if (acltuplename != NULL) { + /* + * If the ACL is given in an optional tuple, retrieve it. + * The parser should have ensured that a valid object be + * returned. + */ + aclobj = cfg_tuple_get(aclobj, acltuplename); + } + result = cfg_acl_fromconfig(aclobj, config, ns_g_lctx, actx, mctx, 0, aclp); return (result); } - /*% * Configure a sortlist at '*aclp'. Essentially the same as * configure_view_acl() except it calls cfg_acl_fromconfig with a @@ -345,8 +398,88 @@ configure_view_sortlist(const cfg_obj_t *vconfig, const cfg_obj_t *config, } static isc_result_t -configure_view_dnsseckey(const cfg_obj_t *vconfig, const cfg_obj_t *key, - dns_keytable_t *keytable, isc_mem_t *mctx) +configure_view_nametable(const cfg_obj_t *vconfig, const cfg_obj_t *config, + const char *confname, const char *conftuplename, + isc_mem_t *mctx, dns_rbt_t **rbtp) +{ + isc_result_t result; + const cfg_obj_t *maps[3]; + const cfg_obj_t *obj = NULL; + const cfg_listelt_t *element; + int i = 0; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + const char *str; + const cfg_obj_t *nameobj; + + if (*rbtp != NULL) + dns_rbt_destroy(rbtp); + if (vconfig != NULL) + maps[i++] = cfg_tuple_get(vconfig, "options"); + if (config != NULL) { + const cfg_obj_t *options = NULL; + (void)cfg_map_get(config, "options", &options); + if (options != NULL) + maps[i++] = options; + } + maps[i] = NULL; + + (void)ns_config_get(maps, confname, &obj); + if (obj == NULL) + /* + * No value available. *rbtp == NULL. + */ + return (ISC_R_SUCCESS); + + if (conftuplename != NULL) { + obj = cfg_tuple_get(obj, conftuplename); + if (cfg_obj_isvoid(obj)) + return (ISC_R_SUCCESS); + } + + result = dns_rbt_create(mctx, NULL, NULL, rbtp); + if (result != ISC_R_SUCCESS) + return (result); + + dns_fixedname_init(&fixed); + name = dns_fixedname_name(&fixed); + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) { + nameobj = cfg_listelt_value(element); + str = cfg_obj_asstring(nameobj); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + /* + * We don't need the node data, but need to set dummy data to + * avoid a partial match with an empty node. For example, if + * we have foo.example.com and bar.example.com, we'd get a match + * for baz.example.com, which is not the expected result. + * We simply use (void *)1 as the dummy data. + */ + result = dns_rbt_addname(*rbtp, name, (void *)1); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(nameobj, ns_g_lctx, ISC_LOG_ERROR, + "failed to add %s for %s: %s", + str, confname, isc_result_totext(result)); + goto cleanup; + } + + } + + return (result); + + cleanup: + dns_rbt_destroy(rbtp); + return (result); + +} + +static isc_result_t +dstkey_fromconfig(const cfg_obj_t *vconfig, const cfg_obj_t *key, + isc_boolean_t managed, dst_key_t **target, isc_mem_t *mctx) { dns_rdataclass_t viewclass; dns_rdata_dnskey_t keystruct; @@ -363,12 +496,28 @@ configure_view_dnsseckey(const cfg_obj_t *vconfig, const cfg_obj_t *key, isc_result_t result; dst_key_t *dstkey = NULL; + INSIST(target != NULL && *target == NULL); + flags = cfg_obj_asuint32(cfg_tuple_get(key, "flags")); proto = cfg_obj_asuint32(cfg_tuple_get(key, "protocol")); alg = cfg_obj_asuint32(cfg_tuple_get(key, "algorithm")); keyname = dns_fixedname_name(&fkeyname); keynamestr = cfg_obj_asstring(cfg_tuple_get(key, "name")); + if (managed) { + const char *initmethod; + initmethod = cfg_obj_asstring(cfg_tuple_get(key, "init")); + + if (strcasecmp(initmethod, "initial-key") != 0) { + cfg_obj_log(key, ns_g_lctx, ISC_LOG_ERROR, + "managed key '%s': " + "invalid initialization method '%s'", + keynamestr, initmethod); + result = ISC_R_FAILURE; + goto cleanup; + } + } + if (vconfig == NULL) viewclass = dns_rdataclass_in; else { @@ -408,7 +557,8 @@ configure_view_dnsseckey(const cfg_obj_t *vconfig, const cfg_obj_t *key, keystruct.algorithm == DST_ALG_RSAMD5) && r.length > 1 && r.base[0] == 1 && r.base[1] == 3) cfg_obj_log(key, ns_g_lctx, ISC_LOG_WARNING, - "trusted key '%s' has a weak exponent", + "%s key '%s' has a weak exponent", + managed ? "managed" : "trusted", keynamestr); CHECK(dns_rdata_fromstruct(NULL, @@ -418,25 +568,28 @@ configure_view_dnsseckey(const cfg_obj_t *vconfig, const cfg_obj_t *key, dns_fixedname_init(&fkeyname); isc_buffer_init(&namebuf, keynamestr, strlen(keynamestr)); isc_buffer_add(&namebuf, strlen(keynamestr)); - CHECK(dns_name_fromtext(keyname, &namebuf, - dns_rootname, ISC_FALSE, - NULL)); + CHECK(dns_name_fromtext(keyname, &namebuf, dns_rootname, 0, NULL)); CHECK(dst_key_fromdns(keyname, viewclass, &rrdatabuf, mctx, &dstkey)); - CHECK(dns_keytable_add(keytable, &dstkey)); - INSIST(dstkey == NULL); + *target = dstkey; return (ISC_R_SUCCESS); cleanup: if (result == DST_R_NOCRYPTO) { cfg_obj_log(key, ns_g_lctx, ISC_LOG_ERROR, - "ignoring trusted key for '%s': no crypto support", + "ignoring %s key for '%s': no crypto support", + managed ? "managed" : "trusted", keynamestr); - result = ISC_R_SUCCESS; + } else if (result == DST_R_UNSUPPORTEDALG) { + cfg_obj_log(key, ns_g_lctx, ISC_LOG_WARNING, + "skipping %s key for '%s': %s", + managed ? "managed" : "trusted", + keynamestr, isc_result_totext(result)); } else { cfg_obj_log(key, ns_g_lctx, ISC_LOG_ERROR, - "configuring trusted key for '%s': %s", + "configuring %s key for '%s': %s", + managed ? "managed" : "trusted", keynamestr, isc_result_totext(result)); result = ISC_R_FAILURE; } @@ -447,63 +600,215 @@ configure_view_dnsseckey(const cfg_obj_t *vconfig, const cfg_obj_t *key, return (result); } +static isc_result_t +load_view_keys(const cfg_obj_t *keys, const cfg_obj_t *vconfig, + dns_view_t *view, isc_boolean_t managed, + dns_name_t *keyname, isc_mem_t *mctx) +{ + const cfg_listelt_t *elt, *elt2; + const cfg_obj_t *key, *keylist; + dst_key_t *dstkey = NULL; + isc_result_t result; + dns_keytable_t *secroots = NULL; + + CHECK(dns_view_getsecroots(view, &secroots)); + + for (elt = cfg_list_first(keys); + elt != NULL; + elt = cfg_list_next(elt)) { + keylist = cfg_listelt_value(elt); + + for (elt2 = cfg_list_first(keylist); + elt2 != NULL; + elt2 = cfg_list_next(elt2)) { + key = cfg_listelt_value(elt2); + result = dstkey_fromconfig(vconfig, key, managed, + &dstkey, mctx); + if (result == DST_R_UNSUPPORTEDALG) { + result = ISC_R_SUCCESS; + continue; + } + if (result != ISC_R_SUCCESS) + goto cleanup; + + /* + * If keyname was specified, we only add that key. + */ + if (keyname != NULL && + !dns_name_equal(keyname, dst_key_name(dstkey))) + { + dst_key_free(&dstkey); + continue; + } + + CHECK(dns_keytable_add(secroots, managed, &dstkey)); + } + } + + cleanup: + if (dstkey != NULL) + dst_key_free(&dstkey); + if (secroots != NULL) + dns_keytable_detach(&secroots); + if (result == DST_R_NOCRYPTO) + result = ISC_R_SUCCESS; + return (result); +} + /*% - * Configure DNSSEC keys for a view. Currently used only for - * the security roots. + * Configure DNSSEC keys for a view. * * The per-view configuration values and the server-global defaults are read - * from 'vconfig' and 'config'. The variable to be configured is '*target'. + * from 'vconfig' and 'config'. */ static isc_result_t -configure_view_dnsseckeys(const cfg_obj_t *vconfig, const cfg_obj_t *config, - isc_mem_t *mctx, dns_keytable_t **target) +configure_view_dnsseckeys(dns_view_t *view, const cfg_obj_t *vconfig, + const cfg_obj_t *config, const cfg_obj_t *bindkeys, + isc_boolean_t auto_dlv, isc_boolean_t auto_root, + isc_mem_t *mctx) { - isc_result_t result; - const cfg_obj_t *keys = NULL; + isc_result_t result = ISC_R_SUCCESS; + const cfg_obj_t *view_keys = NULL; + const cfg_obj_t *global_keys = NULL; + const cfg_obj_t *view_managed_keys = NULL; + const cfg_obj_t *global_managed_keys = NULL; + const cfg_obj_t *maps[4]; const cfg_obj_t *voptions = NULL; - const cfg_listelt_t *element, *element2; - const cfg_obj_t *keylist; - const cfg_obj_t *key; - dns_keytable_t *keytable = NULL; + const cfg_obj_t *options = NULL; + const cfg_obj_t *obj = NULL; + const char *directory; + int i = 0; - CHECK(dns_keytable_create(mctx, &keytable)); + /* We don't need trust anchors for the _bind view */ + if (strcmp(view->name, "_bind") == 0 && + view->rdclass == dns_rdataclass_chaos) { + return (ISC_R_SUCCESS); + } - if (vconfig != NULL) + if (vconfig != NULL) { voptions = cfg_tuple_get(vconfig, "options"); + if (voptions != NULL) { + (void) cfg_map_get(voptions, "trusted-keys", + &view_keys); + (void) cfg_map_get(voptions, "managed-keys", + &view_managed_keys); + maps[i++] = voptions; + } + } - keys = NULL; - if (voptions != NULL) - (void)cfg_map_get(voptions, "trusted-keys", &keys); - if (keys == NULL) - (void)cfg_map_get(config, "trusted-keys", &keys); + if (config != NULL) { + (void)cfg_map_get(config, "trusted-keys", &global_keys); + (void)cfg_map_get(config, "managed-keys", &global_managed_keys); + (void)cfg_map_get(config, "options", &options); + if (options != NULL) { + maps[i++] = options; + } + } - for (element = cfg_list_first(keys); - element != NULL; - element = cfg_list_next(element)) - { - keylist = cfg_listelt_value(element); - for (element2 = cfg_list_first(keylist); - element2 != NULL; - element2 = cfg_list_next(element2)) - { - key = cfg_listelt_value(element2); - CHECK(configure_view_dnsseckey(vconfig, key, - keytable, mctx)); + maps[i++] = ns_g_defaults; + maps[i] = NULL; + + result = dns_view_initsecroots(view, mctx); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "couldn't create keytable"); + return (ISC_R_UNEXPECTED); + } + + if (auto_dlv && view->rdclass == dns_rdataclass_in) { + const cfg_obj_t *builtin_keys = NULL; + const cfg_obj_t *builtin_managed_keys = NULL; + + isc_log_write(ns_g_lctx, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "using built-in DLV key for view %s", + view->name); + + /* + * If bind.keys exists, it overrides the managed-keys + * clause hard-coded in ns_g_config. + */ + if (bindkeys != NULL) { + (void)cfg_map_get(bindkeys, "trusted-keys", + &builtin_keys); + (void)cfg_map_get(bindkeys, "managed-keys", + &builtin_managed_keys); + } else { + (void)cfg_map_get(ns_g_config, "trusted-keys", + &builtin_keys); + (void)cfg_map_get(ns_g_config, "managed-keys", + &builtin_managed_keys); } + + if (builtin_keys != NULL) + CHECK(load_view_keys(builtin_keys, vconfig, view, + ISC_FALSE, view->dlv, mctx)); + if (builtin_managed_keys != NULL) + CHECK(load_view_keys(builtin_managed_keys, vconfig, + view, ISC_TRUE, view->dlv, mctx)); } - dns_keytable_detach(target); - *target = keytable; /* Transfer ownership. */ - keytable = NULL; - result = ISC_R_SUCCESS; + if (auto_root && view->rdclass == dns_rdataclass_in) { + const cfg_obj_t *builtin_keys = NULL; + const cfg_obj_t *builtin_managed_keys = NULL; - cleanup: + isc_log_write(ns_g_lctx, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "using built-in root key for view %s", + view->name); + + /* + * If bind.keys exists, it overrides the managed-keys + * clause hard-coded in ns_g_config. + */ + if (bindkeys != NULL) { + (void)cfg_map_get(bindkeys, "trusted-keys", + &builtin_keys); + (void)cfg_map_get(bindkeys, "managed-keys", + &builtin_managed_keys); + } else { + (void)cfg_map_get(ns_g_config, "trusted-keys", + &builtin_keys); + (void)cfg_map_get(ns_g_config, "managed-keys", + &builtin_managed_keys); + } + + if (builtin_keys != NULL) + CHECK(load_view_keys(builtin_keys, vconfig, view, + ISC_FALSE, dns_rootname, mctx)); + if (builtin_managed_keys != NULL) + CHECK(load_view_keys(builtin_managed_keys, vconfig, + view, ISC_TRUE, dns_rootname, + mctx)); + } + + CHECK(load_view_keys(view_keys, vconfig, view, ISC_FALSE, + NULL, mctx)); + CHECK(load_view_keys(view_managed_keys, vconfig, view, ISC_TRUE, + NULL, mctx)); + + if (view->rdclass == dns_rdataclass_in) { + CHECK(load_view_keys(global_keys, vconfig, view, ISC_FALSE, + NULL, mctx)); + CHECK(load_view_keys(global_managed_keys, vconfig, view, + ISC_TRUE, NULL, mctx)); + } + + /* + * Add key zone for managed-keys. + */ + obj = NULL; + (void)ns_config_get(maps, "managed-keys-directory", &obj); + directory = obj != NULL ? cfg_obj_asstring(obj) : NULL; + CHECK(add_keydata_zone(view, directory, ns_g_mctx)); + + cleanup: return (result); } static isc_result_t -mustbesecure(const cfg_obj_t *mbs, dns_resolver_t *resolver) -{ +mustbesecure(const cfg_obj_t *mbs, dns_resolver_t *resolver) { const cfg_listelt_t *element; const cfg_obj_t *obj; const char *str; @@ -523,8 +828,7 @@ mustbesecure(const cfg_obj_t *mbs, dns_resolver_t *resolver) str = cfg_obj_asstring(cfg_tuple_get(obj, "name")); isc_buffer_init(&b, str, strlen(str)); isc_buffer_add(&b, strlen(str)); - CHECK(dns_name_fromtext(name, &b, dns_rootname, - ISC_FALSE, NULL)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); value = cfg_obj_asboolean(cfg_tuple_get(obj, "value")); CHECK(dns_resolver_setmustbesecure(resolver, name, value)); } @@ -684,7 +988,7 @@ configure_order(dns_order_t *order, const cfg_obj_t *ent) { isc_buffer_add(&b, strlen(str)); dns_fixedname_init(&fixed); result = dns_name_fromtext(dns_fixedname_name(&fixed), &b, - dns_rootname, ISC_FALSE, NULL); + dns_rootname, 0, NULL); if (result != ISC_R_SUCCESS) return (result); @@ -868,7 +1172,7 @@ disable_algorithms(const cfg_obj_t *disabled, dns_resolver_t *resolver) { str = cfg_obj_asstring(cfg_tuple_get(disabled, "name")); isc_buffer_init(&b, str, strlen(str)); isc_buffer_add(&b, strlen(str)); - CHECK(dns_name_fromtext(name, &b, dns_rootname, ISC_FALSE, NULL)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); algorithms = cfg_tuple_get(disabled, "algorithms"); for (element = cfg_list_first(algorithms); @@ -921,7 +1225,7 @@ on_disable_list(const cfg_obj_t *disablelist, dns_name_t *zonename) { isc_buffer_init(&b, str, strlen(str)); isc_buffer_add(&b, strlen(str)); result = dns_name_fromtext(name, &b, dns_rootname, - ISC_TRUE, NULL); + 0, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (dns_name_equal(name, zonename)) return (ISC_TRUE); @@ -979,6 +1283,20 @@ setquerystats(dns_zone_t *zone, isc_mem_t *mctx, isc_boolean_t on) { return (ISC_R_SUCCESS); } +static ns_cache_t * +cachelist_find(ns_cachelist_t *cachelist, const char *cachename) { + ns_cache_t *nsc; + + for (nsc = ISC_LIST_HEAD(*cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (strcmp(dns_cache_getname(nsc->cache), cachename) == 0) + return (nsc); + } + + return (NULL); +} + static isc_boolean_t cache_reusable(dns_view_t *originview, dns_view_t *view, isc_boolean_t new_zero_no_soattl) @@ -996,6 +1314,238 @@ cache_reusable(dns_view_t *originview, dns_view_t *view, return (ISC_TRUE); } +static isc_boolean_t +cache_sharable(dns_view_t *originview, dns_view_t *view, + isc_boolean_t new_zero_no_soattl, + unsigned int new_cleaning_interval, + isc_uint32_t new_max_cache_size) +{ + /* + * If the cache cannot even reused for the same view, it cannot be + * shared with other views. + */ + if (!cache_reusable(originview, view, new_zero_no_soattl)) + return (ISC_FALSE); + + /* + * Check other cache related parameters that must be consistent among + * the sharing views. + */ + if (dns_cache_getcleaninginterval(originview->cache) != + new_cleaning_interval || + dns_cache_getcachesize(originview->cache) != new_max_cache_size) { + return (ISC_FALSE); + } + + return (ISC_TRUE); +} + +#ifdef DLZ +/* + * Callback from DLZ configure when the driver sets up a writeable zone + */ +static isc_result_t +dlzconfigure_callback(dns_view_t *view, dns_zone_t *zone) { + dns_name_t *origin = dns_zone_getorigin(zone); + dns_rdataclass_t zclass = view->rdclass; + isc_result_t result; + + result = dns_zonemgr_managezone(ns_g_server->zonemgr, zone); + if (result != ISC_R_SUCCESS) + return result; + dns_zone_setstats(zone, ns_g_server->zonestats); + + return ns_zone_configure_writeable_dlz(view->dlzdatabase, + zone, zclass, origin); +} +#endif + +static isc_result_t +dns64_reverse(dns_view_t *view, isc_mem_t *mctx, isc_netaddr_t *na, + unsigned int prefixlen, const char *server, + const char *contact) +{ + char *cp; + char reverse[48+sizeof("ip6.arpa.")]; + const char *dns64_dbtype[4] = { "_builtin", "dns64", ".", "." }; + const char *sep = ": view "; + const char *viewname = view->name; + const unsigned char *s6; + dns_fixedname_t fixed; + dns_name_t *name; + dns_zone_t *zone = NULL; + int dns64_dbtypec = 4; + isc_buffer_t b; + isc_result_t result; + + REQUIRE(prefixlen == 32 || prefixlen == 40 || prefixlen == 48 || + prefixlen == 56 || prefixlen == 64 || prefixlen == 96); + + if (!strcmp(viewname, "_default")) { + sep = ""; + viewname = ""; + } + + /* + * Construct the reverse name of the zone. + */ + cp = reverse; + s6 = na->type.in6.s6_addr; + while (prefixlen > 0) { + prefixlen -= 8; + sprintf(cp, "%x.%x.", s6[prefixlen/8] & 0xf, + (s6[prefixlen/8] >> 4) & 0xf); + cp += 4; + } + strcat(cp, "ip6.arpa."); + + /* + * Create the actual zone. + */ + if (server != NULL) + dns64_dbtype[2] = server; + if (contact != NULL) + dns64_dbtype[3] = contact; + dns_fixedname_init(&fixed); + name = dns_fixedname_name(&fixed); + isc_buffer_init(&b, reverse, strlen(reverse)); + isc_buffer_add(&b, strlen(reverse)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + CHECK(dns_zone_create(&zone, mctx)); + CHECK(dns_zone_setorigin(zone, name)); + dns_zone_setview(zone, view); + CHECK(dns_zonemgr_managezone(ns_g_server->zonemgr, zone)); + dns_zone_setclass(zone, view->rdclass); + dns_zone_settype(zone, dns_zone_master); + dns_zone_setstats(zone, ns_g_server->zonestats); + CHECK(dns_zone_setdbtype(zone, dns64_dbtypec, dns64_dbtype)); + if (view->queryacl != NULL) + dns_zone_setqueryacl(zone, view->queryacl); + if (view->queryonacl != NULL) + dns_zone_setqueryonacl(zone, view->queryonacl); + dns_zone_setdialup(zone, dns_dialuptype_no); + dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, ISC_TRUE); + CHECK(setquerystats(zone, mctx, ISC_FALSE)); /* XXXMPA */ + CHECK(dns_view_addzone(view, zone)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, "dns64 reverse zone%s%s: %s", sep, + viewname, reverse); + +cleanup: + if (zone != NULL) + dns_zone_detach(&zone); + return (result); +} + +static isc_result_t +configure_rpz(dns_view_t *view, const cfg_listelt_t *element) { + const cfg_obj_t *rpz_obj, *policy_obj; + const char *str; + dns_fixedname_t fixed; + dns_name_t *origin; + dns_rpz_zone_t *old, *new; + dns_zone_t *zone = NULL; + isc_result_t result; + unsigned int l1, l2; + + new = isc_mem_get(view->mctx, sizeof(*new)); + if (new == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + memset(new, 0, sizeof(*new)); + dns_name_init(&new->nsdname, NULL); + dns_name_init(&new->origin, NULL); + dns_name_init(&new->cname, NULL); + ISC_LIST_INITANDAPPEND(view->rpz_zones, new, link); + + rpz_obj = cfg_listelt_value(element); + policy_obj = cfg_tuple_get(rpz_obj, "policy"); + if (cfg_obj_isvoid(policy_obj)) { + new->policy = DNS_RPZ_POLICY_GIVEN; + } else { + str = cfg_obj_asstring(policy_obj); + new->policy = dns_rpz_str2policy(str); + INSIST(new->policy != DNS_RPZ_POLICY_ERROR); + } + + dns_fixedname_init(&fixed); + origin = dns_fixedname_name(&fixed); + str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "name")); + result = dns_name_fromstring(origin, str, DNS_NAME_DOWNCASE, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid zone '%s'", str); + goto cleanup; + } + + result = dns_name_fromstring2(&new->nsdname, DNS_RPZ_NSDNAME_ZONE, + origin, DNS_NAME_DOWNCASE, view->mctx); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid zone '%s'", str); + goto cleanup; + } + + /* + * The origin is part of 'nsdname' so we don't need to keep it + * seperately. + */ + l1 = dns_name_countlabels(&new->nsdname); + l2 = dns_name_countlabels(origin); + dns_name_getlabelsequence(&new->nsdname, l1 - l2, l2, &new->origin); + + /* + * Are we configured to with the reponse policy zone? + */ + result = dns_view_findzone(view, &new->origin, &zone); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "unknown zone '%s'", str); + goto cleanup; + } + + if (dns_zone_gettype(zone) != dns_zone_master && + dns_zone_gettype(zone) != dns_zone_slave) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "zone '%s' is neither master nor slave", str); + dns_zone_detach(&zone); + result = DNS_R_NOTMASTER; + goto cleanup; + } + dns_zone_detach(&zone); + + for (old = ISC_LIST_HEAD(view->rpz_zones); + old != new; + old = ISC_LIST_NEXT(old, link)) { + ++new->num; + if (dns_name_equal(&old->origin, &new->origin)) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "duplicate '%s'", str); + result = DNS_R_DUPLICATE; + goto cleanup; + } + } + + if (new->policy == DNS_RPZ_POLICY_CNAME) { + str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "cname")); + result = dns_name_fromstring(&new->cname, str, 0, view->mctx); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid cname '%s'", str); + goto cleanup; + } + } + + return (ISC_R_SUCCESS); + + cleanup: + dns_rpz_view_destroy(view); + return (result); +} + /* * Configure 'view' according to 'vconfig', taking defaults from 'config' * where values are missing in 'vconfig'. @@ -1004,12 +1554,15 @@ cache_reusable(dns_view_t *originview, dns_view_t *view, * global defaults in 'config' used exclusively. */ static isc_result_t -configure_view(dns_view_t *view, const cfg_obj_t *config, - const cfg_obj_t *vconfig, isc_mem_t *mctx, - cfg_aclconfctx_t *actx, isc_boolean_t need_hints) +configure_view(dns_view_t *view, cfg_parser_t* parser, + cfg_obj_t *config, cfg_obj_t *vconfig, + ns_cachelist_t *cachelist, const cfg_obj_t *bindkeys, + isc_mem_t *mctx, cfg_aclconfctx_t *actx, + isc_boolean_t need_hints) { const cfg_obj_t *maps[4]; const cfg_obj_t *cfgmaps[3]; + const cfg_obj_t *optionmaps[3]; const cfg_obj_t *options = NULL; const cfg_obj_t *voptions = NULL; const cfg_obj_t *forwardtype; @@ -1028,17 +1581,20 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, dns_cache_t *cache = NULL; isc_result_t result; isc_uint32_t max_adb_size; + unsigned int cleaning_interval; isc_uint32_t max_cache_size; isc_uint32_t max_acache_size; isc_uint32_t lame_ttl; - dns_tsig_keyring_t *ring; + dns_tsig_keyring_t *ring = NULL; dns_view_t *pview = NULL; /* Production view */ isc_mem_t *cmctx; dns_dispatch_t *dispatch4 = NULL; dns_dispatch_t *dispatch6 = NULL; isc_boolean_t reused_cache = ISC_FALSE; - int i; + isc_boolean_t shared_cache = ISC_FALSE; + int i = 0, j = 0, k = 0; const char *str; + const char *cachename = NULL; dns_order_t *order = NULL; isc_uint32_t udpsize; unsigned int resopts = 0; @@ -1052,7 +1608,14 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *disablelist = NULL; isc_stats_t *resstats = NULL; dns_stats_t *resquerystats = NULL; + isc_boolean_t auto_dlv = ISC_FALSE; + isc_boolean_t auto_root = ISC_FALSE; + ns_cache_t *nsc; isc_boolean_t zero_no_soattl; + cfg_parser_t *newzones_parser = NULL; + cfg_obj_t *nzfconf = NULL; + dns_acl_t *clients = NULL, *mapped = NULL, *excluded = NULL; + unsigned int query_timeout; REQUIRE(DNS_VIEW_VALID(view)); @@ -1061,22 +1624,28 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, if (config != NULL) (void)cfg_map_get(config, "options", &options); - i = 0; + /* + * maps: view options, options, defaults + * cfgmaps: view options, config + * optionmaps: view options, options + */ if (vconfig != NULL) { voptions = cfg_tuple_get(vconfig, "options"); maps[i++] = voptions; + optionmaps[j++] = voptions; + cfgmaps[k++] = voptions; } - if (options != NULL) + if (options != NULL) { maps[i++] = options; + optionmaps[j++] = options; + } + maps[i++] = ns_g_defaults; maps[i] = NULL; - - i = 0; - if (voptions != NULL) - cfgmaps[i++] = voptions; + optionmaps[j] = NULL; if (config != NULL) - cfgmaps[i++] = config; - cfgmaps[i] = NULL; + cfgmaps[k++] = config; + cfgmaps[k] = NULL; if (!strcmp(viewname, "_default")) { sep = ""; @@ -1137,12 +1706,12 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, dns_acache_setcachesize(view->acache, max_acache_size); } - CHECK(configure_view_acl(vconfig, config, "allow-query", actx, + CHECK(configure_view_acl(vconfig, config, "allow-query", NULL, actx, ns_g_mctx, &view->queryacl)); - if (view->queryacl == NULL) { - CHECK(configure_view_acl(NULL, ns_g_config, "allow-query", actx, - ns_g_mctx, &view->queryacl)); + CHECK(configure_view_acl(NULL, ns_g_config, "allow-query", + NULL, actx, ns_g_mctx, + &view->queryacl)); } /* @@ -1159,7 +1728,62 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, { const cfg_obj_t *zconfig = cfg_listelt_value(element); CHECK(configure_zone(config, zconfig, vconfig, mctx, view, - actx)); + actx, ISC_FALSE)); + } + + /* + * Are we allowing zones to be added and deleted dynamically? + */ + obj = NULL; + result = ns_config_get(maps, "allow-new-zones", &obj); + if (result == ISC_R_SUCCESS) { + isc_boolean_t allow = cfg_obj_asboolean(obj); + struct cfg_context *cfg = NULL; + if (allow) { + cfg = isc_mem_get(view->mctx, sizeof(*cfg)); + if (cfg == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + memset(cfg, 0, sizeof(*cfg)); + isc_mem_attach(view->mctx, &cfg->mctx); + if (config != NULL) + cfg_obj_attach(config, &cfg->config); + cfg_parser_attach(parser, &cfg->parser); + cfg_aclconfctx_clone(actx, &cfg->actx); + } + dns_view_setnewzones(view, allow, cfg, cfgctx_destroy); + } + + /* + * If we're allowing added zones, then load zone configuration + * from the newzone file for zones that were added during previous + * runs. + */ + if (view->new_zone_file != NULL) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "loading additional zones for view '%s'", + view->name); + + CHECK(cfg_parser_create(view->mctx, ns_g_lctx, + &newzones_parser)); + result = cfg_parse_file(newzones_parser, view->new_zone_file, + &cfg_type_newzones, &nzfconf); + if (result == ISC_R_SUCCESS) { + zonelist = NULL; + cfg_map_get(nzfconf, "zone", &zonelist); + for (element = cfg_list_first(zonelist); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *zconfig = + cfg_listelt_value(element); + CHECK(configure_zone(config, zconfig, vconfig, + mctx, view, actx, + ISC_TRUE)); + } + } } #ifdef DLZ @@ -1197,6 +1821,14 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, isc_mem_put(mctx, dlzargv, dlzargc * sizeof(*dlzargv)); if (result != ISC_R_SUCCESS) goto cleanup; + + /* + * If the dlz backend supports configuration, + * then call its configure method now. + */ + result = dns_dlzconfigure(view, dlzconfigure_callback); + if (result != ISC_R_SUCCESS) + goto cleanup; } } #endif @@ -1205,6 +1837,32 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, * Obtain configuration parameters that affect the decision of whether * we can reuse/share an existing cache. */ + obj = NULL; + result = ns_config_get(maps, "cleaning-interval", &obj); + INSIST(result == ISC_R_SUCCESS); + cleaning_interval = cfg_obj_asuint32(obj) * 60; + + obj = NULL; + result = ns_config_get(maps, "max-cache-size", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isstring(obj)) { + str = cfg_obj_asstring(obj); + INSIST(strcasecmp(str, "unlimited") == 0); + max_cache_size = ISC_UINT32_MAX; + } else { + isc_resourcevalue_t value; + value = cfg_obj_asuint64(obj); + if (value > ISC_UINT32_MAX) { + cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, + "'max-cache-size " + "%" ISC_PRINT_QUADFORMAT "d' is too large", + value); + result = ISC_R_RANGE; + goto cleanup; + } + max_cache_size = (isc_uint32_t)value; + } + /* Check-names. */ obj = NULL; result = ns_checknames_get(maps, "response", &obj); @@ -1229,6 +1887,109 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, zero_no_soattl = cfg_obj_asboolean(obj); obj = NULL; + result = ns_config_get(maps, "dns64", &obj); + if (result == ISC_R_SUCCESS && strcmp(view->name, "_bind") && + strcmp(view->name, "_meta")) { + const cfg_listelt_t *element; + isc_netaddr_t na, suffix, *sp; + unsigned int prefixlen; + const char *server, *contact; + const cfg_obj_t *myobj; + + myobj = NULL; + result = ns_config_get(maps, "dns64-server", &myobj); + if (result == ISC_R_SUCCESS) + server = cfg_obj_asstring(myobj); + else + server = NULL; + + myobj = NULL; + result = ns_config_get(maps, "dns64-contact", &myobj); + if (result == ISC_R_SUCCESS) + contact = cfg_obj_asstring(myobj); + else + contact = NULL; + + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *map = cfg_listelt_value(element); + dns_dns64_t *dns64 = NULL; + unsigned int dns64options = 0; + + cfg_obj_asnetprefix(cfg_map_getname(map), &na, + &prefixlen); + + obj = NULL; + (void)cfg_map_get(map, "suffix", &obj); + if (obj != NULL) { + sp = &suffix; + isc_netaddr_fromsockaddr(sp, + cfg_obj_assockaddr(obj)); + } else + sp = NULL; + + clients = mapped = excluded = NULL; + obj = NULL; + (void)cfg_map_get(map, "clients", &obj); + if (obj != NULL) { + result = cfg_acl_fromconfig(obj, config, + ns_g_lctx, actx, + mctx, 0, &clients); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + obj = NULL; + (void)cfg_map_get(map, "mapped", &obj); + if (obj != NULL) { + result = cfg_acl_fromconfig(obj, config, + ns_g_lctx, actx, + mctx, 0, &mapped); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + obj = NULL; + (void)cfg_map_get(map, "exclude", &obj); + if (obj != NULL) { + result = cfg_acl_fromconfig(obj, config, + ns_g_lctx, actx, + mctx, 0, &excluded); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + + obj = NULL; + (void)cfg_map_get(map, "recursive-only", &obj); + if (obj != NULL && cfg_obj_asboolean(obj)) + dns64options |= DNS_DNS64_RECURSIVE_ONLY; + + obj = NULL; + (void)cfg_map_get(map, "break-dnssec", &obj); + if (obj != NULL && cfg_obj_asboolean(obj)) + dns64options |= DNS_DNS64_BREAK_DNSSEC; + + result = dns_dns64_create(mctx, &na, prefixlen, sp, + clients, mapped, excluded, + dns64options, &dns64); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_dns64_append(&view->dns64, dns64); + view->dns64cnt++; + result = dns64_reverse(view, mctx, &na, prefixlen, + server, contact); + if (result != ISC_R_SUCCESS) + goto cleanup; + if (clients != NULL) + dns_acl_detach(&clients); + if (mapped != NULL) + dns_acl_detach(&mapped); + if (excluded != NULL) + dns_acl_detach(&excluded); + } + } + + obj = NULL; result = ns_config_get(maps, "dnssec-accept-expired", &obj); INSIST(result == ISC_R_SUCCESS); view->acceptexpired = cfg_obj_asboolean(obj); @@ -1236,7 +1997,13 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, obj = NULL; result = ns_config_get(maps, "dnssec-validation", &obj); INSIST(result == ISC_R_SUCCESS); - view->enablevalidation = cfg_obj_asboolean(obj); + if (cfg_obj_isboolean(obj)) { + view->enablevalidation = cfg_obj_asboolean(obj); + } else { + /* If dnssec-validation is not boolean, it must be "auto" */ + view->enablevalidation = ISC_TRUE; + auto_root = ISC_TRUE; + } obj = NULL; result = ns_config_get(maps, "max-cache-ttl", &obj); @@ -1251,53 +2018,113 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, view->maxncachettl = 7 * 24 * 3600; /* - * Configure the view's cache. Try to reuse an existing - * cache if possible, otherwise create a new cache. - * Note that the ADB is not preserved in either case. - * When a matching view is found, the associated statistics are - * also retrieved and reused. + * Configure the view's cache. + * + * First, check to see if there are any attach-cache options. If yes, + * attempt to lookup an existing cache at attach it to the view. If + * there is not one, then try to reuse an existing cache if possible; + * otherwise create a new cache. * - * XXX Determining when it is safe to reuse a cache is tricky. + * Note that the ADB is not preserved or shared in either case. + * + * When a matching view is found, the associated statistics are also + * retrieved and reused. + * + * XXX Determining when it is safe to reuse or share a cache is tricky. * When the view's configuration changes, the cached data may become * invalid because it reflects our old view of the world. We check - * some of the configuration parameters that could invalidate the cache, - * but there are other configuration options that should be checked. - * For example, if a view uses a forwarder, changes in the forwarder - * configuration may invalidate the cache. At the moment, it's the - * administrator's responsibility to ensure these configuration options - * don't invalidate reusing. + * some of the configuration parameters that could invalidate the cache + * or otherwise make it unsharable, but there are other configuration + * options that should be checked. For example, if a view uses a + * forwarder, changes in the forwarder configuration may invalidate + * the cache. At the moment, it's the administrator's responsibility to + * ensure these configuration options don't invalidate reusing/sharing. */ - result = dns_viewlist_find(&ns_g_server->viewlist, - view->name, view->rdclass, - &pview); - if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) - goto cleanup; - if (pview != NULL) { - if (cache_reusable(pview, view, zero_no_soattl)) { - INSIST(pview->cache != NULL); - isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, - NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(3), - "reusing existing cache"); - reused_cache = ISC_TRUE; - dns_cache_attach(pview->cache, &cache); - } else { + obj = NULL; + result = ns_config_get(maps, "attach-cache", &obj); + if (result == ISC_R_SUCCESS) + cachename = cfg_obj_asstring(obj); + else + cachename = view->name; + cache = NULL; + nsc = cachelist_find(cachelist, cachename); + if (nsc != NULL) { + if (!cache_sharable(nsc->primaryview, view, zero_no_soattl, + cleaning_interval, max_cache_size)) { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, - NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), - "cache cannot be reused for view %s " + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "views %s and %s can't share the cache " "due to configuration parameter mismatch", - view->name); + nsc->primaryview->name, view->name); + result = ISC_R_FAILURE; + goto cleanup; } - dns_view_getresstats(pview, &resstats); - dns_view_getresquerystats(pview, &resquerystats); - dns_view_detach(&pview); - } - if (cache == NULL) { - CHECK(isc_mem_create(0, 0, &cmctx)); - CHECK(dns_cache_create(cmctx, ns_g_taskmgr, ns_g_timermgr, - view->rdclass, "rbt", 0, NULL, &cache)); - isc_mem_setname(cmctx, "cache", NULL); + dns_cache_attach(nsc->cache, &cache); + shared_cache = ISC_TRUE; + } else { + if (strcmp(cachename, view->name) == 0) { + result = dns_viewlist_find(&ns_g_server->viewlist, + cachename, view->rdclass, + &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + goto cleanup; + if (pview != NULL) { + if (!cache_reusable(pview, view, + zero_no_soattl)) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_DEBUG(1), + "cache cannot be reused " + "for view %s due to " + "configuration parameter " + "mismatch", view->name); + } else { + INSIST(pview->cache != NULL); + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_DEBUG(3), + "reusing existing cache"); + reused_cache = ISC_TRUE; + dns_cache_attach(pview->cache, &cache); + } + dns_view_getresstats(pview, &resstats); + dns_view_getresquerystats(pview, + &resquerystats); + dns_view_detach(&pview); + } + } + if (cache == NULL) { + /* + * Create a cache with the desired name. This normally + * equals the view name, but may also be a forward + * reference to a view that share the cache with this + * view but is not yet configured. If it is not the + * view name but not a forward reference either, then it + * is simply a named cache that is not shared. + */ + CHECK(isc_mem_create(0, 0, &cmctx)); + isc_mem_setname(cmctx, "cache", NULL); + CHECK(dns_cache_create2(cmctx, ns_g_taskmgr, + ns_g_timermgr, view->rdclass, + cachename, "rbt", 0, NULL, + &cache)); + } + nsc = isc_mem_get(mctx, sizeof(*nsc)); + if (nsc == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + nsc->cache = NULL; + dns_cache_attach(cache, &nsc->cache); + nsc->primaryview = view; + nsc->needflush = ISC_FALSE; + nsc->adbsizeadjusted = ISC_FALSE; + ISC_LINK_INIT(nsc, link); + ISC_LIST_APPEND(*cachelist, nsc, link); } - dns_view_setcache(view, cache); + dns_view_setcache2(view, cache, shared_cache); /* * cache-file cannot be inherited if views are present, but this @@ -1307,35 +2134,11 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, result = ns_config_get(maps, "cache-file", &obj); if (result == ISC_R_SUCCESS && strcmp(view->name, "_bind") != 0) { CHECK(dns_cache_setfilename(cache, cfg_obj_asstring(obj))); - if (!reused_cache) + if (!reused_cache && !shared_cache) CHECK(dns_cache_load(cache)); } - obj = NULL; - result = ns_config_get(maps, "cleaning-interval", &obj); - INSIST(result == ISC_R_SUCCESS); - dns_cache_setcleaninginterval(cache, cfg_obj_asuint32(obj) * 60); - - obj = NULL; - result = ns_config_get(maps, "max-cache-size", &obj); - INSIST(result == ISC_R_SUCCESS); - if (cfg_obj_isstring(obj)) { - str = cfg_obj_asstring(obj); - INSIST(strcasecmp(str, "unlimited") == 0); - max_cache_size = ISC_UINT32_MAX; - } else { - isc_resourcevalue_t value; - value = cfg_obj_asuint64(obj); - if (value > ISC_UINT32_MAX) { - cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, - "'max-cache-size " - "%" ISC_PRINT_QUADFORMAT "d' is too large", - value); - result = ISC_R_RANGE; - goto cleanup; - } - max_cache_size = (isc_uint32_t)value; - } + dns_cache_setcleaninginterval(cache, cleaning_interval); dns_cache_setcachesize(cache, max_cache_size); dns_cache_detach(&cache); @@ -1373,13 +2176,23 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, dns_view_setresquerystats(view, resquerystats); /* - * Set the ADB cache size to 1/8th of the max-cache-size. + * Set the ADB cache size to 1/8th of the max-cache-size or + * MAX_ADB_SIZE_FOR_CACHESHARE when the cache is shared. */ max_adb_size = 0; if (max_cache_size != 0) { max_adb_size = max_cache_size / 8; if (max_adb_size == 0) max_adb_size = 1; /* Force minimum. */ + if (view != nsc->primaryview && + max_adb_size > MAX_ADB_SIZE_FOR_CACHESHARE) { + max_adb_size = MAX_ADB_SIZE_FOR_CACHESHARE; + if (!nsc->adbsizeadjusted) { + dns_adb_setadbsize(nsc->primaryview->adb, + MAX_ADB_SIZE_FOR_CACHESHARE); + nsc->adbsizeadjusted = ISC_TRUE; + } + } } dns_adb_setadbsize(view->adb, max_adb_size); @@ -1395,6 +2208,18 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, dns_resolver_setlamettl(view->resolver, lame_ttl); /* + * Set the resolver's query timeout. + */ + obj = NULL; + result = ns_config_get(maps, "resolver-query-timeout", &obj); + INSIST(result == ISC_R_SUCCESS); + query_timeout = cfg_obj_asuint32(obj); + dns_resolver_settimeout(view->resolver, query_timeout); + + /* Specify whether to use 0-TTL for negative response for SOA query */ + dns_resolver_setzeronosoattl(view->resolver, zero_no_soattl); + + /* * Set the resolver's EDNS UDP size. */ obj = NULL; @@ -1484,9 +2309,29 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, /* * Configure the view's TSIG keys. */ - ring = NULL; CHECK(ns_tsigkeyring_fromconfig(config, vconfig, view->mctx, &ring)); + if (ns_g_server->sessionkey != NULL) { + CHECK(dns_tsigkeyring_add(ring, ns_g_server->session_keyname, + ns_g_server->sessionkey)); + } dns_view_setkeyring(view, ring); + dns_tsigkeyring_detach(&ring); + + /* + * See if we can re-use a dynamic key ring. + */ + result = dns_viewlist_find(&ns_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + goto cleanup; + if (pview != NULL) { + dns_view_getdynamickeyring(pview, &ring); + if (ring != NULL) + dns_view_setdynamickeyring(view, ring); + dns_tsigkeyring_detach(&ring); + dns_view_detach(&pview); + } else + dns_view_restorekeyring(view); /* * Configure the view's peer list. @@ -1543,10 +2388,10 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, /* * Configure the "match-clients" and "match-destinations" ACL. */ - CHECK(configure_view_acl(vconfig, config, "match-clients", actx, + CHECK(configure_view_acl(vconfig, config, "match-clients", NULL, actx, ns_g_mctx, &view->matchclients)); - CHECK(configure_view_acl(vconfig, config, "match-destinations", actx, - ns_g_mctx, &view->matchdestinations)); + CHECK(configure_view_acl(vconfig, config, "match-destinations", NULL, + actx, ns_g_mctx, &view->matchdestinations)); /* * Configure the "match-recursive-only" option. @@ -1618,20 +2463,20 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, * "allow-recursion", and "allow-recursion-on" acls if * configured in named.conf. */ - CHECK(configure_view_acl(vconfig, config, "allow-query-cache", + CHECK(configure_view_acl(vconfig, config, "allow-query-cache", NULL, actx, ns_g_mctx, &view->cacheacl)); - CHECK(configure_view_acl(vconfig, config, "allow-query-cache-on", + CHECK(configure_view_acl(vconfig, config, "allow-query-cache-on", NULL, actx, ns_g_mctx, &view->cacheonacl)); if (view->cacheonacl == NULL) CHECK(configure_view_acl(NULL, ns_g_config, - "allow-query-cache-on", actx, + "allow-query-cache-on", NULL, actx, ns_g_mctx, &view->cacheonacl)); if (strcmp(view->name, "_bind") != 0) { CHECK(configure_view_acl(vconfig, config, "allow-recursion", - actx, ns_g_mctx, + NULL, actx, ns_g_mctx, &view->recursionacl)); CHECK(configure_view_acl(vconfig, config, "allow-recursion-on", - actx, ns_g_mctx, + NULL, actx, ns_g_mctx, &view->recursiononacl)); } @@ -1643,8 +2488,14 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, */ if (view->cacheacl == NULL && view->recursionacl != NULL) dns_acl_attach(view->recursionacl, &view->cacheacl); + /* + * XXXEACH: This call to configure_view_acl() is redundant. We + * are leaving it as it is because we are making a minimal change + * for a patch release. In the future this should be changed to + * dns_acl_attach(view->queryacl, &view->cacheacl). + */ if (view->cacheacl == NULL && view->recursion) - CHECK(configure_view_acl(vconfig, config, "allow-query", + CHECK(configure_view_acl(vconfig, config, "allow-query", NULL, actx, ns_g_mctx, &view->cacheacl)); if (view->recursion && view->recursionacl == NULL && view->cacheacl != NULL) @@ -1656,24 +2507,44 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, */ if (view->recursionacl == NULL && view->recursion) CHECK(configure_view_acl(NULL, ns_g_config, - "allow-recursion", + "allow-recursion", NULL, actx, ns_g_mctx, &view->recursionacl)); if (view->recursiononacl == NULL && view->recursion) CHECK(configure_view_acl(NULL, ns_g_config, - "allow-recursion-on", + "allow-recursion-on", NULL, actx, ns_g_mctx, &view->recursiononacl)); if (view->cacheacl == NULL) { if (view->recursion) CHECK(configure_view_acl(NULL, ns_g_config, - "allow-query-cache", actx, - ns_g_mctx, &view->cacheacl)); + "allow-query-cache", NULL, + actx, ns_g_mctx, + &view->cacheacl)); else - CHECK(dns_acl_none(ns_g_mctx, &view->cacheacl)); + CHECK(dns_acl_none(mctx, &view->cacheacl)); } /* + * Filter setting on addresses in the answer section. + */ + CHECK(configure_view_acl(vconfig, config, "deny-answer-addresses", + "acl", actx, ns_g_mctx, &view->denyansweracl)); + CHECK(configure_view_nametable(vconfig, config, "deny-answer-addresses", + "except-from", ns_g_mctx, + &view->answeracl_exclude)); + + /* + * Filter setting on names (CNAME/DNAME targets) in the answer section. + */ + CHECK(configure_view_nametable(vconfig, config, "deny-answer-aliases", + "name", ns_g_mctx, + &view->denyanswernames)); + CHECK(configure_view_nametable(vconfig, config, "deny-answer-aliases", + "except-from", ns_g_mctx, + &view->answernames_exclude)); + + /* * Configure sortlist, if set */ CHECK(configure_view_sortlist(vconfig, config, actx, ns_g_mctx, @@ -1686,19 +2557,19 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, */ if (view->notifyacl == NULL) CHECK(configure_view_acl(NULL, ns_g_config, - "allow-notify", actx, + "allow-notify", NULL, actx, ns_g_mctx, &view->notifyacl)); if (view->transferacl == NULL) CHECK(configure_view_acl(NULL, ns_g_config, - "allow-transfer", actx, + "allow-transfer", NULL, actx, ns_g_mctx, &view->transferacl)); if (view->updateacl == NULL) CHECK(configure_view_acl(NULL, ns_g_config, - "allow-update", actx, + "allow-update", NULL, actx, ns_g_mctx, &view->updateacl)); if (view->upfwdacl == NULL) CHECK(configure_view_acl(NULL, ns_g_config, - "allow-update-forwarding", actx, + "allow-update-forwarding", NULL, actx, ns_g_mctx, &view->upfwdacl)); obj = NULL; @@ -1728,13 +2599,47 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, cfg_obj_asuint32(obj), max_clients_per_query); +#ifdef ALLOW_FILTER_AAAA_ON_V4 + obj = NULL; + result = ns_config_get(maps, "filter-aaaa-on-v4", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) + view->v4_aaaa = dns_v4_aaaa_filter; + else + view->v4_aaaa = dns_v4_aaaa_ok; + } else { + const char *v4_aaaastr = cfg_obj_asstring(obj); + if (strcasecmp(v4_aaaastr, "break-dnssec") == 0) + view->v4_aaaa = dns_v4_aaaa_break_dnssec; + else + INSIST(0); + } + CHECK(configure_view_acl(vconfig, config, "filter-aaaa", NULL, + actx, ns_g_mctx, &view->v4_aaaa_acl)); +#endif + obj = NULL; result = ns_config_get(maps, "dnssec-enable", &obj); INSIST(result == ISC_R_SUCCESS); view->enablednssec = cfg_obj_asboolean(obj); obj = NULL; - result = ns_config_get(maps, "dnssec-lookaside", &obj); + result = ns_config_get(optionmaps, "dnssec-lookaside", &obj); + if (result == ISC_R_SUCCESS) { + /* If set to "auto", use the version from the defaults */ + const cfg_obj_t *dlvobj; + dlvobj = cfg_listelt_value(cfg_list_first(obj)); + if (!strcmp(cfg_obj_asstring(cfg_tuple_get(dlvobj, "domain")), + "auto") && + cfg_obj_isvoid(cfg_tuple_get(dlvobj, "trust-anchor"))) { + auto_dlv = ISC_TRUE; + obj = NULL; + result = cfg_map_get(ns_g_defaults, + "dnssec-lookaside", &obj); + } + } + if (result == ISC_R_SUCCESS) { for (element = cfg_list_first(obj); element != NULL; @@ -1745,31 +2650,13 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, dns_name_t *dlv; obj = cfg_listelt_value(element); -#if 0 - dns_fixedname_t fixed; - dns_name_t *name; - - /* - * When we support multiple dnssec-lookaside - * entries this is how to find the domain to be - * checked. XXXMPA - */ - dns_fixedname_init(&fixed); - name = dns_fixedname_name(&fixed); - str = cfg_obj_asstring(cfg_tuple_get(obj, - "domain")); - isc_buffer_init(&b, str, strlen(str)); - isc_buffer_add(&b, strlen(str)); - CHECK(dns_name_fromtext(name, &b, dns_rootname, - ISC_TRUE, NULL)); -#endif str = cfg_obj_asstring(cfg_tuple_get(obj, "trust-anchor")); isc_buffer_init(&b, str, strlen(str)); isc_buffer_add(&b, strlen(str)); dlv = dns_fixedname_name(&view->dlv_fixed); CHECK(dns_name_fromtext(dlv, &b, dns_rootname, - ISC_TRUE, NULL)); + DNS_NAME_DOWNCASE, NULL)); view->dlv = dns_fixedname_name(&view->dlv_fixed); } } else @@ -1779,8 +2666,8 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, * For now, there is only one kind of trusted keys, the * "security roots". */ - CHECK(configure_view_dnsseckeys(vconfig, config, mctx, - &view->secroots)); + CHECK(configure_view_dnsseckeys(view, vconfig, config, bindkeys, + auto_dlv, auto_root, mctx)); dns_resolver_resetmustbesecure(view->resolver); obj = NULL; result = ns_config_get(maps, "dnssec-must-be-secure", &obj); @@ -1821,7 +2708,7 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, isc_buffer_init(&b, str, strlen(str)); isc_buffer_add(&b, strlen(str)); CHECK(dns_name_fromtext(name, &b, dns_rootname, - ISC_FALSE, NULL)); + 0, NULL)); CHECK(dns_view_excludedelegationonly(view, name)); } @@ -1874,8 +2761,8 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, str = cfg_obj_asstring(obj); isc_buffer_init(&buffer, str, strlen(str)); isc_buffer_add(&buffer, strlen(str)); - CHECK(dns_name_fromtext(name, &buffer, dns_rootname, - ISC_FALSE, NULL)); + CHECK(dns_name_fromtext(name, &buffer, dns_rootname, 0, + NULL)); isc_buffer_init(&buffer, server, sizeof(server) - 1); CHECK(dns_name_totext(name, ISC_FALSE, &buffer)); server[isc_buffer_usedlength(&buffer)] = 0; @@ -1889,8 +2776,8 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, str = cfg_obj_asstring(obj); isc_buffer_init(&buffer, str, strlen(str)); isc_buffer_add(&buffer, strlen(str)); - CHECK(dns_name_fromtext(name, &buffer, dns_rootname, - ISC_FALSE, NULL)); + CHECK(dns_name_fromtext(name, &buffer, dns_rootname, 0, + NULL)); isc_buffer_init(&buffer, contact, sizeof(contact) - 1); CHECK(dns_name_totext(name, ISC_FALSE, &buffer)); contact[isc_buffer_usedlength(&buffer)] = 0; @@ -1916,8 +2803,8 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, /* * Look for zone on drop list. */ - CHECK(dns_name_fromtext(name, &buffer, dns_rootname, - ISC_FALSE, NULL)); + CHECK(dns_name_fromtext(name, &buffer, dns_rootname, 0, + NULL)); if (disablelist != NULL && on_disable_list(disablelist, name)) continue; @@ -2012,9 +2899,40 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, } } + /* + * Make the list of response policy zone names for views that + * are used for real lookups and so care about hints. + */ + zonelist = NULL; + if (view->rdclass == dns_rdataclass_in && need_hints) { + obj = NULL; + result = ns_config_get(maps, "response-policy", &obj); + if (result == ISC_R_SUCCESS) + cfg_map_get(obj, "zone", &zonelist); + } + if (zonelist != NULL) { + + for (element = cfg_list_first(zonelist); + element != NULL; + element = cfg_list_next(element)) { + result = configure_rpz(view, element); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_rpz_set_need(ISC_TRUE); + } + } + result = ISC_R_SUCCESS; cleanup: + if (clients != NULL) + dns_acl_detach(&clients); + if (mapped != NULL) + dns_acl_detach(&mapped); + if (excluded != NULL) + dns_acl_detach(&excluded); + if (ring != NULL) + dns_tsigkeyring_detach(&ring); if (zone != NULL) dns_zone_detach(&zone); if (dispatch4 != NULL) @@ -2033,6 +2951,12 @@ configure_view(dns_view_t *view, const cfg_obj_t *config, if (cache != NULL) dns_cache_detach(&cache); + if (newzones_parser != NULL) { + if (nzfconf != NULL) + cfg_obj_destroy(newzones_parser, &nzfconf); + cfg_parser_destroy(&newzones_parser); + } + return (result); } @@ -2105,8 +3029,8 @@ configure_alternates(const cfg_obj_t *config, dns_view_t *view, isc_buffer_add(&buffer, strlen(str)); dns_fixedname_init(&fixed); name = dns_fixedname_name(&fixed); - CHECK(dns_name_fromtext(name, &buffer, dns_rootname, - ISC_FALSE, NULL)); + CHECK(dns_name_fromtext(name, &buffer, dns_rootname, 0, + NULL)); portobj = cfg_tuple_get(alternate, "port"); if (cfg_obj_isuint32(portobj)) { @@ -2286,7 +3210,7 @@ create_view(const cfg_obj_t *vconfig, dns_viewlist_t *viewlist, static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, - cfg_aclconfctx_t *aclconf) + cfg_aclconfctx_t *aclconf, isc_boolean_t added) { dns_view_t *pview = NULL; /* Production view */ dns_zone_t *zone = NULL; /* New or reused zone */ @@ -2319,7 +3243,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, isc_buffer_add(&buffer, strlen(zname)); dns_fixedname_init(&fixorigin); CHECK(dns_name_fromtext(dns_fixedname_name(&fixorigin), - &buffer, dns_rootname, ISC_FALSE, NULL)); + &buffer, dns_rootname, 0, NULL)); origin = dns_fixedname_name(&fixorigin); CHECK(ns_config_getclass(cfg_tuple_get(zconfig, "class"), @@ -2349,7 +3273,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, ztypestr = cfg_obj_asstring(typeobj); /* - * "hints zones" aren't zones. If we've got one, + * "hints zones" aren't zones. If we've got one, * configure it and return. */ if (strcasecmp(ztypestr, "hint") == 0) { @@ -2500,6 +3424,11 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, } /* + * Mark whether the zone was originally added at runtime or not + */ + dns_zone_setadded(zone, added); + + /* * Configure the zone. */ CHECK(ns_zone_configure(config, vconfig, zconfig, aclconf, zone)); @@ -2519,6 +3448,95 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, } /* + * Configure built-in zone for storing managed-key data. + */ + +#define KEYZONE "managed-keys.bind" +#define MKEYS ".mkeys" + +static isc_result_t +add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx) { + isc_result_t result; + dns_view_t *pview = NULL; + dns_zone_t *zone = NULL; + dns_acl_t *none = NULL; + char filename[PATH_MAX]; + char buffer[ISC_SHA256_DIGESTSTRINGLENGTH + sizeof(MKEYS)]; + int n; + + REQUIRE(view != NULL); + + /* See if we can re-use an existing keydata zone. */ + result = dns_viewlist_find(&ns_g_server->viewlist, + view->name, view->rdclass, + &pview); + if (result != ISC_R_NOTFOUND && + result != ISC_R_SUCCESS) + return (result); + + if (pview != NULL && pview->managed_keys != NULL) { + dns_zone_attach(pview->managed_keys, &view->managed_keys); + dns_zone_setview(pview->managed_keys, view); + dns_view_detach(&pview); + return (ISC_R_SUCCESS); + } + + /* No existing keydata zone was found; create one */ + CHECK(dns_zone_create(&zone, mctx)); + CHECK(dns_zone_setorigin(zone, dns_rootname)); + + isc_sha256_data((void *)view->name, strlen(view->name), buffer); + strcat(buffer, MKEYS); + n = snprintf(filename, sizeof(filename), "%s%s%s", + directory ? directory : "", directory ? "/" : "", + strcmp(view->name, "_default") == 0 ? KEYZONE : buffer); + if (n < 0 || (size_t)n >= sizeof(filename)) { + result = (n < 0) ? ISC_R_FAILURE : ISC_R_NOSPACE; + goto cleanup; + } + CHECK(dns_zone_setfile(zone, filename)); + + dns_zone_setview(zone, view); + dns_zone_settype(zone, dns_zone_key); + dns_zone_setclass(zone, view->rdclass); + + CHECK(dns_zonemgr_managezone(ns_g_server->zonemgr, zone)); + + if (view->acache != NULL) + dns_zone_setacache(zone, view->acache); + + CHECK(dns_acl_none(mctx, &none)); + dns_zone_setqueryacl(zone, none); + dns_zone_setqueryonacl(zone, none); + dns_acl_detach(&none); + + dns_zone_setdialup(zone, dns_dialuptype_no); + dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, ISC_TRUE); + dns_zone_setjournalsize(zone, 0); + + dns_zone_setstats(zone, ns_g_server->zonestats); + CHECK(setquerystats(zone, mctx, ISC_FALSE)); + + if (view->managed_keys != NULL) + dns_zone_detach(&view->managed_keys); + dns_zone_attach(zone, &view->managed_keys); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "set up managed keys zone for view %s, file '%s'", + view->name, filename); + +cleanup: + if (zone != NULL) + dns_zone_detach(&zone); + if (none != NULL) + dns_acl_detach(&none); + + return (result); +} + +/* * Configure a single server quota. */ static void @@ -2914,13 +3932,226 @@ removed(dns_zone_t *zone, void *uap) { return (ISC_R_SUCCESS); } +static void +cleanup_session_key(ns_server_t *server, isc_mem_t *mctx) { + if (server->session_keyfile != NULL) { + isc_file_remove(server->session_keyfile); + isc_mem_free(mctx, server->session_keyfile); + server->session_keyfile = NULL; + } + + if (server->session_keyname != NULL) { + if (dns_name_dynamic(server->session_keyname)) + dns_name_free(server->session_keyname, mctx); + isc_mem_put(mctx, server->session_keyname, sizeof(dns_name_t)); + server->session_keyname = NULL; + } + + if (server->sessionkey != NULL) + dns_tsigkey_detach(&server->sessionkey); + + server->session_keyalg = DST_ALG_UNKNOWN; + server->session_keybits = 0; +} + +static isc_result_t +generate_session_key(const char *filename, const char *keynamestr, + dns_name_t *keyname, const char *algstr, + dns_name_t *algname, unsigned int algtype, + isc_uint16_t bits, isc_mem_t *mctx, + dns_tsigkey_t **tsigkeyp) +{ + isc_result_t result = ISC_R_SUCCESS; + dst_key_t *key = NULL; + isc_buffer_t key_txtbuffer; + isc_buffer_t key_rawbuffer; + char key_txtsecret[256]; + char key_rawsecret[64]; + isc_region_t key_rawregion; + isc_stdtime_t now; + dns_tsigkey_t *tsigkey = NULL; + FILE *fp = NULL; + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "generating session key for dynamic DNS"); + + /* generate key */ + result = dst_key_generate(keyname, algtype, bits, 1, 0, + DNS_KEYPROTO_ANY, dns_rdataclass_in, + mctx, &key); + if (result != ISC_R_SUCCESS) + return (result); + + /* + * Dump the key to the buffer for later use. Should be done before + * we transfer the ownership of key to tsigkey. + */ + isc_buffer_init(&key_rawbuffer, &key_rawsecret, sizeof(key_rawsecret)); + CHECK(dst_key_tobuffer(key, &key_rawbuffer)); + + isc_buffer_usedregion(&key_rawbuffer, &key_rawregion); + isc_buffer_init(&key_txtbuffer, &key_txtsecret, sizeof(key_txtsecret)); + CHECK(isc_base64_totext(&key_rawregion, -1, "", &key_txtbuffer)); + + /* Store the key in tsigkey. */ + isc_stdtime_get(&now); + CHECK(dns_tsigkey_createfromkey(dst_key_name(key), algname, key, + ISC_FALSE, NULL, now, now, mctx, NULL, + &tsigkey)); + + /* Dump the key to the key file. */ + fp = ns_os_openfile(filename, S_IRUSR|S_IWUSR, ISC_TRUE); + if (fp == NULL) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "could not create %s", filename); + result = ISC_R_NOPERM; + goto cleanup; + } + + fprintf(fp, "key \"%s\" {\n" + "\talgorithm %s;\n" + "\tsecret \"%.*s\";\n};\n", keynamestr, algstr, + (int) isc_buffer_usedlength(&key_txtbuffer), + (char*) isc_buffer_base(&key_txtbuffer)); + + RUNTIME_CHECK(isc_stdio_flush(fp) == ISC_R_SUCCESS); + RUNTIME_CHECK(isc_stdio_close(fp) == ISC_R_SUCCESS); + + dst_key_free(&key); + + *tsigkeyp = tsigkey; + + return (ISC_R_SUCCESS); + + cleanup: + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "failed to generate session key " + "for dynamic DNS: %s", isc_result_totext(result)); + if (tsigkey != NULL) + dns_tsigkey_detach(&tsigkey); + if (key != NULL) + dst_key_free(&key); + + return (result); +} + +static isc_result_t +configure_session_key(const cfg_obj_t **maps, ns_server_t *server, + isc_mem_t *mctx) +{ + const char *keyfile, *keynamestr, *algstr; + unsigned int algtype; + dns_fixedname_t fname; + dns_name_t *keyname, *algname; + isc_buffer_t buffer; + isc_uint16_t bits; + const cfg_obj_t *obj; + isc_boolean_t need_deleteold = ISC_FALSE; + isc_boolean_t need_createnew = ISC_FALSE; + isc_result_t result; + + obj = NULL; + result = ns_config_get(maps, "session-keyfile", &obj); + if (result == ISC_R_SUCCESS) { + if (cfg_obj_isvoid(obj)) + keyfile = NULL; /* disable it */ + else + keyfile = cfg_obj_asstring(obj); + } else + keyfile = ns_g_defaultsessionkeyfile; + + obj = NULL; + result = ns_config_get(maps, "session-keyname", &obj); + INSIST(result == ISC_R_SUCCESS); + keynamestr = cfg_obj_asstring(obj); + dns_fixedname_init(&fname); + isc_buffer_init(&buffer, keynamestr, strlen(keynamestr)); + isc_buffer_add(&buffer, strlen(keynamestr)); + keyname = dns_fixedname_name(&fname); + result = dns_name_fromtext(keyname, &buffer, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) + return (result); + + obj = NULL; + result = ns_config_get(maps, "session-keyalg", &obj); + INSIST(result == ISC_R_SUCCESS); + algstr = cfg_obj_asstring(obj); + algname = NULL; + result = ns_config_getkeyalgorithm2(algstr, &algname, &algtype, &bits); + if (result != ISC_R_SUCCESS) { + const char *s = " (keeping current key)"; + + cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, "session-keyalg: " + "unsupported or unknown algorithm '%s'%s", + algstr, + server->session_keyfile != NULL ? s : ""); + return (result); + } + + /* See if we need to (re)generate a new key. */ + if (keyfile == NULL) { + if (server->session_keyfile != NULL) + need_deleteold = ISC_TRUE; + } else if (server->session_keyfile == NULL) + need_createnew = ISC_TRUE; + else if (strcmp(keyfile, server->session_keyfile) != 0 || + !dns_name_equal(server->session_keyname, keyname) || + server->session_keyalg != algtype || + server->session_keybits != bits) { + need_deleteold = ISC_TRUE; + need_createnew = ISC_TRUE; + } + + if (need_deleteold) { + INSIST(server->session_keyfile != NULL); + INSIST(server->session_keyname != NULL); + INSIST(server->sessionkey != NULL); + + cleanup_session_key(server, mctx); + } + + if (need_createnew) { + INSIST(server->sessionkey == NULL); + INSIST(server->session_keyfile == NULL); + INSIST(server->session_keyname == NULL); + INSIST(server->session_keyalg == DST_ALG_UNKNOWN); + INSIST(server->session_keybits == 0); + + server->session_keyname = isc_mem_get(mctx, sizeof(dns_name_t)); + if (server->session_keyname == NULL) + goto cleanup; + dns_name_init(server->session_keyname, NULL); + CHECK(dns_name_dup(keyname, mctx, server->session_keyname)); + + server->session_keyfile = isc_mem_strdup(mctx, keyfile); + if (server->session_keyfile == NULL) + goto cleanup; + + server->session_keyalg = algtype; + server->session_keybits = bits; + + CHECK(generate_session_key(keyfile, keynamestr, keyname, algstr, + algname, algtype, bits, mctx, + &server->sessionkey)); + } + + return (result); + + cleanup: + cleanup_session_key(server, mctx); + return (result); +} + static isc_result_t load_configuration(const char *filename, ns_server_t *server, isc_boolean_t first_time) { cfg_aclconfctx_t aclconfctx; - cfg_obj_t *config; - cfg_parser_t *parser = NULL; + cfg_obj_t *config = NULL, *bindkeys = NULL; + cfg_parser_t *conf_parser = NULL, *bindkeys_parser = NULL; const cfg_listelt_t *element; const cfg_obj_t *builtin_views; const cfg_obj_t *maps[3]; @@ -2931,7 +4162,7 @@ load_configuration(const char *filename, ns_server_t *server, dns_view_t *view = NULL; dns_view_t *view_next; dns_viewlist_t tmpviewlist; - dns_viewlist_t viewlist; + dns_viewlist_t viewlist, builtin_viewlist; in_port_t listen_port, udpport_low, udpport_high; int i; isc_interval_t interval; @@ -2943,10 +4174,14 @@ load_configuration(const char *filename, ns_server_t *server, isc_uint32_t interface_interval; isc_uint32_t reserved; isc_uint32_t udpsize; + ns_cachelist_t cachelist, tmpcachelist; unsigned int maxsocks; + ns_cache_t *nsc; cfg_aclconfctx_init(&aclconfctx); ISC_LIST_INIT(viewlist); + ISC_LIST_INIT(builtin_viewlist); + ISC_LIST_INIT(cachelist); /* Ensure exclusive access to configuration data. */ result = isc_task_beginexclusive(server->task); @@ -2958,8 +4193,7 @@ load_configuration(const char *filename, ns_server_t *server, if (first_time) { CHECK(ns_config_parsedefaults(ns_g_parser, &ns_g_config)); RUNTIME_CHECK(cfg_map_get(ns_g_config, "options", - &ns_g_defaults) == - ISC_R_SUCCESS); + &ns_g_defaults) == ISC_R_SUCCESS); } /* @@ -2976,10 +4210,10 @@ load_configuration(const char *filename, ns_server_t *server, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_INFO, "loading configuration from '%s'", filename); - CHECK(cfg_parser_create(ns_g_mctx, ns_g_lctx, &parser)); - cfg_parser_setcallback(parser, directory_callback, NULL); - result = cfg_parse_file(parser, filename, &cfg_type_namedconf, - &config); + CHECK(cfg_parser_create(ns_g_mctx, ns_g_lctx, &conf_parser)); + cfg_parser_setcallback(conf_parser, directory_callback, NULL); + result = cfg_parse_file(conf_parser, filename, + &cfg_type_namedconf, &config); } /* @@ -2994,10 +4228,10 @@ load_configuration(const char *filename, ns_server_t *server, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_INFO, "loading configuration from '%s'", lwresd_g_resolvconffile); - if (parser != NULL) - cfg_parser_destroy(&parser); - CHECK(cfg_parser_create(ns_g_mctx, ns_g_lctx, &parser)); - result = ns_lwresd_parseeresolvconf(ns_g_mctx, parser, + if (conf_parser != NULL) + cfg_parser_destroy(&conf_parser); + CHECK(cfg_parser_create(ns_g_mctx, ns_g_lctx, &conf_parser)); + result = ns_lwresd_parseeresolvconf(ns_g_mctx, conf_parser, &config); } CHECK(result); @@ -3019,13 +4253,38 @@ load_configuration(const char *filename, ns_server_t *server, maps[i++] = NULL; /* + * If bind.keys exists, load it. If "dnssec-lookaside auto" + * is turned on, the keys found there will be used as default + * trust anchors. + */ + obj = NULL; + result = ns_config_get(maps, "bindkeys-file", &obj); + INSIST(result == ISC_R_SUCCESS); + CHECKM(setstring(server, &server->bindkeysfile, + cfg_obj_asstring(obj)), "strdup"); + + if (access(server->bindkeysfile, R_OK) == 0) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "reading built-in trusted " + "keys from file '%s'", server->bindkeysfile); + + CHECK(cfg_parser_create(ns_g_mctx, ns_g_lctx, + &bindkeys_parser)); + + result = cfg_parse_file(bindkeys_parser, server->bindkeysfile, + &cfg_type_bindkeys, &bindkeys); + CHECK(result); + } + + /* * Set process limits, which (usually) needs to be done as root. */ set_limits(maps); /* * Check if max number of open sockets that the system allows is - * sufficiently large. Failing this condition is not necessarily fatal, + * sufficiently large. Failing this condition is not necessarily fatal, * but may cause subsequent runtime failures for a busy recursive * server. */ @@ -3078,7 +4337,7 @@ load_configuration(const char *filename, ns_server_t *server, else isc_quota_soft(&server->recursionquota, 0); - CHECK(configure_view_acl(NULL, config, "blackhole", &aclconfctx, + CHECK(configure_view_acl(NULL, config, "blackhole", NULL, &aclconfctx, ns_g_mctx, &server->blackholeacl)); if (server->blackholeacl != NULL) dns_dispatchmgr_setblackhole(ns_g_dispatchmgr, @@ -3318,6 +4577,31 @@ load_configuration(const char *filename, ns_server_t *server, &interval, ISC_FALSE)); /* + * Write the PID file. + */ + obj = NULL; + if (ns_config_get(maps, "pid-file", &obj) == ISC_R_SUCCESS) + if (cfg_obj_isvoid(obj)) + ns_os_writepidfile(NULL, first_time); + else + ns_os_writepidfile(cfg_obj_asstring(obj), first_time); + else if (ns_g_lwresdonly) + ns_os_writepidfile(lwresd_g_defaultpidfile, first_time); + else + ns_os_writepidfile(ns_g_defaultpidfile, first_time); + + /* + * Configure the server-wide session key. This must be done before + * configure views because zone configuration may need to know + * session-keyname. + * + * Failure of session key generation isn't fatal at this time; if it + * turns out that a session key is really needed but doesn't exist, + * we'll treat it as a fatal error then. + */ + (void)configure_session_key(maps, server, ns_g_mctx); + + /* * Configure and freeze all explicit views. Explicit * views that have zones were already created at parsing * time, but views with no zones must be created here. @@ -3328,12 +4612,13 @@ load_configuration(const char *filename, ns_server_t *server, element != NULL; element = cfg_list_next(element)) { - const cfg_obj_t *vconfig = cfg_listelt_value(element); + cfg_obj_t *vconfig = cfg_listelt_value(element); view = NULL; CHECK(create_view(vconfig, &viewlist, &view)); INSIST(view != NULL); - CHECK(configure_view(view, config, vconfig, + CHECK(configure_view(view, conf_parser, config, vconfig, + &cachelist, bindkeys, ns_g_mctx, &aclconfctx, ISC_TRUE)); dns_view_freeze(view); dns_view_detach(&view); @@ -3351,15 +4636,15 @@ load_configuration(const char *filename, ns_server_t *server, * In either case, we need to configure and freeze it. */ CHECK(create_view(NULL, &viewlist, &view)); - CHECK(configure_view(view, config, NULL, ns_g_mctx, - &aclconfctx, ISC_TRUE)); + CHECK(configure_view(view, conf_parser, config, NULL, + &cachelist, bindkeys, + ns_g_mctx, &aclconfctx, ISC_TRUE)); dns_view_freeze(view); dns_view_detach(&view); } /* - * Create (or recreate) the built-in views. Currently - * there is only one, the _bind view. + * Create (or recreate) the built-in views. */ builtin_views = NULL; RUNTIME_CHECK(cfg_map_get(ns_g_config, "view", @@ -3368,25 +4653,38 @@ load_configuration(const char *filename, ns_server_t *server, element != NULL; element = cfg_list_next(element)) { - const cfg_obj_t *vconfig = cfg_listelt_value(element); - CHECK(create_view(vconfig, &viewlist, &view)); - CHECK(configure_view(view, config, vconfig, ns_g_mctx, - &aclconfctx, ISC_FALSE)); + cfg_obj_t *vconfig = cfg_listelt_value(element); + + CHECK(create_view(vconfig, &builtin_viewlist, &view)); + CHECK(configure_view(view, conf_parser, config, vconfig, + &cachelist, bindkeys, + ns_g_mctx, &aclconfctx, ISC_FALSE)); dns_view_freeze(view); dns_view_detach(&view); view = NULL; } - /* - * Swap our new view list with the production one. - */ + /* Now combine the two viewlists into one */ + ISC_LIST_APPENDLIST(viewlist, builtin_viewlist, link); + + /* Swap our new view list with the production one. */ tmpviewlist = server->viewlist; server->viewlist = viewlist; viewlist = tmpviewlist; - /* - * Load the TKEY information from the configuration. - */ + /* Make the view list available to each of the views */ + view = ISC_LIST_HEAD(server->viewlist); + while (view != NULL) { + view->viewlist = &server->viewlist; + view = ISC_LIST_NEXT(view, link); + } + + /* Swap our new cache list with the production one. */ + tmpcachelist = server->cachelist; + server->cachelist = cachelist; + cachelist = tmpcachelist; + + /* Load the TKEY information from the configuration. */ if (options != NULL) { dns_tkeyctx_t *t = NULL; CHECKM(ns_tkeyctx_fromconfig(options, ns_g_mctx, ns_g_entropy, @@ -3551,16 +4849,6 @@ load_configuration(const char *filename, ns_server_t *server, } } - obj = NULL; - if (ns_config_get(maps, "pid-file", &obj) == ISC_R_SUCCESS) - if (cfg_obj_isvoid(obj)) - ns_os_writepidfile(NULL, first_time); - else - ns_os_writepidfile(cfg_obj_asstring(obj), first_time); - else if (ns_g_lwresdonly) - ns_os_writepidfile(lwresd_g_defaultpidfile, first_time); - else - ns_os_writepidfile(ns_g_defaultpidfile, first_time); obj = NULL; if (options != NULL && @@ -3591,6 +4879,12 @@ load_configuration(const char *filename, ns_server_t *server, "strdup"); obj = NULL; + result = ns_config_get(maps, "secroots-file", &obj); + INSIST(result == ISC_R_SUCCESS); + CHECKM(setstring(server, &server->secrootsfile, cfg_obj_asstring(obj)), + "strdup"); + + obj = NULL; result = ns_config_get(maps, "recursing-file", &obj); INSIST(result == ISC_R_SUCCESS); CHECKM(setstring(server, &server->recfile, cfg_obj_asstring(obj)), @@ -3647,12 +4941,18 @@ load_configuration(const char *filename, ns_server_t *server, if (v6portset != NULL) isc_portset_destroy(ns_g_mctx, &v6portset); - cfg_aclconfctx_destroy(&aclconfctx); + cfg_aclconfctx_clear(&aclconfctx); - if (parser != NULL) { + if (conf_parser != NULL) { if (config != NULL) - cfg_obj_destroy(parser, &config); - cfg_parser_destroy(&parser); + cfg_obj_destroy(conf_parser, &config); + cfg_parser_destroy(&conf_parser); + } + + if (bindkeys_parser != NULL) { + if (bindkeys != NULL) + cfg_obj_destroy(bindkeys_parser, &bindkeys); + cfg_parser_destroy(&bindkeys_parser); } if (view != NULL) @@ -3675,6 +4975,13 @@ load_configuration(const char *filename, ns_server_t *server, dns_view_detach(&view); } + /* Same cleanup for cache list. */ + while ((nsc = ISC_LIST_HEAD(cachelist)) != NULL) { + ISC_LIST_UNLINK(cachelist, nsc, link); + dns_cache_detach(&nsc->cache); + isc_mem_put(server->mctx, nsc, sizeof(*nsc)); + } + /* * Adjust the listening interfaces in accordance with the source * addresses specified in views and zones. @@ -3708,6 +5015,8 @@ load_zones(ns_server_t *server, isc_boolean_t stop) { view = ISC_LIST_NEXT(view, link)) { CHECK(dns_view_load(view, stop)); + if (view->managed_keys != NULL) + CHECK(dns_zone_load(view->managed_keys)); } /* @@ -3737,11 +5046,14 @@ load_new_zones(ns_server_t *server, isc_boolean_t stop) { view = ISC_LIST_NEXT(view, link)) { CHECK(dns_view_loadnew(view, stop)); + + /* Load managed-keys data */ + if (view->managed_keys != NULL) + CHECK(dns_zone_loadnew(view->managed_keys)); } + /* - * Force zone maintenance. Do this after loading - * so that we know when we need to force AXFR of - * slave zones whose master files are missing. + * Resume zone XFRs. */ dns_zonemgr_resumexfrs(server->zonemgr); cleanup: @@ -3820,6 +5132,7 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { dns_view_t *view, *view_next; ns_server_t *server = (ns_server_t *)event->ev_arg; isc_boolean_t flush = server->flushonshutdown; + ns_cache_t *nsc; UNUSED(task); INSIST(task == server->task); @@ -3834,6 +5147,7 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { ns_statschannels_shutdown(server); ns_controls_shutdown(server->controls); end_reserved_dispatches(server, ISC_TRUE); + cleanup_session_key(server, server->mctx); cfg_obj_destroy(ns_g_parser, &ns_g_config); cfg_parser_destroy(&ns_g_parser); @@ -3849,6 +5163,12 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { dns_view_detach(&view); } + while ((nsc = ISC_LIST_HEAD(server->cachelist)) != NULL) { + ISC_LIST_UNLINK(server->cachelist, nsc, link); + dns_cache_detach(&nsc->cache); + isc_mem_put(server->mctx, nsc, sizeof(*nsc)); + } + isc_timer_detach(&server->interface_timer); isc_timer_detach(&server->heartbeat_timer); isc_timer_detach(&server->pps_timer); @@ -3860,6 +5180,11 @@ shutdown_server(isc_task_t *task, isc_event_t *event) { dns_zonemgr_shutdown(server->zonemgr); + if (ns_g_sessionkey != NULL) { + dns_tsigkey_detach(&ns_g_sessionkey); + dns_name_free(&ns_g_sessionkeyname, server->mctx); + } + if (server->blackholeacl != NULL) dns_acl_detach(&server->blackholeacl); @@ -3918,7 +5243,8 @@ ns_server_create(isc_mem_t *mctx, ns_server_t **serverp) { ISC_R_NOMEMORY : ISC_R_SUCCESS, "allocating reload event"); - CHECKFATAL(dst_lib_init(ns_g_mctx, ns_g_entropy, ISC_ENTROPY_GOODONLY), + CHECKFATAL(dst_lib_init2(ns_g_mctx, ns_g_entropy, + ns_g_engine, ISC_ENTROPY_GOODONLY), "initializing DST"); server->tkeyctx = NULL; @@ -3963,10 +5289,20 @@ ns_server_create(isc_mem_t *mctx, ns_server_t **serverp) { "isc_stats_create"); isc_socketmgr_setstats(ns_g_socketmgr, server->sockstats); + server->bindkeysfile = isc_mem_strdup(server->mctx, "bind.keys"); + CHECKFATAL(server->bindkeysfile == NULL ? ISC_R_NOMEMORY : + ISC_R_SUCCESS, + "isc_mem_strdup"); + server->dumpfile = isc_mem_strdup(server->mctx, "named_dump.db"); CHECKFATAL(server->dumpfile == NULL ? ISC_R_NOMEMORY : ISC_R_SUCCESS, "isc_mem_strdup"); + server->secrootsfile = isc_mem_strdup(server->mctx, "named.secroots"); + CHECKFATAL(server->secrootsfile == NULL ? ISC_R_NOMEMORY : + ISC_R_SUCCESS, + "isc_mem_strdup"); + server->recfile = isc_mem_strdup(server->mctx, "named.recursing"); CHECKFATAL(server->recfile == NULL ? ISC_R_NOMEMORY : ISC_R_SUCCESS, "isc_mem_strdup"); @@ -4008,6 +5344,14 @@ ns_server_create(isc_mem_t *mctx, ns_server_t **serverp) { ISC_LIST_INIT(server->statschannels); + ISC_LIST_INIT(server->cachelist); + + server->sessionkey = NULL; + server->session_keyfile = NULL; + server->session_keyname = NULL; + server->session_keyalg = DST_ALG_UNKNOWN; + server->session_keybits = 0; + server->magic = NS_SERVER_MAGIC; *serverp = server; } @@ -4027,7 +5371,9 @@ ns_server_destroy(ns_server_t **serverp) { isc_stats_detach(&server->sockstats); isc_mem_free(server->mctx, server->statsfile); + isc_mem_free(server->mctx, server->bindkeysfile); isc_mem_free(server->mctx, server->dumpfile); + isc_mem_free(server->mctx, server->secrootsfile); isc_mem_free(server->mctx, server->recfile); if (server->version != NULL) @@ -4047,6 +5393,7 @@ ns_server_destroy(ns_server_t **serverp) { isc_event_free(&server->reload_event); INSIST(ISC_LIST_EMPTY(server->viewlist)); + INSIST(ISC_LIST_EMPTY(server->cachelist)); dns_aclenv_destroy(&server->aclenv); @@ -4278,7 +5625,9 @@ next_token(char **stringp, const char *delim) { * set '*zonep' to NULL. */ static isc_result_t -zone_from_args(ns_server_t *server, char *args, dns_zone_t **zonep) { +zone_from_args(ns_server_t *server, char *args, dns_zone_t **zonep, + const char **zonename) +{ char *input, *ptr; const char *zonetxt; char *classtxt; @@ -4302,6 +5651,8 @@ zone_from_args(ns_server_t *server, char *args, dns_zone_t **zonep) { zonetxt = next_token(&input, " \t"); if (zonetxt == NULL) return (ISC_R_SUCCESS); + if (zonename) + *zonename = zonetxt; /* Look for the optional class name. */ classtxt = next_token(&input, " \t"); @@ -4314,7 +5665,7 @@ zone_from_args(ns_server_t *server, char *args, dns_zone_t **zonep) { isc_buffer_add(&buf, strlen(zonetxt)); dns_fixedname_init(&name); result = dns_name_fromtext(dns_fixedname_name(&name), - &buf, dns_rootname, ISC_FALSE, NULL); + &buf, dns_rootname, 0, NULL); if (result != ISC_R_SUCCESS) goto fail1; @@ -4362,7 +5713,7 @@ ns_server_retransfercommand(ns_server_t *server, char *args) { dns_zone_t *zone = NULL; dns_zonetype_t type; - result = zone_from_args(server, args, &zone); + result = zone_from_args(server, args, &zone, NULL); if (result != ISC_R_SUCCESS) return (result); if (zone == NULL) @@ -4386,7 +5737,7 @@ ns_server_reloadcommand(ns_server_t *server, char *args, isc_buffer_t *text) { dns_zonetype_t type; const char *msg = NULL; - result = zone_from_args(server, args, &zone); + result = zone_from_args(server, args, &zone, NULL); if (result != ISC_R_SUCCESS) return (result); if (zone == NULL) { @@ -4446,7 +5797,7 @@ ns_server_notifycommand(ns_server_t *server, char *args, isc_buffer_t *text) { dns_zone_t *zone = NULL; const unsigned char msg[] = "zone notify queued"; - result = zone_from_args(server, args, &zone); + result = zone_from_args(server, args, &zone, NULL); if (result != ISC_R_SUCCESS) return (result); if (zone == NULL) @@ -4471,7 +5822,7 @@ ns_server_refreshcommand(ns_server_t *server, char *args, isc_buffer_t *text) { const unsigned char msg2[] = "not a slave or stub zone"; dns_zonetype_t type; - result = zone_from_args(server, args, &zone); + result = zone_from_args(server, args, &zone, NULL); if (result != ISC_R_SUCCESS) return (result); if (zone == NULL) @@ -4710,15 +6061,23 @@ dumpdone(void *arg, isc_result_t result) { nextview: fprintf(dctx->fp, ";\n; Start view %s\n;\n", dctx->view->view->name); resume: - if (dctx->zone == NULL && dctx->cache == NULL && dctx->dumpcache) { + if (dctx->dumpcache && dns_view_iscacheshared(dctx->view->view)) { + fprintf(dctx->fp, + ";\n; Cache of view '%s' is shared as '%s'\n", + dctx->view->view->name, + dns_cache_getname(dctx->view->view->cache)); + } else if (dctx->zone == NULL && dctx->cache == NULL && + dctx->dumpcache) + { style = &dns_master_style_cache; /* start cache dump */ if (dctx->view->view->cachedb != NULL) dns_db_attach(dctx->view->view->cachedb, &dctx->cache); if (dctx->cache != NULL) { - - fprintf(dctx->fp, ";\n; Cache dump of view '%s'\n;\n", - dctx->view->view->name); + fprintf(dctx->fp, + ";\n; Cache dump of view '%s' (cache %s)\n;\n", + dctx->view->view->name, + dns_cache_getname(dctx->view->view->cache)); result = dns_master_dumptostreaminc(dctx->mctx, dctx->cache, NULL, style, dctx->fp, @@ -4880,6 +6239,68 @@ ns_server_dumpdb(ns_server_t *server, char *args) { } isc_result_t +ns_server_dumpsecroots(ns_server_t *server, char *args) { + dns_view_t *view; + dns_keytable_t *secroots = NULL; + isc_result_t result; + char *ptr; + FILE *fp = NULL; + isc_time_t now; + char tbuf[64]; + + /* Skip the command name. */ + ptr = next_token(&args, " \t"); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + ptr = next_token(&args, " \t"); + + CHECKMF(isc_stdio_open(server->secrootsfile, "w", &fp), + "could not open secroots dump file", server->secrootsfile); + TIME_NOW(&now); + isc_time_formattimestamp(&now, tbuf, sizeof(tbuf)); + fprintf(fp, "%s\n", tbuf); + + nextview: + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (ptr != NULL && strcmp(view->name, ptr) != 0) + continue; + if (secroots != NULL) + dns_keytable_detach(&secroots); + result = dns_view_getsecroots(view, &secroots); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + continue; + } + fprintf(fp, "\n Start view %s\n\n", view->name); + CHECK(dns_keytable_dump(secroots, fp)); + } + if (ptr != NULL) { + ptr = next_token(&args, " \t"); + if (ptr != NULL) + goto nextview; + } + + cleanup: + if (secroots != NULL) + dns_keytable_detach(&secroots); + if (fp != NULL) + (void)isc_stdio_close(fp); + if (result == ISC_R_SUCCESS) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumpsecroots complete"); + else + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dumpsecroots failed: %s", + dns_result_totext(result)); + return (result); +} + +isc_result_t ns_server_dumprecursing(ns_server_t *server) { FILE *fp = NULL; isc_result_t result; @@ -4997,6 +6418,7 @@ ns_server_flushcache(ns_server_t *server, char *args) { isc_boolean_t flushed; isc_boolean_t found; isc_result_t result; + ns_cache_t *nsc; /* Skip the command name. */ ptr = next_token(&args, " \t"); @@ -5010,22 +6432,96 @@ ns_server_flushcache(ns_server_t *server, char *args) { RUNTIME_CHECK(result == ISC_R_SUCCESS); flushed = ISC_TRUE; found = ISC_FALSE; - for (view = ISC_LIST_HEAD(server->viewlist); - view != NULL; - view = ISC_LIST_NEXT(view, link)) - { - if (viewname != NULL && strcasecmp(viewname, view->name) != 0) - continue; + + /* + * Flushing a cache is tricky when caches are shared by multiple views. + * We first identify which caches should be flushed in the local cache + * list, flush these caches, and then update other views that refer to + * the flushed cache DB. + */ + if (viewname != NULL) { + /* + * Mark caches that need to be flushed. This is an O(#view^2) + * operation in the very worst case, but should be normally + * much more lightweight because only a few (most typically just + * one) views will match. + */ + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (strcasecmp(viewname, view->name) != 0) + continue; + found = ISC_TRUE; + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (nsc->cache == view->cache) + break; + } + INSIST(nsc != NULL); + nsc->needflush = ISC_TRUE; + } + } else found = ISC_TRUE; - result = dns_view_flushcache(view); + + /* Perform flush */ + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (viewname != NULL && !nsc->needflush) + continue; + nsc->needflush = ISC_TRUE; + result = dns_view_flushcache2(nsc->primaryview, ISC_FALSE); if (result != ISC_R_SUCCESS) { flushed = ISC_FALSE; isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, "flushing cache in view '%s' failed: %s", - view->name, isc_result_totext(result)); + nsc->primaryview->name, + isc_result_totext(result)); + } + } + + /* + * Fix up views that share a flushed cache: let the views update the + * cache DB they're referring to. This could also be an expensive + * operation, but should typically be marginal: the inner loop is only + * necessary for views that share a cache, and if there are many such + * views the number of shared cache should normally be small. + * A worst case is that we have n views and n/2 caches, each shared by + * two views. Then this will be a O(n^2/4) operation. + */ + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (!dns_view_iscacheshared(view)) + continue; + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (!nsc->needflush || nsc->cache != view->cache) + continue; + result = dns_view_flushcache2(view, ISC_TRUE); + if (result != ISC_R_SUCCESS) { + flushed = ISC_FALSE; + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "fixing cache in view '%s' " + "failed: %s", view->name, + isc_result_totext(result)); + } } } + + /* Cleanup the cache list. */ + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + nsc->needflush = ISC_FALSE; + } + if (flushed && found) { if (viewname != NULL) isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, @@ -5076,7 +6572,7 @@ ns_server_flushname(ns_server_t *server, char *args) { isc_buffer_add(&b, strlen(target)); dns_fixedname_init(&fixed); name = dns_fixedname_name(&fixed); - result = dns_name_fromtext(name, &b, dns_rootname, ISC_FALSE, NULL); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); if (result != ISC_R_SUCCESS) return (result); @@ -5094,6 +6590,11 @@ ns_server_flushname(ns_server_t *server, char *args) { if (viewname != NULL && strcasecmp(viewname, view->name) != 0) continue; found = ISC_TRUE; + /* + * It's a little inefficient to try flushing name for all views + * if some of the views share a single cache. But since the + * operation is lightweight we prefer simplicity here. + */ result = dns_view_flushname(view, name); if (result != ISC_R_SUCCESS) { flushed = ISC_FALSE; @@ -5408,6 +6909,46 @@ ns_server_tsiglist(ns_server_t *server, isc_buffer_t *text) { } /* + * Act on a "sign" or "loadkeys" command from the command channel. + */ +isc_result_t +ns_server_rekey(ns_server_t *server, char *args) { + isc_result_t result; + dns_zone_t *zone = NULL; + dns_zonetype_t type; + isc_uint16_t keyopts; + isc_boolean_t fullsign = ISC_FALSE; + + if (strncasecmp(args, NS_COMMAND_SIGN, strlen(NS_COMMAND_SIGN)) == 0) + fullsign = ISC_TRUE; + + result = zone_from_args(server, args, &zone, NULL); + if (result != ISC_R_SUCCESS) + return (result); + if (zone == NULL) + return (ISC_R_UNEXPECTEDEND); /* XXX: or do all zones? */ + + type = dns_zone_gettype(zone); + if (type != dns_zone_master) { + dns_zone_detach(&zone); + return (DNS_R_NOTMASTER); + } + + keyopts = dns_zone_getkeyopts(zone); + + /* "rndc loadkeys" requires "auto-dnssec maintain". */ + if ((keyopts & DNS_ZONEKEY_ALLOW) == 0) + result = ISC_R_NOPERM; + else if ((keyopts & DNS_ZONEKEY_MAINTAIN) == 0 && !fullsign) + result = ISC_R_NOPERM; + else + dns_zone_rekey(zone, fullsign); + + dns_zone_detach(&zone); + return (result); +} + +/* * Act on a "freeze" or "thaw" command from the command channel. */ isc_result_t @@ -5425,7 +6966,7 @@ ns_server_freeze(ns_server_t *server, isc_boolean_t freeze, char *args, isc_boolean_t frozen; const char *msg = NULL; - result = zone_from_args(server, args, &zone); + result = zone_from_args(server, args, &zone, NULL); if (result != ISC_R_SUCCESS) return (result); if (zone == NULL) { @@ -5451,7 +6992,7 @@ ns_server_freeze(ns_server_t *server, isc_boolean_t freeze, char *args, type = dns_zone_gettype(zone); if (type != dns_zone_master) { dns_zone_detach(&zone); - return (ISC_R_NOTFOUND); + return (DNS_R_NOTMASTER); } result = isc_task_beginexclusive(server->task); @@ -5502,8 +7043,8 @@ ns_server_freeze(ns_server_t *server, isc_boolean_t freeze, char *args, strlen(msg) + 1); view = dns_zone_getview(zone); - if (strcmp(view->name, "_bind") == 0 || - strcmp(view->name, "_default") == 0) + if (strcmp(view->name, "_default") == 0 || + strcmp(view->name, "_bind") == 0) { vname = ""; sep = ""; @@ -5543,3 +7084,376 @@ ns_smf_add_message(isc_buffer_t *text) { return (ISC_R_SUCCESS); } #endif /* HAVE_LIBSCF */ + +/* + * Act on an "addzone" command from the command channel. + */ +isc_result_t +ns_server_add_zone(ns_server_t *server, char *args) { + isc_result_t result; + isc_buffer_t argbuf; + size_t arglen; + cfg_parser_t *parser = NULL; + cfg_obj_t *config = NULL; + const cfg_obj_t *vconfig = NULL; + const cfg_obj_t *views = NULL; + const cfg_obj_t *parms = NULL; + const cfg_obj_t *obj = NULL; + const cfg_listelt_t *element; + const char *zonename; + const char *classname = NULL; + const char *argp; + const char *viewname = NULL; + dns_rdataclass_t rdclass; + dns_view_t *view = 0; + isc_buffer_t buf, *nbuf = NULL; + dns_name_t dnsname; + dns_zone_t *zone = NULL; + FILE *fp = NULL; + struct cfg_context *cfg = NULL; + + /* Try to parse the argument string */ + arglen = strlen(args); + isc_buffer_init(&argbuf, args, arglen); + isc_buffer_add(&argbuf, strlen(args)); + CHECK(cfg_parser_create(server->mctx, ns_g_lctx, &parser)); + CHECK(cfg_parse_buffer(parser, &argbuf, &cfg_type_addzoneconf, + &config)); + CHECK(cfg_map_get(config, "addzone", &parms)); + + zonename = cfg_obj_asstring(cfg_tuple_get(parms, "name")); + isc_buffer_init(&buf, zonename, strlen(zonename)); + isc_buffer_add(&buf, strlen(zonename)); + dns_name_init(&dnsname, NULL); + isc_buffer_allocate(server->mctx, &nbuf, 256); + dns_name_setbuffer(&dnsname, nbuf); + CHECK(dns_name_fromtext(&dnsname, &buf, dns_rootname, ISC_FALSE, NULL)); + + /* Make sense of optional class argument */ + obj = cfg_tuple_get(parms, "class"); + CHECK(ns_config_getclass(obj, dns_rdataclass_in, &rdclass)); + if (rdclass != dns_rdataclass_in && obj) + classname = cfg_obj_asstring(obj); + + /* Make sense of optional view argument */ + obj = cfg_tuple_get(parms, "view"); + if (obj && cfg_obj_isstring(obj)) + viewname = cfg_obj_asstring(obj); + if (viewname == NULL || *viewname == '\0') + viewname = "_default"; + CHECK(dns_viewlist_find(&server->viewlist, viewname, rdclass, &view)); + + /* Are we accepting new zones? */ + if (view->new_zone_file == NULL) { + result = ISC_R_NOPERM; + goto cleanup; + } + + cfg = (struct cfg_context *) view->new_zone_config; + if (cfg == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + /* Zone shouldn't already exist */ + result = dns_zt_find(view->zonetable, &dnsname, 0, NULL, &zone); + if (result == ISC_R_SUCCESS) { + result = ISC_R_EXISTS; + goto cleanup; + } else if (result == DNS_R_PARTIALMATCH) { + /* Create our sub-zone anyway */ + dns_zone_detach(&zone); + zone = NULL; + } + else if (result != ISC_R_NOTFOUND) + goto cleanup; + + /* Find the view statement */ + cfg_map_get(cfg->config, "view", &views); + for (element = cfg_list_first(views); + element != NULL; + element = cfg_list_next(element)) + { + const char *vname; + vconfig = cfg_listelt_value(element); + vname = cfg_obj_asstring(cfg_tuple_get(vconfig, "name")); + if (vname && !strcasecmp(vname, viewname)) + break; + vconfig = NULL; + } + + /* Open save file for write configuration */ + CHECK(isc_stdio_open(view->new_zone_file, "a", &fp)); + + /* Mark view unfrozen so that zone can be added */ + dns_view_thaw(view); + result = configure_zone(cfg->config, parms, vconfig, + server->mctx, view, &cfg->actx, ISC_FALSE); + dns_view_freeze(view); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* Is it there yet? */ + CHECK(dns_zt_find(view->zonetable, &dnsname, 0, NULL, &zone)); + + /* + * Load the zone from the master file. If this fails, we'll + * need to undo the configuration we've done already. + */ + result = dns_zone_loadnew(zone); + if (result != ISC_R_SUCCESS) { + dns_db_t *dbp = NULL; + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "addzone failed; reverting."); + + /* If the zone loaded partially, unload it */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + /* Remove the zone from the zone table */ + dns_zt_unmount(view->zonetable, zone); + goto cleanup; + } + + /* Flag the zone as having been added at runtime */ + dns_zone_setadded(zone, ISC_TRUE); + + /* Emit just the zone name from args */ + CHECK(isc_stdio_write("zone ", 5, 1, fp, NULL)); + CHECK(isc_stdio_write(zonename, strlen(zonename), 1, fp, NULL)); + CHECK(isc_stdio_write(" ", 1, 1, fp, NULL)); + + /* Classname, if not default */ + if (classname != NULL && *classname != '\0') { + CHECK(isc_stdio_write(classname, strlen(classname), 1, fp, + NULL)); + CHECK(isc_stdio_write(" ", 1, 1, fp, NULL)); + } + + /* Find beginning of option block from args */ + for (argp = args; *argp; argp++, arglen--) { + if (*argp == '{') { /* Assume matching '}' */ + /* Add that to our file */ + CHECK(isc_stdio_write(argp, arglen, 1, fp, NULL)); + + /* Make sure we end with a LF */ + if (argp[arglen-1] != '\n') { + CHECK(isc_stdio_write("\n", 1, 1, fp, NULL)); + } + break; + } + } + + CHECK(isc_stdio_close(fp)); + fp = NULL; + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "zone %s added to view %s via addzone", + zonename, viewname); + + result = ISC_R_SUCCESS; + + cleanup: + if (fp != NULL) + isc_stdio_close(fp); + if (parser != NULL) { + if (config != NULL) + cfg_obj_destroy(parser, &config); + cfg_parser_destroy(&parser); + } + if (zone != NULL) + dns_zone_detach(&zone); + if (view != NULL) + dns_view_detach(&view); + if (nbuf != NULL) + isc_buffer_free(&nbuf); + + return (result); +} + +/* + * Act on a "delzone" command from the command channel. + */ +isc_result_t +ns_server_del_zone(ns_server_t *server, char *args) { + isc_result_t result; + dns_zone_t *zone = NULL; + dns_view_t *view = NULL; + dns_db_t *dbp = NULL; + const char *filename = NULL; + char *tmpname = NULL; + char buf[1024]; + const char *zonename = NULL; + size_t znamelen = 0; + FILE *ifp = NULL, *ofp = NULL; + + /* Parse parameters */ + CHECK(zone_from_args(server, args, &zone, &zonename)); + if (result != ISC_R_SUCCESS) + return (result); + if (zone == NULL) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + /* + * Was this zone originally added at runtime? + * If not, we can't delete it now. + */ + if (!dns_zone_getadded(zone)) { + result = ISC_R_NOPERM; + goto cleanup; + } + + if (zonename != NULL) + znamelen = strlen(zonename); + + /* Dig out configuration for this zone */ + view = dns_zone_getview(zone); + filename = view->new_zone_file; + if (filename == NULL) { + /* No adding zones in this view */ + result = ISC_R_FAILURE; + goto cleanup; + } + + /* Rewrite zone list */ + result = isc_stdio_open(filename, "r", &ifp); + if (ifp != NULL && result == ISC_R_SUCCESS) { + char *found = NULL, *p = NULL; + size_t n; + + /* Create a temporary file */ + CHECK(isc_string_printf(buf, 1023, "%s.%ld", filename, + (long)getpid())); + if (!(tmpname = isc_mem_strdup(server->mctx, buf))) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + CHECK(isc_stdio_open(tmpname, "w", &ofp)); + + /* Look for the entry for that zone */ + while (fgets(buf, 1024, ifp)) { + /* A 'zone' line */ + if (strncasecmp(buf, "zone", 4)) { + fputs(buf, ofp); + continue; + } + p = buf+4; + + /* Locate a name */ + while (*p && + ((*p == '"') || isspace((unsigned char)*p))) + p++; + + /* Is that the zone we're looking for */ + if (strncasecmp(p, zonename, znamelen)) { + fputs(buf, ofp); + continue; + } + + /* And nothing else? */ + p += znamelen; + if (isspace((unsigned char)*p) || + *p == '"' || *p == '{') { + /* This must be the entry */ + found = p; + break; + } + + /* Spit it out, keep looking */ + fputs(buf, ofp); + } + + /* Skip over an option block (matching # of braces) */ + if (found) { + int obrace = 0, cbrace = 0; + for (;;) { + while (*p) { + if (*p == '{') obrace++; + if (*p == '}') cbrace++; + p++; + } + if (obrace && (obrace == cbrace)) + break; + if (!fgets(buf, 1024, ifp)) + break; + p = buf; + } + + /* Just spool the remainder of the file out */ + result = isc_stdio_read(buf, 1, 1024, ifp, &n); + while (n > 0U) { + if (result == ISC_R_EOF) + result = ISC_R_SUCCESS; + CHECK(result); + isc_stdio_write(buf, 1, n, ofp, NULL); + result = isc_stdio_read(buf, 1, 1024, ifp, &n); + } + + /* Move temporary into place */ + CHECK(isc_file_rename(tmpname, view->new_zone_file)); + } else { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "deleted zone %s was missing from " + "new zone file", zonename); + goto cleanup; + } + } + + /* Stop answering for this zone */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + CHECK(dns_zt_unmount(view->zonetable, zone)); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "zone %s removed via delzone", zonename); + + result = ISC_R_SUCCESS; + + cleanup: + if (ifp != NULL) + isc_stdio_close(ifp); + if (ofp != NULL) { + isc_stdio_close(ofp); + isc_file_remove(tmpname); + } + if (tmpname != NULL) + isc_mem_free(server->mctx, tmpname); + if (zone != NULL) + dns_zone_detach(&zone); + + return (result); +} + +static void +cfgctx_destroy(void **cfgp) { + struct cfg_context *cfg; + isc_mem_t *mctx; + + REQUIRE(cfgp != NULL && *cfgp != NULL); + cfg = *cfgp; + mctx = cfg->mctx; + cfg->mctx = NULL; + + if (cfg->parser != NULL) { + if (cfg->config != NULL) + cfg_obj_destroy(cfg->parser, &cfg->config); + cfg_parser_destroy(&cfg->parser); + } + cfg_aclconfctx_clear(&cfg->actx); + + isc_mem_put(mctx, cfg, sizeof(*cfg)); + isc_mem_detach(&mctx); + *cfgp = NULL; +} |