diff options
Diffstat (limited to '')
-rw-r--r-- | bin/named/server.c | 14277 |
1 files changed, 14277 insertions, 0 deletions
diff --git a/bin/named/server.c b/bin/named/server.c new file mode 100644 index 0000000..7f87ccf --- /dev/null +++ b/bin/named/server.c @@ -0,0 +1,14277 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <config.h> + +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <limits.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <isc/aes.h> +#include <isc/app.h> +#include <isc/base64.h> +#include <isc/commandline.h> +#include <isc/dir.h> +#include <isc/entropy.h> +#include <isc/file.h> +#include <isc/hash.h> +#include <isc/hex.h> +#include <isc/hmacsha.h> +#include <isc/httpd.h> +#include <isc/lex.h> +#include <isc/meminfo.h> +#include <isc/parseint.h> +#include <isc/portset.h> +#include <isc/print.h> +#include <isc/random.h> +#include <isc/refcount.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> +#include <isc/task.h> +#include <isc/timer.h> +#include <isc/util.h> +#include <isc/xml.h> + +#include <isccfg/grammar.h> +#include <isccfg/namedconf.h> + +#include <bind9/check.h> + +#include <dns/acache.h> +#include <dns/adb.h> +#include <dns/badcache.h> +#include <dns/cache.h> +#include <dns/catz.h> +#include <dns/db.h> +#include <dns/dispatch.h> +#include <dns/dlz.h> +#include <dns/dns64.h> +#include <dns/dyndb.h> +#include <dns/events.h> +#include <dns/forward.h> +#include <dns/fixedname.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> +#include <dns/nta.h> +#include <dns/order.h> +#include <dns/peer.h> +#include <dns/portlist.h> +#include <dns/private.h> +#include <dns/rbt.h> +#include <dns/rdataclass.h> +#include <dns/rdatalist.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/resolver.h> +#include <dns/rootns.h> +#include <dns/rriterator.h> +#include <dns/secalg.h> +#include <dns/soa.h> +#include <dns/stats.h> +#include <dns/tkey.h> +#include <dns/tsig.h> +#include <dns/ttl.h> +#include <dns/view.h> +#include <dns/zone.h> +#include <dns/zt.h> + +#include <dst/dst.h> +#include <dst/result.h> + +#include <named/client.h> +#include <named/config.h> +#include <named/control.h> +#ifdef HAVE_GEOIP +#include <named/geoip.h> +#endif /* HAVE_GEOIP */ +#include <named/interfacemgr.h> +#include <named/log.h> +#include <named/logconf.h> +#include <named/lwresd.h> +#include <named/main.h> +#include <named/os.h> +#include <named/server.h> +#include <named/statschannel.h> +#include <named/tkeyconf.h> +#include <named/tsigconf.h> +#include <named/zoneconf.h> +#ifdef HAVE_LIBSCF +#include <named/ns_smf_globals.h> +#include <stdlib.h> +#endif + +#ifdef HAVE_LMDB +#include <lmdb.h> +#define count_newzones count_newzones_db +#define configure_newzones configure_newzones_db +#define dumpzone dumpzone_db +#else /* HAVE_LMDB */ +#define count_newzones count_newzones_file +#define configure_newzones configure_newzones_file +#define dumpzone dumpzone_file +#endif /* HAVE_LMDB */ + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t)-1) +#endif + +#ifndef SIZE_AS_PERCENT +#define SIZE_AS_PERCENT ((size_t)-2) +#endif + +#ifdef TUNE_LARGE +#define RESOLVER_NTASKS 523 +#define UDPBUFFERS 32768 +#define EXCLBUFFERS 32768 +#else +#define RESOLVER_NTASKS 31 +#define UDPBUFFERS 1000 +#define EXCLBUFFERS 4096 +#endif /* TUNE_LARGE */ + +/*% + * Check an operation for failure. Assumes that the function + * using it has a 'result' variable and a 'cleanup' label. + */ +#define CHECK(op) \ + do { result = (op); \ + if (result != ISC_R_SUCCESS) goto cleanup; \ + } while (0) + +#define TCHECK(op) \ + do { tresult = (op); \ + if (tresult != ISC_R_SUCCESS) { \ + isc_buffer_clear(*text); \ + goto cleanup; \ + } \ + } while (0) + +#define CHECKM(op, msg) \ + do { result = (op); \ + if (result != ISC_R_SUCCESS) { \ + isc_log_write(ns_g_lctx, \ + NS_LOGCATEGORY_GENERAL, \ + NS_LOGMODULE_SERVER, \ + ISC_LOG_ERROR, \ + "%s: %s", msg, \ + isc_result_totext(result)); \ + goto cleanup; \ + } \ + } while (0) \ + +#define CHECKMF(op, msg, file) \ + do { result = (op); \ + if (result != ISC_R_SUCCESS) { \ + isc_log_write(ns_g_lctx, \ + NS_LOGCATEGORY_GENERAL, \ + NS_LOGMODULE_SERVER, \ + ISC_LOG_ERROR, \ + "%s '%s': %s", msg, file, \ + isc_result_totext(result)); \ + goto cleanup; \ + } \ + } while (0) \ + +#define CHECKFATAL(op, msg) \ + do { result = (op); \ + if (result != ISC_R_SUCCESS) \ + 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 8388608U + +struct ns_dispatch { + isc_sockaddr_t addr; + unsigned int dispatchgen; + dns_dispatch_t *dispatch; + ISC_LINK(struct ns_dispatch) link; +}; + +struct ns_cache { + dns_cache_t *cache; + dns_view_t *primaryview; + bool needflush; + bool adbsizeadjusted; + dns_rdataclass_t rdclass; + ISC_LINK(ns_cache_t) link; +}; + +struct dumpcontext { + isc_mem_t *mctx; + bool dumpcache; + bool dumpzones; + bool dumpadb; + bool dumpbad; + bool dumpfail; + FILE *fp; + ISC_LIST(struct viewlistentry) viewlist; + struct viewlistentry *view; + struct zonelistentry *zone; + dns_dumpctx_t *mdctx; + dns_db_t *db; + dns_db_t *cache; + isc_task_t *task; + dns_dbversion_t *version; +}; + +struct viewlistentry { + dns_view_t *view; + ISC_LINK(struct viewlistentry) link; + ISC_LIST(struct zonelistentry) zonelist; +}; + +struct zonelistentry { + dns_zone_t *zone; + ISC_LINK(struct zonelistentry) link; +}; + +/*% + * Configuration context to retain for each view that allows + * new zones to be added at runtime. + */ +typedef struct ns_cfgctx { + isc_mem_t * mctx; + cfg_parser_t * conf_parser; + cfg_parser_t * add_parser; + cfg_obj_t * config; + cfg_obj_t * vconfig; + cfg_obj_t * nzf_config; + cfg_aclconfctx_t * actx; +} ns_cfgctx_t; + +/*% + * A function to write out added-zone configuration to the new_zone_file + * specified in 'view'. Maybe called by delete_zoneconf(). + */ +typedef isc_result_t (*nzfwriter_t)(const cfg_obj_t *config, dns_view_t *view); + +/*% + * Holds state information for the initial zone loading process. + * Uses the isc_refcount structure to count the number of views + * with pending zone loads, dereferencing as each view finishes. + */ +typedef struct { + ns_server_t *server; + bool reconfig; + isc_refcount_t refs; +} ns_zoneload_t; + +typedef struct { + ns_server_t *server; +} catz_cb_data_t; + +typedef struct catz_chgzone_event { + ISC_EVENT_COMMON(struct catz_chgzone_event); + dns_catz_entry_t *entry; + dns_catz_zone_t *origin; + dns_view_t *view; + catz_cb_data_t *cbd; + bool mod; +} catz_chgzone_event_t; + +/* + * These zones should not leak onto the Internet. + */ +const char *empty_zones[] = { + /* RFC 1918 */ + "10.IN-ADDR.ARPA", + "16.172.IN-ADDR.ARPA", + "17.172.IN-ADDR.ARPA", + "18.172.IN-ADDR.ARPA", + "19.172.IN-ADDR.ARPA", + "20.172.IN-ADDR.ARPA", + "21.172.IN-ADDR.ARPA", + "22.172.IN-ADDR.ARPA", + "23.172.IN-ADDR.ARPA", + "24.172.IN-ADDR.ARPA", + "25.172.IN-ADDR.ARPA", + "26.172.IN-ADDR.ARPA", + "27.172.IN-ADDR.ARPA", + "28.172.IN-ADDR.ARPA", + "29.172.IN-ADDR.ARPA", + "30.172.IN-ADDR.ARPA", + "31.172.IN-ADDR.ARPA", + "168.192.IN-ADDR.ARPA", + + /* RFC 6598 */ + "64.100.IN-ADDR.ARPA", + "65.100.IN-ADDR.ARPA", + "66.100.IN-ADDR.ARPA", + "67.100.IN-ADDR.ARPA", + "68.100.IN-ADDR.ARPA", + "69.100.IN-ADDR.ARPA", + "70.100.IN-ADDR.ARPA", + "71.100.IN-ADDR.ARPA", + "72.100.IN-ADDR.ARPA", + "73.100.IN-ADDR.ARPA", + "74.100.IN-ADDR.ARPA", + "75.100.IN-ADDR.ARPA", + "76.100.IN-ADDR.ARPA", + "77.100.IN-ADDR.ARPA", + "78.100.IN-ADDR.ARPA", + "79.100.IN-ADDR.ARPA", + "80.100.IN-ADDR.ARPA", + "81.100.IN-ADDR.ARPA", + "82.100.IN-ADDR.ARPA", + "83.100.IN-ADDR.ARPA", + "84.100.IN-ADDR.ARPA", + "85.100.IN-ADDR.ARPA", + "86.100.IN-ADDR.ARPA", + "87.100.IN-ADDR.ARPA", + "88.100.IN-ADDR.ARPA", + "89.100.IN-ADDR.ARPA", + "90.100.IN-ADDR.ARPA", + "91.100.IN-ADDR.ARPA", + "92.100.IN-ADDR.ARPA", + "93.100.IN-ADDR.ARPA", + "94.100.IN-ADDR.ARPA", + "95.100.IN-ADDR.ARPA", + "96.100.IN-ADDR.ARPA", + "97.100.IN-ADDR.ARPA", + "98.100.IN-ADDR.ARPA", + "99.100.IN-ADDR.ARPA", + "100.100.IN-ADDR.ARPA", + "101.100.IN-ADDR.ARPA", + "102.100.IN-ADDR.ARPA", + "103.100.IN-ADDR.ARPA", + "104.100.IN-ADDR.ARPA", + "105.100.IN-ADDR.ARPA", + "106.100.IN-ADDR.ARPA", + "107.100.IN-ADDR.ARPA", + "108.100.IN-ADDR.ARPA", + "109.100.IN-ADDR.ARPA", + "110.100.IN-ADDR.ARPA", + "111.100.IN-ADDR.ARPA", + "112.100.IN-ADDR.ARPA", + "113.100.IN-ADDR.ARPA", + "114.100.IN-ADDR.ARPA", + "115.100.IN-ADDR.ARPA", + "116.100.IN-ADDR.ARPA", + "117.100.IN-ADDR.ARPA", + "118.100.IN-ADDR.ARPA", + "119.100.IN-ADDR.ARPA", + "120.100.IN-ADDR.ARPA", + "121.100.IN-ADDR.ARPA", + "122.100.IN-ADDR.ARPA", + "123.100.IN-ADDR.ARPA", + "124.100.IN-ADDR.ARPA", + "125.100.IN-ADDR.ARPA", + "126.100.IN-ADDR.ARPA", + "127.100.IN-ADDR.ARPA", + + /* RFC 5735 and RFC 5737 */ + "0.IN-ADDR.ARPA", /* THIS NETWORK */ + "127.IN-ADDR.ARPA", /* LOOPBACK */ + "254.169.IN-ADDR.ARPA", /* LINK LOCAL */ + "2.0.192.IN-ADDR.ARPA", /* TEST NET */ + "100.51.198.IN-ADDR.ARPA", /* TEST NET 2 */ + "113.0.203.IN-ADDR.ARPA", /* TEST NET 3 */ + "255.255.255.255.IN-ADDR.ARPA", /* BROADCAST */ + + /* Local IPv6 Unicast Addresses */ + "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA", + "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA", + /* LOCALLY ASSIGNED LOCAL ADDRESS SCOPE */ + "D.F.IP6.ARPA", + "8.E.F.IP6.ARPA", /* LINK LOCAL */ + "9.E.F.IP6.ARPA", /* LINK LOCAL */ + "A.E.F.IP6.ARPA", /* LINK LOCAL */ + "B.E.F.IP6.ARPA", /* LINK LOCAL */ + + /* Example Prefix, RFC 3849. */ + "8.B.D.0.1.0.0.2.IP6.ARPA", + + /* RFC 7534 */ + "EMPTY.AS112.ARPA", + + /* RFC 8375 */ + "HOME.ARPA", + + NULL +}; + +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); + +static isc_result_t +ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, + uint16_t family, ns_listenelt_t **target); +static isc_result_t +ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, + uint16_t family, ns_listenlist_t **target); + +static isc_result_t +configure_forward(const cfg_obj_t *config, dns_view_t *view, dns_name_t *origin, + const cfg_obj_t *forwarders, const cfg_obj_t *forwardtype); + +static isc_result_t +configure_alternates(const cfg_obj_t *config, dns_view_t *view, + const cfg_obj_t *alternates); + +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, + dns_viewlist_t *viewlist, cfg_aclconfctx_t *aclconf, + bool added, bool old_rpz_ok, + bool modify); + +static isc_result_t +configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + isc_mem_t *mctx, cfg_aclconfctx_t *actx); + +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, bool all); + +static void +newzone_cfgctx_destroy(void **cfgp); + +static inline isc_result_t +putstr(isc_buffer_t **b, const char *str); + +static isc_result_t +putmem(isc_buffer_t **b, const char *str, size_t len); + +static isc_result_t +putuint8(isc_buffer_t **b, uint8_t val); + +static inline isc_result_t +putnull(isc_buffer_t **b); + +static int +count_zones(const cfg_obj_t *conf); + +#ifdef HAVE_LMDB +static isc_result_t +migrate_nzf(dns_view_t *view); + +static isc_result_t +nzd_writable(dns_view_t *view); + +static isc_result_t +nzd_open(dns_view_t *view, unsigned int flags, MDB_txn **txnp, MDB_dbi *dbi); + +static isc_result_t +nzd_env_reopen(dns_view_t *view); + +static void +nzd_env_close(dns_view_t *view); + +static isc_result_t +nzd_close(MDB_txn **txnp, bool commit); + +static isc_result_t +nzd_count(dns_view_t *view, int *countp); +#else +static isc_result_t +nzf_append(dns_view_t *view, const cfg_obj_t *zconfig); +#endif + +/*% + * 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 cfg_obj_t *gconfig, 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[4]; + const cfg_obj_t *aclobj = NULL; + int i = 0; + + if (*aclp != NULL) { + dns_acl_detach(aclp); + } + 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; + } + } + if (gconfig != NULL) { + const cfg_obj_t *options = NULL; + (void)cfg_map_get(gconfig, "options", &options); + if (options != NULL) { + maps[i++] = options; + } + } + maps[i] = NULL; + + (void)ns_config_get(maps, aclname, &aclobj); + if (aclobj == NULL) { + /* + * No value available. *aclp == NULL. + */ + 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 + * nest_level value of 2. + */ +static isc_result_t +configure_view_sortlist(const cfg_obj_t *vconfig, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, + dns_acl_t **aclp) +{ + isc_result_t result; + const cfg_obj_t *maps[3]; + const cfg_obj_t *aclobj = NULL; + int i = 0; + + if (*aclp != NULL) + dns_acl_detach(aclp); + 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, "sortlist", &aclobj); + if (aclobj == NULL) + return (ISC_R_SUCCESS); + + /* + * Use a nest level of 3 for the "top level" of the sortlist; + * this means each entry in the top three levels will be stored + * as lists of separate, nested ACLs, rather than merged together + * into IP tables as is usually done with ACLs. + */ + result = cfg_acl_fromconfig(aclobj, config, ns_g_lctx, + actx, mctx, 3, aclp); + + return (result); +} + +static isc_result_t +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); + + name = dns_fixedname_initname(&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_constinit(&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, + bool managed, dst_key_t **target, isc_mem_t *mctx) +{ + dns_rdataclass_t viewclass; + dns_rdata_dnskey_t keystruct; + uint32_t flags, proto, alg; + const char *keystr, *keynamestr; + unsigned char keydata[4096]; + isc_buffer_t keydatabuf; + unsigned char rrdata[4096]; + isc_buffer_t rrdatabuf; + isc_region_t r; + dns_fixedname_t fkeyname; + dns_name_t *keyname; + isc_buffer_t namebuf; + 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 { + const cfg_obj_t *classobj = cfg_tuple_get(vconfig, "class"); + CHECK(ns_config_getclass(classobj, dns_rdataclass_in, + &viewclass)); + } + keystruct.common.rdclass = viewclass; + keystruct.common.rdtype = dns_rdatatype_dnskey; + /* + * The key data in keystruct is not dynamically allocated. + */ + keystruct.mctx = NULL; + + ISC_LINK_INIT(&keystruct.common, link); + + if (flags > 0xffff) + CHECKM(ISC_R_RANGE, "key flags"); + if (proto > 0xff) + CHECKM(ISC_R_RANGE, "key protocol"); + if (alg > 0xff) + CHECKM(ISC_R_RANGE, "key algorithm"); + keystruct.flags = (uint16_t)flags; + keystruct.protocol = (uint8_t)proto; + keystruct.algorithm = (uint8_t)alg; + + isc_buffer_init(&keydatabuf, keydata, sizeof(keydata)); + isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); + + keystr = cfg_obj_asstring(cfg_tuple_get(key, "key")); + CHECK(isc_base64_decodestring(keystr, &keydatabuf)); + isc_buffer_usedregion(&keydatabuf, &r); + keystruct.datalen = r.length; + keystruct.data = r.base; + + if ((keystruct.algorithm == DST_ALG_RSASHA1 || + 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, + "%s key '%s' has a weak exponent", + managed ? "managed" : "trusted", + keynamestr); + + CHECK(dns_rdata_fromstruct(NULL, + keystruct.common.rdclass, + keystruct.common.rdtype, + &keystruct, &rrdatabuf)); + dns_fixedname_init(&fkeyname); + isc_buffer_constinit(&namebuf, keynamestr, strlen(keynamestr)); + isc_buffer_add(&namebuf, strlen(keynamestr)); + CHECK(dns_name_fromtext(keyname, &namebuf, dns_rootname, 0, NULL)); + CHECK(dst_key_fromdns(keyname, viewclass, &rrdatabuf, + mctx, &dstkey)); + + *target = dstkey; + return (ISC_R_SUCCESS); + + cleanup: + if (result == DST_R_NOCRYPTO) { + cfg_obj_log(key, ns_g_lctx, ISC_LOG_ERROR, + "ignoring %s key for '%s': no crypto support", + managed ? "managed" : "trusted", + keynamestr); + } 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 %s key for '%s': %s", + managed ? "managed" : "trusted", + keynamestr, isc_result_totext(result)); + result = ISC_R_FAILURE; + } + + if (dstkey != NULL) + dst_key_free(&dstkey); + + return (result); +} + +static isc_result_t +load_view_keys(const cfg_obj_t *keys, const cfg_obj_t *vconfig, + dns_view_t *view, bool 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); +} + +/*% + * Check whether a key has been successfully loaded. + */ +static bool +keyloaded(dns_view_t *view, dns_name_t *name) { + isc_result_t result; + dns_keytable_t *secroots = NULL; + dns_keynode_t *keynode = NULL; + + result = dns_view_getsecroots(view, &secroots); + if (result != ISC_R_SUCCESS) + return (false); + + result = dns_keytable_find(secroots, name, &keynode); + + if (keynode != NULL) + dns_keytable_detachkeynode(secroots, &keynode); + if (secroots != NULL) + dns_keytable_detach(&secroots); + + return (result == ISC_R_SUCCESS); +} + +/*% + * Configure DNSSEC keys for a view. + * + * The per-view configuration values and the server-global defaults are read + * from 'vconfig' and 'config'. + */ +static isc_result_t +configure_view_dnsseckeys(dns_view_t *view, const cfg_obj_t *vconfig, + const cfg_obj_t *config, const cfg_obj_t *bindkeys, + bool auto_root, isc_mem_t *mctx) +{ + 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_obj_t *options = NULL; + const cfg_obj_t *obj = NULL; + const char *directory; + bool need_mkey_dir = false; + int i = 0; + + /* 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) { + 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; + } + } + + 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; + } + } + + 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); + } + + result = dns_view_initntatable(view, ns_g_taskmgr, ns_g_timermgr); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "couldn't create NTA table"); + return (ISC_R_UNEXPECTED); + } + + if (auto_root && view->rdclass == dns_rdataclass_in) { + const cfg_obj_t *builtin_keys = NULL; + const cfg_obj_t *builtin_managed_keys = NULL; + + /* + * If bind.keys exists and is populated, it overrides + * the managed-keys clause hard-coded in ns_g_config. + */ + if (bindkeys != NULL) { + isc_log_write(ns_g_lctx, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "obtaining root key for view %s " + "from '%s'", + view->name, ns_g_server->bindkeysfile); + + (void)cfg_map_get(bindkeys, "trusted-keys", + &builtin_keys); + (void)cfg_map_get(bindkeys, "managed-keys", + &builtin_managed_keys); + + if ((builtin_keys == NULL) && + (builtin_managed_keys == NULL)) + isc_log_write(ns_g_lctx, + DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, + "dnssec-validation auto: " + "WARNING: root zone key " + "not found"); + } + + if ((builtin_keys == NULL) && + (builtin_managed_keys == NULL)) + { + isc_log_write(ns_g_lctx, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "using built-in root key for view %s", + view->name); + + (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, + false, dns_rootname, mctx)); + if (builtin_managed_keys != NULL) + CHECK(load_view_keys(builtin_managed_keys, vconfig, + view, true, dns_rootname, + mctx)); + + if (!keyloaded(view, dns_rootname)) { + isc_log_write(ns_g_lctx, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "root key not loaded"); + result = ISC_R_FAILURE; + goto cleanup; + } + } + + CHECK(load_view_keys(view_keys, vconfig, view, false, + NULL, mctx)); + CHECK(load_view_keys(view_managed_keys, vconfig, view, true, + NULL, mctx)); + + if (view->rdclass == dns_rdataclass_in) { + CHECK(load_view_keys(global_keys, vconfig, view, false, + NULL, mctx)); + CHECK(load_view_keys(global_managed_keys, vconfig, view, + true, NULL, mctx)); + } + + /* + * Add key zone for managed-keys. + */ + need_mkey_dir = (auto_root || view_managed_keys != NULL); + + obj = NULL; + (void)ns_config_get(maps, "managed-keys-directory", &obj); + directory = (obj != NULL ? cfg_obj_asstring(obj) : NULL); + if (directory != NULL) { + result = isc_file_isdirectory(directory); + } + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "invalid managed-keys-directory %s: %s", + directory, isc_result_totext(result)); + goto cleanup; + + } else if (need_mkey_dir && directory != NULL) { + if (!isc_file_isdirwritable(directory)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "managed-keys-directory '%s' " + "is not writable", directory); + result = ISC_R_NOPERM; + goto cleanup; + } + } else if (need_mkey_dir) { + char cwd[PATH_MAX]; + + if (getcwd(cwd, sizeof(cwd)) == NULL) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "unable to retrieve " + "current working directory"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (!isc_file_isdirwritable(cwd)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "working directory '%s' " + "is not writable", cwd); + result = ISC_R_NOPERM; + goto cleanup; + } + } + + 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) { + const cfg_listelt_t *element; + const cfg_obj_t *obj; + const char *str; + dns_fixedname_t fixed; + dns_name_t *name; + bool value; + isc_result_t result; + isc_buffer_t b; + + name = dns_fixedname_initname(&fixed); + for (element = cfg_list_first(mbs); + element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(cfg_tuple_get(obj, "name")); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + 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)); + } + + result = ISC_R_SUCCESS; + + cleanup: + return (result); +} + +/*% + * Get a dispatch appropriate for the resolver of a given view. + */ +static isc_result_t +get_view_querysource_dispatch(const cfg_obj_t **maps, int af, + dns_dispatch_t **dispatchp, isc_dscp_t *dscpp, + bool is_firstview) +{ + isc_result_t result = ISC_R_FAILURE; + dns_dispatch_t *disp; + isc_sockaddr_t sa; + unsigned int attrs, attrmask; + const cfg_obj_t *obj = NULL; + unsigned int maxdispatchbuffers = UDPBUFFERS; + isc_dscp_t dscp = -1; + + switch (af) { + case AF_INET: + result = ns_config_get(maps, "query-source", &obj); + INSIST(result == ISC_R_SUCCESS); + break; + case AF_INET6: + result = ns_config_get(maps, "query-source-v6", &obj); + INSIST(result == ISC_R_SUCCESS); + break; + default: + INSIST(0); + } + + sa = *(cfg_obj_assockaddr(obj)); + INSIST(isc_sockaddr_pf(&sa) == af); + + dscp = cfg_obj_getdscp(obj); + if (dscp != -1 && dscpp != NULL) + *dscpp = dscp; + + /* + * If we don't support this address family, we're done! + */ + switch (af) { + case AF_INET: + result = isc_net_probeipv4(); + break; + case AF_INET6: + result = isc_net_probeipv6(); + break; + default: + INSIST(0); + } + if (result != ISC_R_SUCCESS) + return (ISC_R_SUCCESS); + + /* + * Try to find a dispatcher that we can share. + */ + attrs = 0; + attrs |= DNS_DISPATCHATTR_UDP; + switch (af) { + case AF_INET: + attrs |= DNS_DISPATCHATTR_IPV4; + break; + case AF_INET6: + attrs |= DNS_DISPATCHATTR_IPV6; + break; + } + if (isc_sockaddr_getport(&sa) == 0) { + attrs |= DNS_DISPATCHATTR_EXCLUSIVE; + maxdispatchbuffers = EXCLBUFFERS; + } else { + INSIST(obj != NULL); + if (is_firstview) { + cfg_obj_log(obj, ns_g_lctx, ISC_LOG_INFO, + "using specific query-source port " + "suppresses port randomization and can be " + "insecure."); + } + } + + attrmask = 0; + attrmask |= DNS_DISPATCHATTR_UDP; + attrmask |= DNS_DISPATCHATTR_TCP; + attrmask |= DNS_DISPATCHATTR_IPV4; + attrmask |= DNS_DISPATCHATTR_IPV6; + + disp = NULL; + result = dns_dispatch_getudp(ns_g_dispatchmgr, ns_g_socketmgr, + ns_g_taskmgr, &sa, 4096, + maxdispatchbuffers, 32768, 16411, 16433, + attrs, attrmask, &disp); + if (result != ISC_R_SUCCESS) { + isc_sockaddr_t any; + char buf[ISC_SOCKADDR_FORMATSIZE]; + + switch (af) { + case AF_INET: + isc_sockaddr_any(&any); + break; + case AF_INET6: + isc_sockaddr_any6(&any); + break; + } + if (isc_sockaddr_equal(&sa, &any)) + return (ISC_R_SUCCESS); + isc_sockaddr_format(&sa, buf, sizeof(buf)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "could not get query source dispatcher (%s)", + buf); + return (result); + } + + *dispatchp = disp; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +configure_order(dns_order_t *order, const cfg_obj_t *ent) { + dns_rdataclass_t rdclass; + dns_rdatatype_t rdtype; + const cfg_obj_t *obj; + dns_fixedname_t fixed; + unsigned int mode = 0; + const char *str; + isc_buffer_t b; + isc_result_t result; + bool addroot; + + result = ns_config_getclass(cfg_tuple_get(ent, "class"), + dns_rdataclass_any, &rdclass); + if (result != ISC_R_SUCCESS) + return (result); + + result = ns_config_gettype(cfg_tuple_get(ent, "type"), + dns_rdatatype_any, &rdtype); + if (result != ISC_R_SUCCESS) + return (result); + + obj = cfg_tuple_get(ent, "name"); + if (cfg_obj_isstring(obj)) + str = cfg_obj_asstring(obj); + else + str = "*"; + addroot = (strcmp(str, "*") == 0); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + dns_fixedname_init(&fixed); + result = dns_name_fromtext(dns_fixedname_name(&fixed), &b, + dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) + return (result); + + obj = cfg_tuple_get(ent, "ordering"); + INSIST(cfg_obj_isstring(obj)); + str = cfg_obj_asstring(obj); + if (!strcasecmp(str, "fixed")) +#if DNS_RDATASET_FIXED + mode = DNS_RDATASETATTR_FIXEDORDER; +#else + mode = 0; +#endif /* DNS_RDATASET_FIXED */ + else if (!strcasecmp(str, "random")) + mode = DNS_RDATASETATTR_RANDOMIZE; + else if (!strcasecmp(str, "cyclic")) + mode = 0; + else + INSIST(0); + + /* + * "*" should match everything including the root (BIND 8 compat). + * As dns_name_matcheswildcard(".", "*.") returns FALSE add a + * explicit entry for "." when the name is "*". + */ + if (addroot) { + result = dns_order_add(order, dns_rootname, + rdtype, rdclass, mode); + if (result != ISC_R_SUCCESS) + return (result); + } + + return (dns_order_add(order, dns_fixedname_name(&fixed), + rdtype, rdclass, mode)); +} + +static isc_result_t +configure_peer(const cfg_obj_t *cpeer, isc_mem_t *mctx, dns_peer_t **peerp) { + isc_netaddr_t na; + dns_peer_t *peer; + const cfg_obj_t *obj; + const char *str; + isc_result_t result; + unsigned int prefixlen; + + cfg_obj_asnetprefix(cfg_map_getname(cpeer), &na, &prefixlen); + + peer = NULL; + result = dns_peer_newprefix(mctx, &na, prefixlen, &peer); + if (result != ISC_R_SUCCESS) + return (result); + + obj = NULL; + (void)cfg_map_get(cpeer, "bogus", &obj); + if (obj != NULL) + CHECK(dns_peer_setbogus(peer, cfg_obj_asboolean(obj))); + + obj = NULL; + (void)cfg_map_get(cpeer, "provide-ixfr", &obj); + if (obj != NULL) + CHECK(dns_peer_setprovideixfr(peer, cfg_obj_asboolean(obj))); + + obj = NULL; + (void)cfg_map_get(cpeer, "request-expire", &obj); + if (obj != NULL) + CHECK(dns_peer_setrequestexpire(peer, cfg_obj_asboolean(obj))); + + obj = NULL; + (void)cfg_map_get(cpeer, "request-ixfr", &obj); + if (obj != NULL) + CHECK(dns_peer_setrequestixfr(peer, cfg_obj_asboolean(obj))); + + obj = NULL; + (void)cfg_map_get(cpeer, "request-nsid", &obj); + if (obj != NULL) + CHECK(dns_peer_setrequestnsid(peer, cfg_obj_asboolean(obj))); + + obj = NULL; + (void)cfg_map_get(cpeer, "send-cookie", &obj); + if (obj != NULL) + CHECK(dns_peer_setsendcookie(peer, cfg_obj_asboolean(obj))); + + obj = NULL; + (void)cfg_map_get(cpeer, "edns", &obj); + if (obj != NULL) + CHECK(dns_peer_setsupportedns(peer, cfg_obj_asboolean(obj))); + + obj = NULL; + (void)cfg_map_get(cpeer, "edns-udp-size", &obj); + if (obj != NULL) { + uint32_t udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512) + udpsize = 512; + if (udpsize > 4096) + udpsize = 4096; + CHECK(dns_peer_setudpsize(peer, (uint16_t)udpsize)); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "edns-version", &obj); + if (obj != NULL) { + uint32_t ednsversion = cfg_obj_asuint32(obj); + if (ednsversion > 255) + ednsversion = 255; + CHECK(dns_peer_setednsversion(peer, (uint8_t)ednsversion)); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "max-udp-size", &obj); + if (obj != NULL) { + uint32_t udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512) + udpsize = 512; + if (udpsize > 4096) + udpsize = 4096; + CHECK(dns_peer_setmaxudp(peer, (uint16_t)udpsize)); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "tcp-only", &obj); + if (obj != NULL) + CHECK(dns_peer_setforcetcp(peer, cfg_obj_asboolean(obj))); + + obj = NULL; + (void)cfg_map_get(cpeer, "transfers", &obj); + if (obj != NULL) + CHECK(dns_peer_settransfers(peer, cfg_obj_asuint32(obj))); + + obj = NULL; + (void)cfg_map_get(cpeer, "transfer-format", &obj); + if (obj != NULL) { + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "many-answers") == 0) + CHECK(dns_peer_settransferformat(peer, + dns_many_answers)); + else if (strcasecmp(str, "one-answer") == 0) + CHECK(dns_peer_settransferformat(peer, + dns_one_answer)); + else + INSIST(0); + } + + obj = NULL; + (void)cfg_map_get(cpeer, "keys", &obj); + if (obj != NULL) { + result = dns_peer_setkeybycharp(peer, cfg_obj_asstring(obj)); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + + obj = NULL; + if (na.family == AF_INET) + (void)cfg_map_get(cpeer, "transfer-source", &obj); + else + (void)cfg_map_get(cpeer, "transfer-source-v6", &obj); + if (obj != NULL) { + result = dns_peer_settransfersource(peer, + cfg_obj_assockaddr(obj)); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_peer_settransferdscp(peer, cfg_obj_getdscp(obj)); + if (result != ISC_R_SUCCESS) + goto cleanup; + ns_add_reserved_dispatch(ns_g_server, cfg_obj_assockaddr(obj)); + } + + obj = NULL; + if (na.family == AF_INET) + (void)cfg_map_get(cpeer, "notify-source", &obj); + else + (void)cfg_map_get(cpeer, "notify-source-v6", &obj); + if (obj != NULL) { + result = dns_peer_setnotifysource(peer, + cfg_obj_assockaddr(obj)); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_peer_setnotifydscp(peer, cfg_obj_getdscp(obj)); + if (result != ISC_R_SUCCESS) + goto cleanup; + ns_add_reserved_dispatch(ns_g_server, cfg_obj_assockaddr(obj)); + } + + obj = NULL; + if (na.family == AF_INET) + (void)cfg_map_get(cpeer, "query-source", &obj); + else + (void)cfg_map_get(cpeer, "query-source-v6", &obj); + if (obj != NULL) { + result = dns_peer_setquerysource(peer, + cfg_obj_assockaddr(obj)); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_peer_setquerydscp(peer, cfg_obj_getdscp(obj)); + if (result != ISC_R_SUCCESS) + goto cleanup; + ns_add_reserved_dispatch(ns_g_server, cfg_obj_assockaddr(obj)); + } + + *peerp = peer; + return (ISC_R_SUCCESS); + + cleanup: + dns_peer_detach(&peer); + return (result); +} + +#ifdef HAVE_DLOPEN +static isc_result_t +configure_dyndb(const cfg_obj_t *dyndb, isc_mem_t *mctx, + const dns_dyndbctx_t *dctx) +{ + isc_result_t result = ISC_R_SUCCESS; + const cfg_obj_t *obj; + const char *name, *library; + + /* Get the name of the dyndb instance and the library path . */ + name = cfg_obj_asstring(cfg_tuple_get(dyndb, "name")); + library = cfg_obj_asstring(cfg_tuple_get(dyndb, "library")); + + obj = cfg_tuple_get(dyndb, "parameters"); + if (obj != NULL) + result = dns_dyndb_load(library, name, cfg_obj_asstring(obj), + cfg_obj_file(obj), cfg_obj_line(obj), + mctx, dctx); + + if (result != ISC_R_SUCCESS) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dynamic database '%s' configuration failed: %s", + name, isc_result_totext(result)); + return (result); +} +#endif + + +static isc_result_t +disable_algorithms(const cfg_obj_t *disabled, dns_resolver_t *resolver) { + isc_result_t result; + const cfg_obj_t *algorithms; + const cfg_listelt_t *element; + const char *str; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + + name = dns_fixedname_initname(&fixed); + str = cfg_obj_asstring(cfg_tuple_get(disabled, "name")); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + + algorithms = cfg_tuple_get(disabled, "algorithms"); + for (element = cfg_list_first(algorithms); + element != NULL; + element = cfg_list_next(element)) + { + isc_textregion_t r; + dns_secalg_t alg; + + DE_CONST(cfg_obj_asstring(cfg_listelt_value(element)), r.base); + r.length = strlen(r.base); + + result = dns_secalg_fromtext(&alg, &r); + if (result != ISC_R_SUCCESS) { + uint8_t ui; + result = isc_parse_uint8(&ui, r.base, 10); + alg = ui; + } + if (result != ISC_R_SUCCESS) { + cfg_obj_log(cfg_listelt_value(element), + ns_g_lctx, ISC_LOG_ERROR, + "invalid algorithm"); + CHECK(result); + } + CHECK(dns_resolver_disable_algorithm(resolver, name, alg)); + } + cleanup: + return (result); +} + +static isc_result_t +disable_ds_digests(const cfg_obj_t *disabled, dns_resolver_t *resolver) { + isc_result_t result; + const cfg_obj_t *digests; + const cfg_listelt_t *element; + const char *str; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + + name = dns_fixedname_initname(&fixed); + str = cfg_obj_asstring(cfg_tuple_get(disabled, "name")); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + CHECK(dns_name_fromtext(name, &b, dns_rootname, 0, NULL)); + + digests = cfg_tuple_get(disabled, "digests"); + for (element = cfg_list_first(digests); + element != NULL; + element = cfg_list_next(element)) + { + isc_textregion_t r; + dns_dsdigest_t digest; + + DE_CONST(cfg_obj_asstring(cfg_listelt_value(element)), r.base); + r.length = strlen(r.base); + + /* disable_ds_digests handles numeric values. */ + result = dns_dsdigest_fromtext(&digest, &r); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(cfg_listelt_value(element), + ns_g_lctx, ISC_LOG_ERROR, + "invalid algorithm"); + CHECK(result); + } + CHECK(dns_resolver_disable_ds_digest(resolver, name, digest)); + } + cleanup: + return (result); +} + +static bool +on_disable_list(const cfg_obj_t *disablelist, dns_name_t *zonename) { + const cfg_listelt_t *element; + dns_fixedname_t fixed; + dns_name_t *name; + isc_result_t result; + const cfg_obj_t *value; + const char *str; + isc_buffer_t b; + + name = dns_fixedname_initname(&fixed); + + for (element = cfg_list_first(disablelist); + element != NULL; + element = cfg_list_next(element)) + { + value = cfg_listelt_value(element); + str = cfg_obj_asstring(value); + isc_buffer_constinit(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + result = dns_name_fromtext(name, &b, dns_rootname, + 0, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (dns_name_equal(name, zonename)) + return (true); + } + return (false); +} + +static isc_result_t +check_dbtype(dns_zone_t *zone, unsigned int dbtypec, const char **dbargv, + isc_mem_t *mctx) +{ + char **argv = NULL; + unsigned int i; + isc_result_t result = ISC_R_SUCCESS; + + CHECK(dns_zone_getdbtype(zone, &argv, mctx)); + + /* + * Check that all the arguments match. + */ + for (i = 0; i < dbtypec; i++) + if (argv[i] == NULL || strcmp(argv[i], dbargv[i]) != 0) + CHECK(ISC_R_FAILURE); + + /* + * Check that there are not extra arguments. + */ + if (i == dbtypec && argv[i] != NULL) + result = ISC_R_FAILURE; + + cleanup: + isc_mem_free(mctx, argv); + return (result); +} + +static isc_result_t +setquerystats(dns_zone_t *zone, isc_mem_t *mctx, dns_zonestat_level_t level) { + isc_result_t result; + isc_stats_t *zoneqrystats; + + dns_zone_setstatlevel(zone, level); + + zoneqrystats = NULL; + if (level == dns_zonestat_full) { + result = isc_stats_create(mctx, &zoneqrystats, + dns_nsstatscounter_max); + if (result != ISC_R_SUCCESS) + return (result); + } + dns_zone_setrequeststats(zone, zoneqrystats); + if (zoneqrystats != NULL) + isc_stats_detach(&zoneqrystats); + + return (ISC_R_SUCCESS); +} + +static ns_cache_t * +cachelist_find(ns_cachelist_t *cachelist, const char *cachename, + dns_rdataclass_t rdclass) +{ + ns_cache_t *nsc; + + for (nsc = ISC_LIST_HEAD(*cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (nsc->rdclass == rdclass && + strcmp(dns_cache_getname(nsc->cache), cachename) == 0) + return (nsc); + } + + return (NULL); +} + +static bool +cache_reusable(dns_view_t *originview, dns_view_t *view, + bool new_zero_no_soattl) +{ + if (originview->rdclass != view->rdclass || + originview->checknames != view->checknames || + dns_resolver_getzeronosoattl(originview->resolver) != + new_zero_no_soattl || + originview->acceptexpired != view->acceptexpired || + originview->enablevalidation != view->enablevalidation || + originview->maxcachettl != view->maxcachettl || + originview->maxncachettl != view->maxncachettl) { + return (false); + } + + return (true); +} + +static bool +cache_sharable(dns_view_t *originview, dns_view_t *view, + bool new_zero_no_soattl, + unsigned int new_cleaning_interval, + uint64_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 (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 (false); + } + + return (true); +} + +/* + * Callback from DLZ configure when the driver sets up a writeable zone + */ +static isc_result_t +dlzconfigure_callback(dns_view_t *view, dns_dlzdb_t *dlzdb, 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(dlzdb, zone, zclass, origin)); +} + +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 reverse[48+sizeof("ip6.arpa.")] = { 0 }; + char buf[sizeof("x.x.")]; + const char *dns64_dbtype[4] = { "_dns64", "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. + */ + s6 = na->type.in6.s6_addr; + while (prefixlen > 0) { + prefixlen -= 8; + snprintf(buf, sizeof(buf), "%x.%x.", s6[prefixlen/8] & 0xf, + (s6[prefixlen/8] >> 4) & 0xf); + strlcat(reverse, buf, sizeof(reverse)); + } + strlcat(reverse, "ip6.arpa.", sizeof(reverse)); + + /* + * Create the actual zone. + */ + if (server != NULL) + dns64_dbtype[2] = server; + if (contact != NULL) + dns64_dbtype[3] = contact; + name = dns_fixedname_initname(&fixed); + isc_buffer_constinit(&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, true); + CHECK(setquerystats(zone, mctx, dns_zonestat_none)); /* 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_name(dns_view_t *view, const cfg_obj_t *obj, dns_name_t *name, + const char *str, const char *msg) +{ + isc_result_t result; + + result = dns_name_fromstring(name, str, DNS_NAME_DOWNCASE, view->mctx); + if (result != ISC_R_SUCCESS) + cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid %s '%s'", msg, str); + return (result); +} + +static isc_result_t +configure_rpz_name2(dns_view_t *view, const cfg_obj_t *obj, dns_name_t *name, + const char *str, const dns_name_t *origin) +{ + isc_result_t result; + + result = dns_name_fromstring2(name, str, origin, DNS_NAME_DOWNCASE, + view->mctx); + if (result != ISC_R_SUCCESS) + cfg_obj_log(obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid zone '%s'", str); + return (result); +} + +static isc_result_t +configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element, + bool recursive_only_def, dns_ttl_t ttl_def, + const dns_rpz_zone_t *old, bool *old_rpz_okp) +{ + const cfg_obj_t *rpz_obj, *obj; + const char *str; + dns_rpz_zone_t *new; + isc_result_t result; + dns_rpz_num_t rpz_num; + + REQUIRE(old != NULL || !*old_rpz_okp); + + rpz_obj = cfg_listelt_value(element); + + if (view->rpzs->p.num_zones >= DNS_RPZ_MAX_ZONES) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "limit of %d response policy zones exceeded", + DNS_RPZ_MAX_ZONES); + return (ISC_R_FAILURE); + } + + new = isc_mem_get(view->rpzs->mctx, sizeof(*new)); + if (new == NULL) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "no memory for response policy zones"); + return (ISC_R_NOMEMORY); + } + + memset(new, 0, sizeof(*new)); + result = isc_refcount_init(&new->refs, 1); + if (result != ISC_R_SUCCESS) { + isc_mem_put(view->rpzs->mctx, new, sizeof(*new)); + return (result); + } + dns_name_init(&new->origin, NULL); + dns_name_init(&new->client_ip, NULL); + dns_name_init(&new->ip, NULL); + dns_name_init(&new->nsdname, NULL); + dns_name_init(&new->nsip, NULL); + dns_name_init(&new->passthru, NULL); + dns_name_init(&new->drop, NULL); + dns_name_init(&new->tcp_only, NULL); + dns_name_init(&new->cname, NULL); + new->num = view->rpzs->p.num_zones++; + view->rpzs->zones[new->num] = new; + + obj = cfg_tuple_get(rpz_obj, "recursive-only"); + if (cfg_obj_isvoid(obj) ? recursive_only_def : cfg_obj_asboolean(obj)) { + view->rpzs->p.no_rd_ok &= ~DNS_RPZ_ZBIT(new->num); + } else { + view->rpzs->p.no_rd_ok |= DNS_RPZ_ZBIT(new->num); + } + + obj = cfg_tuple_get(rpz_obj, "log"); + if (!cfg_obj_isvoid(obj) && !cfg_obj_asboolean(obj)) { + view->rpzs->p.no_log |= DNS_RPZ_ZBIT(new->num); + } else { + view->rpzs->p.no_log &= ~DNS_RPZ_ZBIT(new->num); + } + + obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); + if (cfg_obj_isuint32(obj)) { + new->max_policy_ttl = cfg_obj_asuint32(obj); + } else { + new->max_policy_ttl = ttl_def; + } + if (*old_rpz_okp && new->max_policy_ttl != old->max_policy_ttl) + *old_rpz_okp = false; + + str = cfg_obj_asstring(cfg_tuple_get(rpz_obj, "zone name")); + result = configure_rpz_name(view, rpz_obj, &new->origin, str, "zone"); + if (result != ISC_R_SUCCESS) + return (result); + if (dns_name_equal(&new->origin, dns_rootname)) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "invalid zone name '%s'", str); + return (DNS_R_EMPTYLABEL); + } + for (rpz_num = 0; rpz_num < view->rpzs->p.num_zones-1; ++rpz_num) { + if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, + &new->origin)) { + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_ERROR_LEVEL, + "duplicate '%s'", str); + result = DNS_R_DUPLICATE; + return (result); + } + } + if (*old_rpz_okp && !dns_name_equal(&old->origin, &new->origin)) + *old_rpz_okp = false; + + result = configure_rpz_name2(view, rpz_obj, &new->client_ip, + DNS_RPZ_CLIENT_IP_ZONE, &new->origin); + if (result != ISC_R_SUCCESS) + return (result); + + result = configure_rpz_name2(view, rpz_obj, &new->ip, + DNS_RPZ_IP_ZONE, &new->origin); + if (result != ISC_R_SUCCESS) + return (result); + + result = configure_rpz_name2(view, rpz_obj, &new->nsdname, + DNS_RPZ_NSDNAME_ZONE, &new->origin); + if (result != ISC_R_SUCCESS) + return (result); + + result = configure_rpz_name2(view, rpz_obj, &new->nsip, + DNS_RPZ_NSIP_ZONE, &new->origin); + if (result != ISC_R_SUCCESS) + return (result); + + result = configure_rpz_name(view, rpz_obj, &new->passthru, + DNS_RPZ_PASSTHRU_NAME, "name"); + if (result != ISC_R_SUCCESS) + return (result); + + result = configure_rpz_name(view, rpz_obj, &new->drop, + DNS_RPZ_DROP_NAME, "name"); + if (result != ISC_R_SUCCESS) + return (result); + + result = configure_rpz_name(view, rpz_obj, &new->tcp_only, + DNS_RPZ_TCP_ONLY_NAME, "name"); + if (result != ISC_R_SUCCESS) + return (result); + + obj = cfg_tuple_get(rpz_obj, "policy"); + if (cfg_obj_isvoid(obj)) { + new->policy = DNS_RPZ_POLICY_GIVEN; + } else { + str = cfg_obj_asstring(cfg_tuple_get(obj, "policy name")); + new->policy = dns_rpz_str2policy(str); + INSIST(new->policy != DNS_RPZ_POLICY_ERROR); + if (new->policy == DNS_RPZ_POLICY_CNAME) { + str = cfg_obj_asstring(cfg_tuple_get(obj, "cname")); + result = configure_rpz_name(view, rpz_obj, &new->cname, + str, "cname"); + if (result != ISC_R_SUCCESS) + return (result); + } + } + if (*old_rpz_okp && (new->policy != old->policy || + !dns_name_equal(&old->cname, &new->cname))) + *old_rpz_okp = false; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +configure_rpz(dns_view_t *view, const cfg_obj_t *rpz_obj, + bool *old_rpz_okp) +{ + const cfg_listelt_t *zone_element; + const cfg_obj_t *sub_obj; + bool recursive_only_def; + dns_ttl_t ttl_def; + dns_rpz_zones_t *new; + const dns_rpz_zones_t *old; + dns_view_t *pview; + const dns_rpz_zone_t *old_zone; + isc_result_t result; + int i; + + *old_rpz_okp = false; + + zone_element = cfg_list_first(cfg_tuple_get(rpz_obj, "zone list")); + if (zone_element == NULL) + return (ISC_R_SUCCESS); + + result = dns_rpz_new_zones(&view->rpzs, view->mctx); + if (result != ISC_R_SUCCESS) + return (result); + new = view->rpzs; + + sub_obj = cfg_tuple_get(rpz_obj, "recursive-only"); + if (!cfg_obj_isvoid(sub_obj) && + !cfg_obj_asboolean(sub_obj)) + recursive_only_def = false; + else + recursive_only_def = true; + + sub_obj = cfg_tuple_get(rpz_obj, "break-dnssec"); + if (!cfg_obj_isvoid(sub_obj) && + cfg_obj_asboolean(sub_obj)) + new->p.break_dnssec = true; + else + new->p.break_dnssec = false; + + sub_obj = cfg_tuple_get(rpz_obj, "max-policy-ttl"); + if (cfg_obj_isuint32(sub_obj)) + ttl_def = cfg_obj_asuint32(sub_obj); + else + ttl_def = DNS_RPZ_MAX_TTL_DEFAULT; + + sub_obj = cfg_tuple_get(rpz_obj, "min-ns-dots"); + if (cfg_obj_isuint32(sub_obj)) + new->p.min_ns_labels = cfg_obj_asuint32(sub_obj) + 1; + else + new->p.min_ns_labels = 2; + + sub_obj = cfg_tuple_get(rpz_obj, "qname-wait-recurse"); + if (cfg_obj_isvoid(sub_obj) || cfg_obj_asboolean(sub_obj)) + new->p.qname_wait_recurse = true; + else + new->p.qname_wait_recurse = false; + + sub_obj = cfg_tuple_get(rpz_obj, "nsip-wait-recurse"); + if (cfg_obj_isvoid(sub_obj) || cfg_obj_asboolean(sub_obj)) + new->p.nsip_wait_recurse = true; + else + new->p.nsip_wait_recurse = false; + + pview = NULL; + result = dns_viewlist_find(&ns_g_server->viewlist, + view->name, view->rdclass, &pview); + if (result == ISC_R_SUCCESS) { + old = pview->rpzs; + } else { + old = NULL; + } + if (old == NULL) + *old_rpz_okp = false; + else + *old_rpz_okp = true; + + for (i = 0; + zone_element != NULL; + ++i, zone_element = cfg_list_next(zone_element)) { + INSIST(old != NULL || !*old_rpz_okp); + if (*old_rpz_okp && i < old->p.num_zones) { + old_zone = old->zones[i]; + } else { + *old_rpz_okp = false; + old_zone = NULL; + } + result = configure_rpz_zone(view, zone_element, + recursive_only_def, ttl_def, + old_zone, old_rpz_okp); + if (result != ISC_R_SUCCESS) { + if (pview != NULL) + dns_view_detach(&pview); + return (result); + } + } + + /* + * If this is a reloading and the parameters and list of policy + * zones are unchanged, then use the same policy data. + * Data for individual zones that must be reloaded will be merged. + */ + if (old != NULL && memcmp(&old->p, &new->p, sizeof(new->p)) != 0) + *old_rpz_okp = false; + if (*old_rpz_okp) { + dns_rpz_detach_rpzs(&view->rpzs); + dns_rpz_attach_rpzs(pview->rpzs, &view->rpzs); + } else if (old != NULL && pview != NULL) { + pview->rpzs->rpz_ver += 1; + view->rpzs->rpz_ver = pview->rpzs->rpz_ver; + cfg_obj_log(rpz_obj, ns_g_lctx, DNS_RPZ_DEBUG_LEVEL1, + "updated RPZ policy: version %d", + view->rpzs->rpz_ver); + } + + if (pview != NULL) + dns_view_detach(&pview); + + return (ISC_R_SUCCESS); +} + +static void +catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { + catz_chgzone_event_t *ev = (catz_chgzone_event_t *) event0; + isc_result_t result; + isc_buffer_t namebuf; + isc_buffer_t *confbuf; + char nameb[DNS_NAME_FORMATSIZE]; + const cfg_obj_t *zlist = NULL; + cfg_obj_t *zoneconf = NULL; + cfg_obj_t *zoneobj = NULL; + ns_cfgctx_t *cfg; + dns_zone_t *zone = NULL; + + cfg = (ns_cfgctx_t *) ev->view->new_zone_config; + if (cfg == NULL) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "catz: allow-new-zones statement missing from " + "config; cannot add zone from the catalog"); + goto cleanup; + } + + isc_buffer_init(&namebuf, nameb, DNS_NAME_FORMATSIZE); + dns_name_totext(dns_catz_entry_getname(ev->entry), true, &namebuf); + isc_buffer_putuint8(&namebuf, 0); + + /* Zone shouldn't already exist */ + result = dns_zt_find(ev->view->zonetable, + dns_catz_entry_getname(ev->entry), 0, NULL, &zone); + + if (ev->mod == true) { + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: error \"%s\" while trying to " + "modify zone \"%s\"", + isc_result_totext(result), + nameb); + goto cleanup; + } else { + if (!dns_zone_getadded(zone)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, + "catz: catz_addmodzone_taskaction: " + "zone '%s' is not a dynamically " + "added zone", + nameb); + goto cleanup; + } + if (dns_zone_get_parentcatz(zone) != ev->origin) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' exists in multiple " + "catalog zones", + nameb); + goto cleanup; + } + dns_zone_detach(&zone); + } + + } else { + if (result != ISC_R_NOTFOUND && result != DNS_R_PARTIALMATCH) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: error \"%s\" while trying to " + "add zone \"%s\"", + isc_result_totext(result), + nameb); + goto cleanup; + } else { /* this can happen in case of DNS_R_PARTIALMATCH */ + if (zone != NULL) + dns_zone_detach(&zone); + } + } + RUNTIME_CHECK(zone == NULL); + /* Create a config for new zone */ + confbuf = NULL; + result = dns_catz_generate_zonecfg(ev->origin, ev->entry, &confbuf); + if (result == ISC_R_SUCCESS) { + cfg_parser_reset(cfg->add_parser); + result = cfg_parse_buffer3(cfg->add_parser, confbuf, "catz", 0, + &cfg_type_addzoneconf, &zoneconf); + isc_buffer_free(&confbuf); + } + /* + * Fail if either dns_catz_generate_zonecfg() or cfg_parse_buffer3() + * failed. + */ + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "catz: error \"%s\" while trying to generate " + "config for zone \"%s\"", + isc_result_totext(result), nameb); + goto cleanup; + } + CHECK(cfg_map_get(zoneconf, "zone", &zlist)); + if (!cfg_obj_islist(zlist)) + CHECK(ISC_R_FAILURE); + + /* For now we only support adding one zone at a time */ + zoneobj = cfg_listelt_value(cfg_list_first(zlist)); + + /* Mark view unfrozen so that zone can be added */ + + result = isc_task_beginexclusive(task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_view_thaw(ev->view); + result = configure_zone(cfg->config, zoneobj, cfg->vconfig, + ev->cbd->server->mctx, ev->view, + &ev->cbd->server->viewlist, cfg->actx, + true, false, ev->mod); + dns_view_freeze(ev->view); + isc_task_endexclusive(task); + + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: failed to configure zone \"%s\" - %d", + nameb, result); + goto cleanup; + } + + /* Is it there yet? */ + CHECK(dns_zt_find(ev->view->zonetable, + dns_catz_entry_getname(ev->entry), 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_ERROR, + "catz: dns_zone_loadnew() failed " + "with %s; reverting.", + isc_result_totext(result)); + + /* 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(ev->view->zonetable, zone); + goto cleanup; + } + + /* Flag the zone as having been added at runtime */ + dns_zone_setadded(zone, true); + dns_zone_set_parentcatz(zone, ev->origin); + + cleanup: + if (zone != NULL) + dns_zone_detach(&zone); + if (zoneconf != NULL) + cfg_obj_destroy(cfg->add_parser, &zoneconf); + dns_catz_entry_detach(ev->origin, &ev->entry); + dns_catz_zone_detach(&ev->origin); + dns_view_detach(&ev->view); + isc_event_free(ISC_EVENT_PTR(&ev)); +} + +static void +catz_delzone_taskaction(isc_task_t *task, isc_event_t *event0) { + catz_chgzone_event_t *ev = (catz_chgzone_event_t *) event0; + isc_result_t result; + dns_zone_t *zone = NULL; + dns_db_t *dbp = NULL; + char cname[DNS_NAME_FORMATSIZE]; + const char * file; + + result = isc_task_beginexclusive(task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_name_format(dns_catz_entry_getname(ev->entry), cname, + DNS_NAME_FORMATSIZE); + result = dns_zt_find(ev->view->zonetable, + dns_catz_entry_getname(ev->entry), 0, NULL, &zone); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' not found", cname); + goto cleanup; + } + + if (!dns_zone_getadded(zone)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' is not a dynamically added zone", + cname); + goto cleanup; + } + + if (dns_zone_get_parentcatz(zone) != ev->origin) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: zone " + "'%s' exists in multiple catalog zones", + cname); + 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(ev->view->zonetable, zone)); + file = dns_zone_getfile(zone); + if (file != NULL) + isc_file_remove(file); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "catz: catz_delzone_taskaction: " + "zone '%s' deleted", cname); + cleanup: + isc_task_endexclusive(task); + if (zone != NULL) + dns_zone_detach(&zone); + dns_catz_entry_detach(ev->origin, &ev->entry); + dns_catz_zone_detach(&ev->origin); + dns_view_detach(&ev->view); + isc_event_free(ISC_EVENT_PTR(&ev)); +} + +static isc_result_t +catz_create_chg_task(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata, + isc_eventtype_t type) +{ + catz_chgzone_event_t *event; + isc_task_t *task; + isc_result_t result; + isc_taskaction_t action; + + switch (type) { + case DNS_EVENT_CATZADDZONE: + case DNS_EVENT_CATZMODZONE: + action = catz_addmodzone_taskaction; + break; + case DNS_EVENT_CATZDELZONE: + action = catz_delzone_taskaction; + break; + default: + REQUIRE(0); + } + + event = (catz_chgzone_event_t *) isc_event_allocate(view->mctx, origin, + type, action, NULL, + sizeof(*event)); + if (event == NULL) + return (ISC_R_NOMEMORY); + + event->cbd = (catz_cb_data_t *) udata; + event->entry = NULL; + event->origin = NULL; + event->view = NULL; + event->mod = (type == DNS_EVENT_CATZMODZONE); + dns_catz_entry_attach(entry, &event->entry); + dns_catz_zone_attach(origin, &event->origin); + dns_view_attach(view, &event->view); + + task = NULL; + result = isc_taskmgr_excltask(taskmgr, &task); + REQUIRE(result == ISC_R_SUCCESS); + isc_task_send(task, ISC_EVENT_PTR(&event)); + isc_task_detach(&task); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +catz_addzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata) +{ + return (catz_create_chg_task(entry, origin, view, taskmgr, udata, + DNS_EVENT_CATZADDZONE)); +} + +static isc_result_t +catz_delzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata) +{ + return (catz_create_chg_task(entry, origin, view, taskmgr, udata, + DNS_EVENT_CATZDELZONE)); +} + +static isc_result_t +catz_modzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, + dns_view_t *view, isc_taskmgr_t *taskmgr, void *udata) +{ + return (catz_create_chg_task(entry, origin, view, taskmgr, udata, + DNS_EVENT_CATZMODZONE)); +} + +static isc_result_t +configure_catz_zone(dns_view_t *view, const cfg_obj_t *config, + const cfg_listelt_t *element) +{ + const cfg_obj_t *catz_obj, *obj; + dns_catz_zone_t *zone = NULL; + const char *str; + isc_result_t result; + dns_name_t origin; + dns_catz_options_t *opts; + dns_view_t *pview = NULL; + + dns_name_init(&origin, NULL); + catz_obj = cfg_listelt_value(element); + + str = cfg_obj_asstring(cfg_tuple_get(catz_obj, "zone name")); + + result = dns_name_fromstring(&origin, str, DNS_NAME_DOWNCASE, + view->mctx); + if (result == ISC_R_SUCCESS && dns_name_equal(&origin, dns_rootname)) + result = DNS_R_EMPTYLABEL; + + if (result != ISC_R_SUCCESS) { + cfg_obj_log(catz_obj, ns_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: invalid zone name '%s'", str); + goto cleanup; + } + + result = dns_catz_add_zone(view->catzs, &origin, &zone); + if (result != ISC_R_SUCCESS && result != ISC_R_EXISTS) { + cfg_obj_log(catz_obj, ns_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: unable to create catalog zone '%s', " + "error %s", + str, isc_result_totext(result)); + goto cleanup; + } + + if (result == ISC_R_EXISTS) { + isc_ht_iter_t *it = NULL; + + result = dns_viewlist_find(&ns_g_server->viewlist, + view->name, + view->rdclass, &pview); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * xxxwpk todo: reconfigure the zone!!!! + */ + cfg_obj_log(catz_obj, ns_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: catalog zone '%s' will not be reconfigured", + str); + /* + * We have to walk through all the member zones and attach + * them to current view + */ + result = dns_catz_get_iterator(zone, &it); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(catz_obj, ns_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: unable to create iterator"); + goto cleanup; + } + + for (result = isc_ht_iter_first(it); + result == ISC_R_SUCCESS; + result = isc_ht_iter_next(it)) + { + dns_name_t *name = NULL; + dns_zone_t *dnszone = NULL; + dns_catz_entry_t *entry = NULL; + isc_result_t tresult; + + isc_ht_iter_current(it, (void **) &entry); + name = dns_catz_entry_getname(entry); + + tresult = dns_view_findzone(pview, name, &dnszone); + RUNTIME_CHECK(tresult == ISC_R_SUCCESS); + + dns_zone_setview(dnszone, view); + if (view->acache != NULL) + dns_zone_setacache(dnszone, view->acache); + dns_view_addzone(view, dnszone); + + /* + * The dns_view_findzone() call above increments the + * zone's reference count, which we need to decrement + * back. However, as dns_zone_detach() sets the + * supplied pointer to NULL, calling it is deferred + * until the dnszone variable is no longer used. + */ + dns_zone_detach(&dnszone); + } + + isc_ht_iter_destroy(&it); + + result = ISC_R_SUCCESS; + } + + dns_catz_zone_resetdefoptions(zone); + opts = dns_catz_zone_getdefoptions(zone); + + obj = cfg_tuple_get(catz_obj, "default-masters"); + if (obj != NULL && cfg_obj_istuple(obj)) + result = ns_config_getipandkeylist(config, obj, + view->mctx, &opts->masters); + + obj = cfg_tuple_get(catz_obj, "in-memory"); + if (obj != NULL && cfg_obj_isboolean(obj)) + opts->in_memory = cfg_obj_asboolean(obj); + + obj = cfg_tuple_get(catz_obj, "zone-directory"); + if (!opts->in_memory && obj != NULL && cfg_obj_isstring(obj)) { + opts->zonedir = isc_mem_strdup(view->mctx, + cfg_obj_asstring(obj)); + if (isc_file_isdirectory(opts->zonedir) != ISC_R_SUCCESS) { + cfg_obj_log(obj, ns_g_lctx, DNS_CATZ_ERROR_LEVEL, + "catz: zone-directory '%s' " + "not found; zone files will not be " + "saved", opts->zonedir); + opts->in_memory = true; + } + } + + obj = cfg_tuple_get(catz_obj, "min-update-interval"); + if (obj != NULL && cfg_obj_isuint32(obj)) + opts->min_update_interval = cfg_obj_asuint32(obj); + + cleanup: + if (pview != NULL) + dns_view_detach(&pview); + dns_name_free(&origin, view->mctx); + + return (result); +} + +static catz_cb_data_t ns_catz_cbdata; +static dns_catz_zonemodmethods_t ns_catz_zonemodmethods = { + catz_addzone, + catz_modzone, + catz_delzone, + &ns_catz_cbdata +}; + +static isc_result_t +configure_catz(dns_view_t *view, const cfg_obj_t *config, + const cfg_obj_t *catz_obj) +{ + const cfg_listelt_t *zone_element; + const dns_catz_zones_t *old = NULL; + dns_view_t *pview = NULL; + isc_result_t result; + + /* xxxwpk TODO do it cleaner, once, somewhere */ + ns_catz_cbdata.server = ns_g_server; + + zone_element = cfg_list_first(cfg_tuple_get(catz_obj, "zone list")); + if (zone_element == NULL) + return (ISC_R_SUCCESS); + + CHECK(dns_catz_new_zones(&view->catzs, &ns_catz_zonemodmethods, + view->mctx, ns_g_taskmgr, ns_g_timermgr)); + + result = dns_viewlist_find(&ns_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result == ISC_R_SUCCESS) + old = pview->catzs; + + if (old != NULL) { + dns_catz_catzs_detach(&view->catzs); + dns_catz_catzs_attach(pview->catzs, &view->catzs); + dns_catz_prereconfig(view->catzs); + } + + while (zone_element != NULL) { + CHECK(configure_catz_zone(view, config, zone_element)); + zone_element = cfg_list_next(zone_element); + } + + if (old != NULL) + dns_catz_postreconfig(view->catzs); + + result = ISC_R_SUCCESS; + + cleanup: + if (pview != NULL) + dns_view_detach(&pview); + + return (result); +} + +#define CHECK_RRL(cond, pat, val1, val2) \ + do { \ + if (!(cond)) { \ + cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, \ + pat, val1, val2); \ + result = ISC_R_RANGE; \ + goto cleanup; \ + } \ + } while (0) + +#define CHECK_RRL_RATE(rate, def, max_rate, name) \ + do { \ + obj = NULL; \ + rrl->rate.str = name; \ + result = cfg_map_get(map, name, &obj); \ + if (result == ISC_R_SUCCESS) { \ + rrl->rate.r = cfg_obj_asuint32(obj); \ + CHECK_RRL(rrl->rate.r <= max_rate, \ + name" %d > %d", \ + rrl->rate.r, max_rate); \ + } else { \ + rrl->rate.r = def; \ + } \ + rrl->rate.scaled = rrl->rate.r; \ + } while (0) + +static isc_result_t +configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) { + const cfg_obj_t *obj; + dns_rrl_t *rrl; + isc_result_t result; + int min_entries, i, j; + + /* + * Most DNS servers have few clients, but intentinally open + * recursive and authoritative servers often have many. + * So start with a small number of entries unless told otherwise + * to reduce cold-start costs. + */ + min_entries = 500; + obj = NULL; + result = cfg_map_get(map, "min-table-size", &obj); + if (result == ISC_R_SUCCESS) { + min_entries = cfg_obj_asuint32(obj); + if (min_entries < 1) + min_entries = 1; + } + result = dns_rrl_init(&rrl, view, min_entries); + if (result != ISC_R_SUCCESS) + return (result); + + i = ISC_MAX(20000, min_entries); + obj = NULL; + result = cfg_map_get(map, "max-table-size", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= min_entries, + "max-table-size %d < min-table-size %d", + i, min_entries); + } + rrl->max_entries = i; + + CHECK_RRL_RATE(responses_per_second, 0, DNS_RRL_MAX_RATE, + "responses-per-second"); + CHECK_RRL_RATE(referrals_per_second, + rrl->responses_per_second.r, DNS_RRL_MAX_RATE, + "referrals-per-second"); + CHECK_RRL_RATE(nodata_per_second, + rrl->responses_per_second.r, DNS_RRL_MAX_RATE, + "nodata-per-second"); + CHECK_RRL_RATE(nxdomains_per_second, + rrl->responses_per_second.r, DNS_RRL_MAX_RATE, + "nxdomains-per-second"); + CHECK_RRL_RATE(errors_per_second, + rrl->responses_per_second.r, DNS_RRL_MAX_RATE, + "errors-per-second"); + + CHECK_RRL_RATE(all_per_second, 0, DNS_RRL_MAX_RATE, + "all-per-second"); + + CHECK_RRL_RATE(slip, 2, DNS_RRL_MAX_SLIP, + "slip"); + + i = 15; + obj = NULL; + result = cfg_map_get(map, "window", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= 1 && i <= DNS_RRL_MAX_WINDOW, + "window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW); + } + rrl->window = i; + + i = 0; + obj = NULL; + result = cfg_map_get(map, "qps-scale", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= 1, "invalid 'qps-scale %d'%s", i, ""); + } + rrl->qps_scale = i; + rrl->qps = 1.0; + + i = 24; + obj = NULL; + result = cfg_map_get(map, "ipv4-prefix-length", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= 8 && i <= 32, + "invalid 'ipv4-prefix-length %d'%s", i, ""); + } + rrl->ipv4_prefixlen = i; + if (i == 32) + rrl->ipv4_mask = 0xffffffff; + else + rrl->ipv4_mask = htonl(0xffffffff << (32-i)); + + i = 56; + obj = NULL; + result = cfg_map_get(map, "ipv6-prefix-length", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + CHECK_RRL(i >= 16 && i <= DNS_RRL_MAX_PREFIX, + "ipv6-prefix-length %d < 16 or > %d", + i, DNS_RRL_MAX_PREFIX); + } + rrl->ipv6_prefixlen = i; + for (j = 0; j < 4; ++j) { + if (i <= 0) { + rrl->ipv6_mask[j] = 0; + } else if (i < 32) { + rrl->ipv6_mask[j] = htonl(0xffffffff << (32-i)); + } else { + rrl->ipv6_mask[j] = 0xffffffff; + } + i -= 32; + } + + obj = NULL; + result = cfg_map_get(map, "exempt-clients", &obj); + if (result == ISC_R_SUCCESS) { + result = cfg_acl_fromconfig(obj, config, ns_g_lctx, + ns_g_aclconfctx, ns_g_mctx, + 0, &rrl->exempt); + CHECK_RRL(result == ISC_R_SUCCESS, + "invalid %s%s", "address match list", ""); + } + + obj = NULL; + result = cfg_map_get(map, "log-only", &obj); + if (result == ISC_R_SUCCESS && cfg_obj_asboolean(obj)) + rrl->log_only = true; + else + rrl->log_only = false; + + return (ISC_R_SUCCESS); + + cleanup: + dns_rrl_view_destroy(view); + return (result); +} + +static isc_result_t +add_soa(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, + dns_name_t *origin, dns_name_t *contact) +{ + dns_dbnode_t *node = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + isc_result_t result; + unsigned char buf[DNS_SOA_BUFFERSIZE]; + + CHECK(dns_soa_buildrdata(origin, contact, dns_db_class(db), + 0, 28800, 7200, 604800, 86400, buf, &rdata)); + + dns_rdatalist_init(&rdatalist); + rdatalist.type = rdata.type; + rdatalist.rdclass = rdata.rdclass; + rdatalist.ttl = 86400; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + dns_rdataset_init(&rdataset); + CHECK(dns_rdatalist_tordataset(&rdatalist, &rdataset)); + CHECK(dns_db_findnode(db, name, true, &node)); + CHECK(dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL)); + + cleanup: + if (node != NULL) + dns_db_detachnode(db, &node); + return (result); +} + +static isc_result_t +add_ns(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, + dns_name_t *nsname) +{ + dns_dbnode_t *node = NULL; + dns_rdata_ns_t ns; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + isc_result_t result; + isc_buffer_t b; + unsigned char buf[DNS_NAME_MAXWIRE]; + + isc_buffer_init(&b, buf, sizeof(buf)); + + ns.common.rdtype = dns_rdatatype_ns; + ns.common.rdclass = dns_db_class(db); + ns.mctx = NULL; + dns_name_init(&ns.name, NULL); + dns_name_clone(nsname, &ns.name); + CHECK(dns_rdata_fromstruct(&rdata, dns_db_class(db), dns_rdatatype_ns, + &ns, &b)); + + dns_rdatalist_init(&rdatalist); + rdatalist.type = rdata.type; + rdatalist.rdclass = rdata.rdclass; + rdatalist.ttl = 86400; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + dns_rdataset_init(&rdataset); + CHECK(dns_rdatalist_tordataset(&rdatalist, &rdataset)); + CHECK(dns_db_findnode(db, name, true, &node)); + CHECK(dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL)); + + cleanup: + if (node != NULL) + dns_db_detachnode(db, &node); + return (result); +} + +static isc_result_t +create_empty_zone(dns_zone_t *zone, dns_name_t *name, dns_view_t *view, + const cfg_obj_t *zonelist, const char **empty_dbtype, + int empty_dbtypec, dns_zonestat_level_t statlevel) +{ + char namebuf[DNS_NAME_FORMATSIZE]; + const cfg_listelt_t *element; + const cfg_obj_t *obj; + const cfg_obj_t *zconfig; + const cfg_obj_t *zoptions; + const char *rbt_dbtype[4] = { "rbt" }; + const char *sep = ": view "; + const char *str; + const char *viewname = view->name; + dns_db_t *db = NULL; + dns_dbversion_t *version = NULL; + dns_fixedname_t cfixed; + dns_fixedname_t fixed; + dns_fixedname_t nsfixed; + dns_name_t *contact; + dns_name_t *ns; + dns_name_t *zname; + dns_zone_t *myzone = NULL; + int rbt_dbtypec = 1; + isc_result_t result; + dns_namereln_t namereln; + int order; + unsigned int nlabels; + + zname = dns_fixedname_initname(&fixed); + ns = dns_fixedname_initname(&nsfixed); + contact = dns_fixedname_initname(&cfixed); + + /* + * Look for forward "zones" beneath this empty zone and if so + * create a custom db for the empty zone. + */ + for (element = cfg_list_first(zonelist); + element != NULL; + element = cfg_list_next(element)) { + + zconfig = cfg_listelt_value(element); + str = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + CHECK(dns_name_fromstring(zname, str, 0, NULL)); + namereln = dns_name_fullcompare(zname, name, &order, &nlabels); + if (namereln != dns_namereln_subdomain) + continue; + + zoptions = cfg_tuple_get(zconfig, "options"); + + obj = NULL; + (void)cfg_map_get(zoptions, "type", &obj); + if (obj != NULL && + strcasecmp(cfg_obj_asstring(obj), "forward") == 0) { + obj = NULL; + (void)cfg_map_get(zoptions, "forward", &obj); + if (obj == NULL) + continue; + if (strcasecmp(cfg_obj_asstring(obj), "only") != 0) + continue; + } + if (db == NULL) { + CHECK(dns_db_create(view->mctx, "rbt", name, + dns_dbtype_zone, view->rdclass, + 0, NULL, &db)); + CHECK(dns_db_newversion(db, &version)); + if (strcmp(empty_dbtype[2], "@") == 0) + dns_name_clone(name, ns); + else + CHECK(dns_name_fromstring(ns, empty_dbtype[2], + 0, NULL)); + CHECK(dns_name_fromstring(contact, empty_dbtype[3], + 0, NULL)); + CHECK(add_soa(db, version, name, ns, contact)); + CHECK(add_ns(db, version, name, ns)); + } + CHECK(add_ns(db, version, zname, dns_rootname)); + } + + /* + * Is the existing zone the ok to use? + */ + if (zone != NULL) { + unsigned int typec; + const char **dbargv; + + if (db != NULL) { + typec = rbt_dbtypec; + dbargv = rbt_dbtype; + } else { + typec = empty_dbtypec; + dbargv = empty_dbtype; + } + + result = check_dbtype(zone, typec, dbargv, view->mctx); + if (result != ISC_R_SUCCESS) + zone = NULL; + + if (zone != NULL && dns_zone_gettype(zone) != dns_zone_master) + zone = NULL; + if (zone != NULL && dns_zone_getfile(zone) != NULL) + zone = NULL; + if (zone != NULL) { + dns_zone_getraw(zone, &myzone); + if (myzone != NULL) { + dns_zone_detach(&myzone); + zone = NULL; + } + } + } + + if (zone == NULL) { + CHECK(dns_zonemgr_createzone(ns_g_server->zonemgr, &myzone)); + zone = myzone; + CHECK(dns_zone_setorigin(zone, name)); + CHECK(dns_zonemgr_managezone(ns_g_server->zonemgr, zone)); + if (db == NULL) + CHECK(dns_zone_setdbtype(zone, empty_dbtypec, + empty_dbtype)); + dns_zone_setclass(zone, view->rdclass); + dns_zone_settype(zone, dns_zone_master); + dns_zone_setstats(zone, ns_g_server->zonestats); + } + + dns_zone_setoption(zone, ~DNS_ZONEOPT_NOCHECKNS, false); + dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); + dns_zone_setnotifytype(zone, dns_notifytype_no); + dns_zone_setdialup(zone, dns_dialuptype_no); + dns_zone_setautomatic(zone, true); + if (view->queryacl != NULL) + dns_zone_setqueryacl(zone, view->queryacl); + else + dns_zone_clearqueryacl(zone); + if (view->queryonacl != NULL) + dns_zone_setqueryonacl(zone, view->queryonacl); + else + dns_zone_clearqueryonacl(zone); + dns_zone_clearupdateacl(zone); + if (view->transferacl != NULL) + dns_zone_setxfracl(zone, view->transferacl); + else + dns_zone_clearxfracl(zone); + + CHECK(setquerystats(zone, view->mctx, statlevel)); + if (db != NULL) { + dns_db_closeversion(db, &version, true); + CHECK(dns_zone_replacedb(zone, db, false)); + } + dns_zone_setoption2(zone, DNS_ZONEOPT2_AUTOEMPTY, true); + dns_zone_setview(zone, view); + CHECK(dns_view_addzone(view, zone)); + + if (!strcmp(viewname, "_default")) { + sep = ""; + viewname = ""; + } + dns_name_format(name, namebuf, sizeof(namebuf)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, "automatic empty zone%s%s: %s", + sep, viewname, namebuf); + + cleanup: + if (myzone != NULL) + dns_zone_detach(&myzone); + if (version != NULL) + dns_db_closeversion(db, &version, false); + if (db != NULL) + dns_db_detach(&db); + + INSIST(version == NULL); + + return (result); +} + +#ifdef HAVE_DNSTAP +static isc_result_t +configure_dnstap(const cfg_obj_t **maps, dns_view_t *view) { + isc_result_t result; + const cfg_obj_t *obj, *obj2; + const cfg_listelt_t *element; + const char *dpath = ns_g_defaultdnstap; + const cfg_obj_t *dlist = NULL; + dns_dtmsgtype_t dttypes = 0; + dns_dtmode_t dmode; + unsigned int i; + struct fstrm_iothr_options *fopt = NULL; + + result = ns_config_get(maps, "dnstap", &dlist); + if (result != ISC_R_SUCCESS) + return (ISC_R_SUCCESS); + + for (element = cfg_list_first(dlist); + element != NULL; + element = cfg_list_next(element)) + { + const char *str; + dns_dtmsgtype_t dt = 0; + + obj = cfg_listelt_value(element); + obj2 = cfg_tuple_get(obj, "type"); + str = cfg_obj_asstring(obj2); + if (strcasecmp(str, "client") == 0) { + dt |= DNS_DTTYPE_CQ|DNS_DTTYPE_CR; + } else if (strcasecmp(str, "auth") == 0) { + dt |= DNS_DTTYPE_AQ|DNS_DTTYPE_AR; + } else if (strcasecmp(str, "resolver") == 0) { + dt |= DNS_DTTYPE_RQ|DNS_DTTYPE_RR; + } else if (strcasecmp(str, "forwarder") == 0) { + dt |= DNS_DTTYPE_FQ|DNS_DTTYPE_FR; + } else if (strcasecmp(str, "all") == 0) { + dt |= DNS_DTTYPE_CQ|DNS_DTTYPE_CR| + DNS_DTTYPE_AQ|DNS_DTTYPE_AR| + DNS_DTTYPE_RQ|DNS_DTTYPE_RR| + DNS_DTTYPE_FQ|DNS_DTTYPE_FR; + } + + obj2 = cfg_tuple_get(obj, "mode"); + if (obj2 == NULL || cfg_obj_isvoid(obj2)) { + dttypes |= dt; + continue; + } + + str = cfg_obj_asstring(obj2); + if (strcasecmp(str, "query") == 0) { + dt &= ~DNS_DTTYPE_RESPONSE; + } else if (strcasecmp(str, "response") == 0) { + dt &= ~DNS_DTTYPE_QUERY; + } + + dttypes |= dt; + } + + if (ns_g_server->dtenv == NULL && dttypes != 0) { + obj = NULL; + CHECKM(ns_config_get(maps, "dnstap-output", &obj), + "'dnstap-output' must be set if 'dnstap' is set"); + + obj2 = cfg_tuple_get(obj, "mode"); + if (obj2 == NULL) + CHECKM(ISC_R_FAILURE, "dnstap-output mode not found"); + if (strcasecmp(cfg_obj_asstring(obj2), "file") == 0) + dmode = dns_dtmode_file; + else + dmode = dns_dtmode_unix; + + obj2 = cfg_tuple_get(obj, "path"); + if (obj2 == NULL) + CHECKM(ISC_R_FAILURE, "dnstap-output path not found"); + + dpath = cfg_obj_asstring(obj2); + + fopt = fstrm_iothr_options_init(); + fstrm_iothr_options_set_num_input_queues(fopt, ns_g_cpus); + fstrm_iothr_options_set_queue_model(fopt, + FSTRM_IOTHR_QUEUE_MODEL_MPSC); + + obj = NULL; + result = ns_config_get(maps, "fstrm-set-buffer-hint", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_buffer_hint(fopt, i); + } + + obj = NULL; + result = ns_config_get(maps, "fstrm-set-flush-timeout", &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_flush_timeout(fopt, i); + } + + obj = NULL; + result = ns_config_get(maps, "fstrm-set-input-queue-size", + &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_input_queue_size(fopt, i); + } + + obj = NULL; + result = ns_config_get(maps, + "fstrm-set-output-notify-threshold", + &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_queue_notify_threshold(fopt, + i); + } + + obj = NULL; + result = ns_config_get(maps, "fstrm-set-output-queue-model", + &obj); + if (result == ISC_R_SUCCESS) { + if (strcasecmp(cfg_obj_asstring(obj), "spsc") == 0) + i = FSTRM_IOTHR_QUEUE_MODEL_SPSC; + else + i = FSTRM_IOTHR_QUEUE_MODEL_MPSC; + fstrm_iothr_options_set_queue_model(fopt, i); + } + + obj = NULL; + result = ns_config_get(maps, "fstrm-set-output-queue-size", + &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_output_queue_size(fopt, i); + } + + obj = NULL; + result = ns_config_get(maps, "fstrm-set-reopen-interval", + &obj); + if (result == ISC_R_SUCCESS) { + i = cfg_obj_asuint32(obj); + fstrm_iothr_options_set_reopen_interval(fopt, i); + } + + CHECKM(dns_dt_create(ns_g_mctx, dmode, dpath, &fopt, + &ns_g_server->dtenv), + "unable to create dnstap environment"); + } + + if (ns_g_server->dtenv == NULL) + return (ISC_R_SUCCESS); + + obj = NULL; + result = ns_config_get(maps, "dnstap-version", &obj); + if (result != ISC_R_SUCCESS) { + /* not specified; use the product and version */ + dns_dt_setversion(ns_g_server->dtenv, PRODUCT " " VERSION); + } else if (result == ISC_R_SUCCESS && !cfg_obj_isvoid(obj)) { + /* Quoted string */ + dns_dt_setversion(ns_g_server->dtenv, cfg_obj_asstring(obj)); + } + + obj = NULL; + result = ns_config_get(maps, "dnstap-identity", &obj); + if (result == ISC_R_SUCCESS && cfg_obj_isboolean(obj)) { + /* "hostname" is interpreted as boolean true */ + char buf[256]; + result = ns_os_gethostname(buf, sizeof(buf)); + if (result == ISC_R_SUCCESS) + dns_dt_setidentity(ns_g_server->dtenv, buf); + } else if (result == ISC_R_SUCCESS && !cfg_obj_isvoid(obj)) { + /* Quoted string */ + dns_dt_setidentity(ns_g_server->dtenv, cfg_obj_asstring(obj)); + } + + dns_dt_attach(ns_g_server->dtenv, &view->dtenv); + view->dttypes = dttypes; + + result = ISC_R_SUCCESS; + + cleanup: + if (fopt != NULL) + fstrm_iothr_options_destroy(&fopt); + + return (result); +} +#endif /* HAVE_DNSTAP */ + +static isc_result_t +create_mapped_acl(void) { + isc_result_t result; + dns_acl_t *acl = NULL; + struct in6_addr in6 = IN6ADDR_V4MAPPED_INIT; + isc_netaddr_t addr; + + isc_netaddr_fromin6(&addr, &in6); + + result = dns_acl_create(ns_g_mctx, 1, &acl); + if (result != ISC_R_SUCCESS) + return (result); + + result = dns_iptable_addprefix2(acl->iptable, &addr, 96, + true, false); + if (result == ISC_R_SUCCESS) + dns_acl_attach(acl, &ns_g_mapped); + dns_acl_detach(&acl); + return (result); +} + +/* + * Configure 'view' according to 'vconfig', taking defaults from 'config' + * where values are missing in 'vconfig'. + * + * When configuring the default view, 'vconfig' will be NULL and the + * global defaults in 'config' used exclusively. + */ +static isc_result_t +configure_view(dns_view_t *view, dns_viewlist_t *viewlist, + 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, + bool 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; + const cfg_obj_t *forwarders; + const cfg_obj_t *alternates; + const cfg_obj_t *zonelist; + const cfg_obj_t *dlzlist; + const cfg_obj_t *dlz; + const cfg_obj_t *dlvobj = NULL; + unsigned int dlzargc; + char **dlzargv; + const cfg_obj_t *dyndb_list; + const cfg_obj_t *disabled; + const cfg_obj_t *obj, *obj2; + const cfg_listelt_t *element; + in_port_t port; + dns_cache_t *cache = NULL; + isc_result_t result; + unsigned int cleaning_interval; + size_t max_cache_size; + uint32_t max_cache_size_percent = 0; + size_t max_acache_size; + size_t max_adb_size; + uint32_t lame_ttl, fail_ttl; + dns_tsig_keyring_t *ring = NULL; + dns_view_t *pview = NULL; /* Production view */ + isc_mem_t *cmctx = NULL, *hmctx = NULL; + dns_dispatch_t *dispatch4 = NULL; + dns_dispatch_t *dispatch6 = NULL; + bool reused_cache = false; + bool shared_cache = false; + int i = 0, j = 0, k = 0; + const char *str; + const char *cachename = NULL; + dns_order_t *order = NULL; + uint32_t udpsize; + uint32_t maxbits; + unsigned int resopts = 0; + dns_zone_t *zone = NULL; + uint32_t max_clients_per_query; + bool empty_zones_enable; + const cfg_obj_t *disablelist = NULL; + isc_stats_t *resstats = NULL; + dns_stats_t *resquerystats = NULL; + bool auto_root = false; + ns_cache_t *nsc; + bool zero_no_soattl; + dns_acl_t *clients = NULL, *mapped = NULL, *excluded = NULL; + unsigned int query_timeout, ndisp; + bool old_rpz_ok = false; + isc_dscp_t dscp4 = -1, dscp6 = -1; + dns_dyndbctx_t *dctx = NULL; + + REQUIRE(DNS_VIEW_VALID(view)); + + if (config != NULL) + (void)cfg_map_get(config, "options", &options); + + /* + * 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) { + maps[i++] = options; + optionmaps[j++] = options; + } + + maps[i++] = ns_g_defaults; + maps[i] = NULL; + optionmaps[j] = NULL; + if (config != NULL) + cfgmaps[k++] = config; + cfgmaps[k] = NULL; + + /* + * Set the view's port number for outgoing queries. + */ + CHECKM(ns_config_getport(config, &port), "port"); + dns_view_setdstport(view, port); + + /* + * Create additional cache for this view and zones under the view + * if explicitly enabled. + * XXX950 default to on. + */ + obj = NULL; + (void)ns_config_get(maps, "acache-enable", &obj); + if (obj != NULL && cfg_obj_asboolean(obj)) { + cmctx = NULL; + CHECK(isc_mem_create(0, 0, &cmctx)); + CHECK(dns_acache_create(&view->acache, cmctx, ns_g_taskmgr, + ns_g_timermgr)); + isc_mem_setname(cmctx, "acache", NULL); + isc_mem_detach(&cmctx); + } + if (view->acache != NULL) { + obj = NULL; + result = ns_config_get(maps, "acache-cleaning-interval", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_acache_setcleaninginterval(view->acache, + cfg_obj_asuint32(obj) * 60); + + obj = NULL; + result = ns_config_get(maps, "max-acache-size", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isstring(obj)) { + str = cfg_obj_asstring(obj); + INSIST(strcasecmp(str, "unlimited") == 0); + max_acache_size = 0; + } else { + isc_resourcevalue_t value; + value = cfg_obj_asuint64(obj); + if (value > SIZE_MAX) { + cfg_obj_log(obj, ns_g_lctx, + ISC_LOG_WARNING, + "'max-acache-size " + "%" PRIu64 "' " + "is too large for this " + "system; reducing to %lu", + value, (unsigned long)SIZE_MAX); + value = SIZE_MAX; + } + max_acache_size = (size_t) value; + } + dns_acache_setcachesize(view->acache, max_acache_size); + } + + /* + * Make the list of response policy zone names for a view that + * is used for real lookups and so cares about hints. + */ + obj = NULL; + if (view->rdclass == dns_rdataclass_in && need_hints && + ns_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS) { + CHECK(configure_rpz(view, obj, &old_rpz_ok)); + } + + obj = NULL; + if (view->rdclass == dns_rdataclass_in && need_hints && + ns_config_get(maps, "catalog-zones", &obj) == ISC_R_SUCCESS) { + CHECK(configure_catz(view, config, obj)); + } + + /* + * Configure the zones. + */ + zonelist = NULL; + if (voptions != NULL) + (void)cfg_map_get(voptions, "zone", &zonelist); + else + (void)cfg_map_get(config, "zone", &zonelist); + + /* + * Load zone configuration + */ + 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, + viewlist, actx, false, old_rpz_ok, + false)); + } + + /* + * If we're allowing added zones, then load zone configuration + * from the newzone file for zones that were added during previous + * runs. + */ + CHECK(configure_newzones(view, config, vconfig, mctx, actx)); + + /* + * Create Dynamically Loadable Zone driver. + */ + dlzlist = NULL; + if (voptions != NULL) + (void)cfg_map_get(voptions, "dlz", &dlzlist); + else + (void)cfg_map_get(config, "dlz", &dlzlist); + + for (element = cfg_list_first(dlzlist); + element != NULL; + element = cfg_list_next(element)) + { + dlz = cfg_listelt_value(element); + + obj = NULL; + (void)cfg_map_get(dlz, "database", &obj); + if (obj != NULL) { + dns_dlzdb_t *dlzdb = NULL; + const cfg_obj_t *name, *search = NULL; + char *s = isc_mem_strdup(mctx, cfg_obj_asstring(obj)); + + if (s == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + result = isc_commandline_strtoargv(mctx, s, &dlzargc, + &dlzargv, 0); + if (result != ISC_R_SUCCESS) { + isc_mem_free(mctx, s); + goto cleanup; + } + + name = cfg_map_getname(dlz); + result = dns_dlzcreate(mctx, cfg_obj_asstring(name), + dlzargv[0], dlzargc, dlzargv, + &dlzdb); + isc_mem_free(mctx, s); + isc_mem_put(mctx, dlzargv, dlzargc * sizeof(*dlzargv)); + if (result != ISC_R_SUCCESS) + goto cleanup; + + /* + * If the DLZ backend supports configuration, + * and is searchable, then call its configure + * method now. If not searchable, we'll take + * care of it when we process the zone statement. + */ + (void)cfg_map_get(dlz, "search", &search); + if (search == NULL || cfg_obj_asboolean(search)) { + dlzdb->search = true; + result = dns_dlzconfigure(view, dlzdb, + dlzconfigure_callback); + if (result != ISC_R_SUCCESS) + goto cleanup; + ISC_LIST_APPEND(view->dlz_searched, + dlzdb, link); + } else { + dlzdb->search = false; + ISC_LIST_APPEND(view->dlz_unsearched, + dlzdb, link); + } + + } + } + + /* + * 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 = 0; + } else if (cfg_obj_ispercentage(obj)) { + max_cache_size = SIZE_AS_PERCENT; + max_cache_size_percent = cfg_obj_aspercentage(obj); + } else { + isc_resourcevalue_t value; + value = cfg_obj_asuint64(obj); + if (value > SIZE_MAX) { + cfg_obj_log(obj, ns_g_lctx, + ISC_LOG_WARNING, + "'max-cache-size " + "%" PRIu64 "' " + "is too large for this " + "system; reducing to %lu", + value, (unsigned long)SIZE_MAX); + value = SIZE_MAX; + } + max_cache_size = (size_t) value; + } + + if (max_cache_size == SIZE_AS_PERCENT) { + uint64_t totalphys = isc_meminfo_totalphys(); + + max_cache_size = + (size_t) (totalphys * max_cache_size_percent/100); + if (totalphys == 0) { + cfg_obj_log(obj, ns_g_lctx, + ISC_LOG_WARNING, + "Unable to determine amount of physical " + "memory, setting 'max-cache-size' to " + "unlimited"); + } else { + cfg_obj_log(obj, ns_g_lctx, + ISC_LOG_INFO, + "'max-cache-size %d%%' " + "- setting to %" PRIu64 "MB " + "(out of %" PRIu64 "MB)", + max_cache_size_percent, + (uint64_t)(max_cache_size / (1024*1024)), + totalphys / (1024*1024)); + } + } + + /* Check-names. */ + obj = NULL; + result = ns_checknames_get(maps, "response", &obj); + INSIST(result == ISC_R_SUCCESS); + + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "fail") == 0) { + resopts |= DNS_RESOLVER_CHECKNAMES | + DNS_RESOLVER_CHECKNAMESFAIL; + view->checknames = true; + } else if (strcasecmp(str, "warn") == 0) { + resopts |= DNS_RESOLVER_CHECKNAMES; + view->checknames = false; + } else if (strcasecmp(str, "ignore") == 0) { + view->checknames = false; + } else + INSIST(0); + + obj = NULL; + result = ns_config_get(maps, "zero-no-soa-ttl-cache", &obj); + INSIST(result == ISC_R_SUCCESS); + 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")) { + 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; + } else { + if (ns_g_mapped == NULL) { + result = create_mapped_acl(); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + dns_acl_attach(ns_g_mapped, &excluded); + } + + 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); + + obj = NULL; + result = ns_config_get(maps, "dnssec-validation", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isboolean(obj)) { + view->enablevalidation = cfg_obj_asboolean(obj); + } else { + /* If dnssec-validation is not boolean, it must be "auto" */ + view->enablevalidation = true; + auto_root = true; + } + + obj = NULL; + result = ns_config_get(maps, "max-cache-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->maxcachettl = cfg_obj_asuint32(obj); + + obj = NULL; + result = ns_config_get(maps, "max-ncache-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->maxncachettl = cfg_obj_asuint32(obj); + if (view->maxncachettl > 7 * 24 * 3600) + view->maxncachettl = 7 * 24 * 3600; + + /* + * 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. + * + * 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 + * 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. + */ + 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, view->rdclass); + 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_ERROR, + "views %s and %s can't share the cache " + "due to configuration parameter mismatch", + nsc->primaryview->name, view->name); + result = ISC_R_FAILURE; + goto cleanup; + } + dns_cache_attach(nsc->cache, &cache); + shared_cache = 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 = 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. + * + * We use two separate memory contexts for the + * cache, for the main cache memory and the heap + * memory. + */ + CHECK(isc_mem_create(0, 0, &cmctx)); + isc_mem_setname(cmctx, "cache", NULL); + CHECK(isc_mem_create(0, 0, &hmctx)); + isc_mem_setname(hmctx, "cache_heap", NULL); + CHECK(dns_cache_create3(cmctx, hmctx, ns_g_taskmgr, + ns_g_timermgr, view->rdclass, + cachename, "rbt", 0, NULL, + &cache)); + isc_mem_detach(&cmctx); + isc_mem_detach(&hmctx); + } + 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 = false; + nsc->adbsizeadjusted = false; + nsc->rdclass = view->rdclass; + ISC_LINK_INIT(nsc, link); + ISC_LIST_APPEND(*cachelist, nsc, link); + } + dns_view_setcache2(view, cache, shared_cache); + + /* + * cache-file cannot be inherited if views are present, but this + * should be caught by the configuration checking stage. + */ + obj = NULL; + 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 && !shared_cache) + CHECK(dns_cache_load(cache)); + } + + dns_cache_setcleaninginterval(cache, cleaning_interval); + dns_cache_setcachesize(cache, max_cache_size); + + dns_cache_detach(&cache); + + /* + * Resolver. + * + * XXXRTH Hardwired number of tasks. + */ + CHECK(get_view_querysource_dispatch(maps, AF_INET, &dispatch4, &dscp4, + (ISC_LIST_PREV(view, link) + == NULL))); + CHECK(get_view_querysource_dispatch(maps, AF_INET6, &dispatch6, &dscp6, + (ISC_LIST_PREV(view, link) + == NULL))); + if (dispatch4 == NULL && dispatch6 == NULL) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "unable to obtain neither an IPv4 nor" + " an IPv6 dispatch"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + if (resstats == NULL) { + CHECK(isc_stats_create(mctx, &resstats, + dns_resstatscounter_max)); + } + dns_view_setresstats(view, resstats); + if (resquerystats == NULL) + CHECK(dns_rdatatypestats_create(mctx, &resquerystats)); + dns_view_setresquerystats(view, resquerystats); + + ndisp = 4 * ISC_MIN(ns_g_udpdisp, MAX_UDP_DISPATCH); + CHECK(dns_view_createresolver(view, ns_g_taskmgr, RESOLVER_NTASKS, + ndisp, ns_g_socketmgr, ns_g_timermgr, + resopts, ns_g_dispatchmgr, + dispatch4, dispatch6)); + + if (dscp4 == -1) + dscp4 = ns_g_dscp; + if (dscp6 == -1) + dscp6 = ns_g_dscp; + if (dscp4 != -1) + dns_resolver_setquerydscp4(view->resolver, dscp4); + if (dscp6 != -1) + dns_resolver_setquerydscp6(view->resolver, dscp6); + + /* + * 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 != 0U) { + max_adb_size = max_cache_size / 8; + if (max_adb_size == 0U) + 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 = true; + } + } + } + dns_adb_setadbsize(view->adb, max_adb_size); + + /* + * Set up ADB quotas + */ + { + uint32_t fps, freq; + double low, high, discount; + + obj = NULL; + result = ns_config_get(maps, "fetches-per-server", &obj); + INSIST(result == ISC_R_SUCCESS); + obj2 = cfg_tuple_get(obj, "fetches"); + fps = cfg_obj_asuint32(obj2); + obj2 = cfg_tuple_get(obj, "response"); + if (!cfg_obj_isvoid(obj2)) { + const char *resp = cfg_obj_asstring(obj2); + isc_result_t r; + + if (strcasecmp(resp, "drop") == 0) + r = DNS_R_DROP; + else if (strcasecmp(resp, "fail") == 0) + r = DNS_R_SERVFAIL; + else + INSIST(0); + + dns_resolver_setquotaresponse(view->resolver, + dns_quotatype_server, r); + } + + obj = NULL; + result = ns_config_get(maps, "fetch-quota-params", &obj); + INSIST(result == ISC_R_SUCCESS); + + obj2 = cfg_tuple_get(obj, "frequency"); + freq = cfg_obj_asuint32(obj2); + + obj2 = cfg_tuple_get(obj, "low"); + low = (double) cfg_obj_asfixedpoint(obj2) / 100.0; + + obj2 = cfg_tuple_get(obj, "high"); + high = (double) cfg_obj_asfixedpoint(obj2) / 100.0; + + obj2 = cfg_tuple_get(obj, "discount"); + discount = (double) cfg_obj_asfixedpoint(obj2) / 100.0; + + dns_adb_setquota(view->adb, fps, freq, low, high, discount); + } + + /* + * Set resolver's lame-ttl. + */ + obj = NULL; + result = ns_config_get(maps, "lame-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + lame_ttl = cfg_obj_asuint32(obj); + if (lame_ttl > 1800) + lame_ttl = 1800; + 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; + result = ns_config_get(maps, "edns-udp-size", &obj); + INSIST(result == ISC_R_SUCCESS); + udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512) + udpsize = 512; + if (udpsize > 4096) + udpsize = 4096; + dns_resolver_setudpsize(view->resolver, (uint16_t)udpsize); + + /* + * Set the maximum UDP response size. + */ + obj = NULL; + result = ns_config_get(maps, "max-udp-size", &obj); + INSIST(result == ISC_R_SUCCESS); + udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512) + udpsize = 512; + if (udpsize > 4096) + udpsize = 4096; + view->maxudp = udpsize; + + /* + * Set the maximum UDP when a COOKIE is not provided. + */ + obj = NULL; + result = ns_config_get(maps, "nocookie-udp-size", &obj); + INSIST(result == ISC_R_SUCCESS); + udpsize = cfg_obj_asuint32(obj); + if (udpsize < 128) + udpsize = 128; + if (udpsize > view->maxudp) + udpsize = view->maxudp; + view->nocookieudp = udpsize; + + /* + * Set the maximum rsa exponent bits. + */ + obj = NULL; + result = ns_config_get(maps, "max-rsa-exponent-size", &obj); + INSIST(result == ISC_R_SUCCESS); + maxbits = cfg_obj_asuint32(obj); + if (maxbits != 0 && maxbits < 35) + maxbits = 35; + if (maxbits > 4096) + maxbits = 4096; + view->maxbits = maxbits; + + /* + * Set supported DNSSEC algorithms. + */ + dns_resolver_reset_algorithms(view->resolver); + disabled = NULL; + (void)ns_config_get(maps, "disable-algorithms", &disabled); + if (disabled != NULL) { + for (element = cfg_list_first(disabled); + element != NULL; + element = cfg_list_next(element)) + CHECK(disable_algorithms(cfg_listelt_value(element), + view->resolver)); + } + + /* + * Set supported DS/DLV digest types. + */ + dns_resolver_reset_ds_digests(view->resolver); + disabled = NULL; + (void)ns_config_get(maps, "disable-ds-digests", &disabled); + if (disabled != NULL) { + for (element = cfg_list_first(disabled); + element != NULL; + element = cfg_list_next(element)) + CHECK(disable_ds_digests(cfg_listelt_value(element), + view->resolver)); + } + + /* + * A global or view "forwarders" option, if present, + * creates an entry for "." in the forwarding table. + */ + forwardtype = NULL; + forwarders = NULL; + (void)ns_config_get(maps, "forward", &forwardtype); + (void)ns_config_get(maps, "forwarders", &forwarders); + if (forwarders != NULL) + CHECK(configure_forward(config, view, dns_rootname, + forwarders, forwardtype)); + + /* + * Dual Stack Servers. + */ + alternates = NULL; + (void)ns_config_get(maps, "dual-stack-servers", &alternates); + if (alternates != NULL) + CHECK(configure_alternates(config, view, alternates)); + + /* + * We have default hints for class IN if we need them. + */ + if (view->rdclass == dns_rdataclass_in && view->hints == NULL) + dns_view_sethints(view, ns_g_server->in_roothints); + + /* + * If we still have no hints, this is a non-IN view with no + * "hints zone" configured. Issue a warning, except if this + * is a root server. Root servers never need to consult + * their hints, so it's no point requiring users to configure + * them. + */ + if (view->hints == NULL) { + dns_zone_t *rootzone = NULL; + (void)dns_view_findzone(view, dns_rootname, &rootzone); + if (rootzone != NULL) { + dns_zone_detach(&rootzone); + need_hints = false; + } + if (need_hints) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "no root hints for view '%s'", + view->name); + } + + /* + * Configure the view's TSIG keys. + */ + 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. + */ + { + const cfg_obj_t *peers = NULL; + dns_peerlist_t *newpeers = NULL; + + (void)ns_config_get(cfgmaps, "server", &peers); + CHECK(dns_peerlist_new(mctx, &newpeers)); + for (element = cfg_list_first(peers); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *cpeer = cfg_listelt_value(element); + dns_peer_t *peer; + + CHECK(configure_peer(cpeer, mctx, &peer)); + dns_peerlist_addpeer(newpeers, peer); + dns_peer_detach(&peer); + } + dns_peerlist_detach(&view->peers); + view->peers = newpeers; /* Transfer ownership. */ + } + + /* + * Configure the views rrset-order. + */ + { + const cfg_obj_t *rrsetorder = NULL; + + (void)ns_config_get(maps, "rrset-order", &rrsetorder); + CHECK(dns_order_create(mctx, &order)); + for (element = cfg_list_first(rrsetorder); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *ent = cfg_listelt_value(element); + + CHECK(configure_order(order, ent)); + } + if (view->order != NULL) + dns_order_detach(&view->order); + dns_order_attach(order, &view->order); + dns_order_detach(&order); + } + /* + * Copy the aclenv object. + */ + dns_aclenv_copy(&view->aclenv, &ns_g_server->aclenv); + + /* + * Configure the "match-clients" and "match-destinations" ACL. + * (These are only meaningful at the view level, but 'config' + * must be passed so that named ACLs defined at the global level + * can be retrieved.) + */ + CHECK(configure_view_acl(vconfig, config, NULL, "match-clients", + NULL, actx, ns_g_mctx, + &view->matchclients)); + CHECK(configure_view_acl(vconfig, config, NULL, "match-destinations", + NULL, actx, ns_g_mctx, + &view->matchdestinations)); + + /* + * Configure the "match-recursive-only" option. + */ + obj = NULL; + (void)ns_config_get(maps, "match-recursive-only", &obj); + if (obj != NULL && cfg_obj_asboolean(obj)) + view->matchrecursiveonly = true; + else + view->matchrecursiveonly = false; + + /* + * Configure other configurable data. + */ + obj = NULL; + result = ns_config_get(maps, "recursion", &obj); + INSIST(result == ISC_R_SUCCESS); + view->recursion = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "auth-nxdomain", &obj); + INSIST(result == ISC_R_SUCCESS); + view->auth_nxdomain = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "minimal-any", &obj); + INSIST(result == ISC_R_SUCCESS); + view->minimal_any = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "minimal-responses", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) + view->minimalresponses = dns_minimal_yes; + else + view->minimalresponses = dns_minimal_no; + } else { + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "no-auth") == 0) { + view->minimalresponses = dns_minimal_noauth; + } else if (strcasecmp(str, "no-auth-recursive") == 0) { + view->minimalresponses = dns_minimal_noauthrec; + } else + INSIST(0); + } + + obj = NULL; + result = ns_config_get(maps, "transfer-format", &obj); + INSIST(result == ISC_R_SUCCESS); + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "many-answers") == 0) + view->transfer_format = dns_many_answers; + else if (strcasecmp(str, "one-answer") == 0) + view->transfer_format = dns_one_answer; + else + INSIST(0); + + obj = NULL; + result = ns_config_get(maps, "trust-anchor-telemetry", &obj); + INSIST(result == ISC_R_SUCCESS); + view->trust_anchor_telemetry = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "root-key-sentinel", &obj); + INSIST(result == ISC_R_SUCCESS); + view->root_key_sentinel = cfg_obj_asboolean(obj); + + /* + * Set sources where additional data and CNAME/DNAME + * targets for authoritative answers may be found. + */ + obj = NULL; + result = ns_config_get(maps, "additional-from-auth", &obj); + INSIST(result == ISC_R_SUCCESS); + view->additionalfromauth = cfg_obj_asboolean(obj); + if (view->recursion && ! view->additionalfromauth) { + cfg_obj_log(obj, ns_g_lctx, ISC_LOG_WARNING, + "'additional-from-auth no' is only supported " + "with 'recursion no'"); + view->additionalfromauth = true; + } + + obj = NULL; + result = ns_config_get(maps, "additional-from-cache", &obj); + INSIST(result == ISC_R_SUCCESS); + view->additionalfromcache = cfg_obj_asboolean(obj); + if (view->recursion && ! view->additionalfromcache) { + cfg_obj_log(obj, ns_g_lctx, ISC_LOG_WARNING, + "'additional-from-cache no' is only supported " + "with 'recursion no'"); + view->additionalfromcache = true; + } + + CHECK(configure_view_acl(vconfig, config, ns_g_config, + "allow-query-cache-on", NULL, actx, + ns_g_mctx, &view->cacheonacl)); + + /* + * Set the "allow-query", "allow-query-cache", "allow-recursion", + * and "allow-recursion-on" ACLs if configured in named.conf, but + * NOT from the global defaults. This is done by leaving the third + * argument to configure_view_acl() NULL. + * + * We ignore the global defaults here because these ACLs + * can inherit from each other. If any are still unset after + * applying the inheritance rules, we'll look up the defaults at + * that time. + */ + + /* named.conf only */ + CHECK(configure_view_acl(vconfig, config, NULL, + "allow-query", NULL, actx, + ns_g_mctx, &view->queryacl)); + + /* named.conf only */ + CHECK(configure_view_acl(vconfig, config, NULL, + "allow-query-cache", NULL, actx, + ns_g_mctx, &view->cacheacl)); + + if (strcmp(view->name, "_bind") != 0 && + view->rdclass != dns_rdataclass_chaos) + { + /* named.conf only */ + CHECK(configure_view_acl(vconfig, config, NULL, + "allow-recursion", NULL, actx, + ns_g_mctx, &view->recursionacl)); + /* named.conf only */ + CHECK(configure_view_acl(vconfig, config, NULL, + "allow-recursion-on", NULL, actx, + ns_g_mctx, &view->recursiononacl)); + } + + if (view->recursion) { + /* + * "allow-query-cache" inherits from "allow-recursion" if set, + * otherwise from "allow-query" if set. + * "allow-recursion" inherits from "allow-query-cache" if set, + * otherwise from "allow-query" if set. + */ + if (view->cacheacl == NULL) { + if (view->recursionacl != NULL) { + dns_acl_attach(view->recursionacl, + &view->cacheacl); + } else if (view->queryacl != NULL) { + dns_acl_attach(view->queryacl, + &view->cacheacl); + } + } + if (view->recursionacl == NULL) { + if (view->cacheacl != NULL) { + dns_acl_attach(view->cacheacl, + &view->recursionacl); + } else if (view->queryacl != NULL) { + dns_acl_attach(view->queryacl, + &view->recursionacl); + } + } + + /* + * If any are still unset, we now get default "allow-recursion", + * "allow-recursion-on" and "allow-query-cache" ACLs from + * the global config. + */ + if (view->recursionacl == NULL) { + /* global default only */ + CHECK(configure_view_acl(NULL, NULL, ns_g_config, + "allow-recursion", NULL, + actx, ns_g_mctx, + &view->recursionacl)); + } + if (view->recursiononacl == NULL) { + /* global default only */ + CHECK(configure_view_acl(NULL, NULL, ns_g_config, + "allow-recursion-on", NULL, + actx, ns_g_mctx, + &view->recursiononacl)); + } + if (view->cacheacl == NULL) { + /* global default only */ + CHECK(configure_view_acl(NULL, NULL, ns_g_config, + "allow-query-cache", NULL, + actx, ns_g_mctx, + &view->cacheacl)); + } + } else if (view->cacheacl == NULL) { + /* + * We're not recursive; if "allow-query-cache" hasn't been + * set at the options/view level, set it to none. + */ + CHECK(dns_acl_none(mctx, &view->cacheacl)); + } + + if (view->queryacl == NULL) { + /* global default only */ + CHECK(configure_view_acl(NULL, NULL, ns_g_config, + "allow-query", NULL, + actx, ns_g_mctx, + &view->queryacl)); + } + + /* + * Ignore case when compressing responses to the specified + * clients. This causes case not always to be preserved, + * and is needed by some broken clients. + */ + CHECK(configure_view_acl(vconfig, config, ns_g_config, + "no-case-compress", NULL, actx, + ns_g_mctx, &view->nocasecompress)); + + /* + * Disable name compression completely, this is a tradeoff + * between CPU and network usage. + */ + obj = NULL; + result = ns_config_get(maps, "message-compression", &obj); + INSIST(result == ISC_R_SUCCESS); + view->msgcompression = cfg_obj_asboolean(obj); + + /* + * Filter setting on addresses in the answer section. + */ + CHECK(configure_view_acl(vconfig, config, ns_g_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, + &view->sortlist)); + + /* + * Configure default allow-notify, allow-update + * and allow-update-forwarding ACLs, so they can be + * inherited by zones. (Note these cannot be set at + * options/view level.) + */ + if (view->notifyacl == NULL) { + CHECK(configure_view_acl(vconfig, config, ns_g_config, + "allow-notify", NULL, actx, + ns_g_mctx, &view->notifyacl)); + } + if (view->updateacl == NULL) { + CHECK(configure_view_acl(NULL, NULL, ns_g_config, + "allow-update", NULL, actx, + ns_g_mctx, &view->updateacl)); + } + if (view->upfwdacl == NULL) { + CHECK(configure_view_acl(NULL, NULL, ns_g_config, + "allow-update-forwarding", NULL, actx, + ns_g_mctx, &view->upfwdacl)); + } + + /* + * Configure default allow-transer ACL so it can be inherited + * by zones. (Note this *can* be set at options or view level.) + */ + if (view->transferacl == NULL) { + CHECK(configure_view_acl(vconfig, config, ns_g_config, + "allow-transfer", NULL, actx, + ns_g_mctx, &view->transferacl)); + } + + obj = NULL; + result = ns_config_get(maps, "provide-ixfr", &obj); + INSIST(result == ISC_R_SUCCESS); + view->provideixfr = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "request-nsid", &obj); + INSIST(result == ISC_R_SUCCESS); + view->requestnsid = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "send-cookie", &obj); + INSIST(result == ISC_R_SUCCESS); + view->sendcookie = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "require-server-cookie", &obj); + INSIST(result == ISC_R_SUCCESS); + view->requireservercookie = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "v6-bias", &obj); + INSIST(result == ISC_R_SUCCESS); + view->v6bias = cfg_obj_asuint32(obj) * 1000; + + obj = NULL; + result = ns_config_get(maps, "max-clients-per-query", &obj); + INSIST(result == ISC_R_SUCCESS); + max_clients_per_query = cfg_obj_asuint32(obj); + + obj = NULL; + result = ns_config_get(maps, "clients-per-query", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_resolver_setclientsperquery(view->resolver, + cfg_obj_asuint32(obj), + max_clients_per_query); + + obj = NULL; + result = ns_config_get(maps, "max-recursion-depth", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_resolver_setmaxdepth(view->resolver, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "max-recursion-queries", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_resolver_setmaxqueries(view->resolver, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "fetches-per-zone", &obj); + INSIST(result == ISC_R_SUCCESS); + obj2 = cfg_tuple_get(obj, "fetches"); + dns_resolver_setfetchesperzone(view->resolver, cfg_obj_asuint32(obj2)); + obj2 = cfg_tuple_get(obj, "response"); + if (!cfg_obj_isvoid(obj2)) { + const char *resp = cfg_obj_asstring(obj2); + isc_result_t r; + + if (strcasecmp(resp, "drop") == 0) + r = DNS_R_DROP; + else if (strcasecmp(resp, "fail") == 0) + r = DNS_R_SERVFAIL; + else + INSIST(0); + + dns_resolver_setquotaresponse(view->resolver, + dns_quotatype_zone, r); + } + +#ifdef ALLOW_FILTER_AAAA + 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_aaaa_filter; + else + view->v4_aaaa = dns_aaaa_ok; + } else { + const char *v4_aaaastr = cfg_obj_asstring(obj); + if (strcasecmp(v4_aaaastr, "break-dnssec") == 0) + view->v4_aaaa = dns_aaaa_break_dnssec; + else + INSIST(0); + } + + obj = NULL; + result = ns_config_get(maps, "filter-aaaa-on-v6", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) + view->v6_aaaa = dns_aaaa_filter; + else + view->v6_aaaa = dns_aaaa_ok; + } else { + const char *v6_aaaastr = cfg_obj_asstring(obj); + if (strcasecmp(v6_aaaastr, "break-dnssec") == 0) + view->v6_aaaa = dns_aaaa_break_dnssec; + else + INSIST(0); + } + + CHECK(configure_view_acl(vconfig, config, ns_g_config, + "filter-aaaa", NULL, actx, + ns_g_mctx, &view->aaaa_acl)); +#endif + obj = NULL; + result = ns_config_get(maps, "prefetch", &obj); + if (result == ISC_R_SUCCESS) { + const cfg_obj_t *trigger, *eligible; + + trigger = cfg_tuple_get(obj, "trigger"); + view->prefetch_trigger = cfg_obj_asuint32(trigger); + if (view->prefetch_trigger > 10) + view->prefetch_trigger = 10; + eligible = cfg_tuple_get(obj, "eligible"); + if (cfg_obj_isvoid(eligible)) { + int m; + for (m = 1; maps[m] != NULL; m++) { + obj = NULL; + result = ns_config_get(&maps[m], + "prefetch", &obj); + INSIST(result == ISC_R_SUCCESS); + eligible = cfg_tuple_get(obj, "eligible"); + if (cfg_obj_isuint32(eligible)) + break; + } + INSIST(cfg_obj_isuint32(eligible)); + } + view->prefetch_eligible = cfg_obj_asuint32(eligible); + if (view->prefetch_eligible < view->prefetch_trigger + 6) + view->prefetch_eligible = view->prefetch_trigger + 6; + } + + 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(optionmaps, "dnssec-lookaside", &obj); + if (result == ISC_R_SUCCESS) { + /* "auto" is deprecated, log a warning if seen */ + const char *dom; + dlvobj = cfg_listelt_value(cfg_list_first(obj)); + dom = cfg_obj_asstring(cfg_tuple_get(dlvobj, "domain")); + if (cfg_obj_isvoid(cfg_tuple_get(dlvobj, "trust-anchor"))) { + /* If "no", skip; if "auto", log warning */ + if (!strcasecmp(dom, "no")) { + result = ISC_R_NOTFOUND; + } else if (!strcasecmp(dom, "auto")) { + /* + * Warning logged by libbind9. + */ + result = ISC_R_NOTFOUND; + } + } + } + + if (result == ISC_R_SUCCESS) { + dns_name_t *dlv, *iscdlv; + dns_fixedname_t f; + + /* Also log a warning if manually configured to dlv.isc.org */ + iscdlv = dns_fixedname_initname(&f); + CHECK(dns_name_fromstring(iscdlv, "dlv.isc.org", 0, NULL)); + + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + obj = cfg_tuple_get(obj, "trust-anchor"); + + dlv = dns_fixedname_name(&view->dlv_fixed); + CHECK(dns_name_fromstring(dlv, cfg_obj_asstring(obj), + DNS_NAME_DOWNCASE, NULL)); + if (dns_name_equal(dlv, iscdlv)) { + /* + * Warning logged by libbind9. + */ + view->dlv = NULL; + } else { + view->dlv = dlv; + } + } + } else { + view->dlv = NULL; + } + + /* + * For now, there is only one kind of trusted keys, the + * "security roots". + */ + CHECK(configure_view_dnsseckeys(view, vconfig, config, bindkeys, + auto_root, mctx)); + dns_resolver_resetmustbesecure(view->resolver); + obj = NULL; + result = ns_config_get(maps, "dnssec-must-be-secure", &obj); + if (result == ISC_R_SUCCESS) + CHECK(mustbesecure(obj, view->resolver)); + + obj = NULL; + result = ns_config_get(maps, "nta-recheck", &obj); + INSIST(result == ISC_R_SUCCESS); + view->nta_recheck = cfg_obj_asuint32(obj); + + obj = NULL; + result = ns_config_get(maps, "nta-lifetime", &obj); + INSIST(result == ISC_R_SUCCESS); + view->nta_lifetime = cfg_obj_asuint32(obj); + + obj = NULL; + result = ns_config_get(maps, "preferred-glue", &obj); + if (result == ISC_R_SUCCESS) { + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "a") == 0) + view->preferred_glue = dns_rdatatype_a; + else if (strcasecmp(str, "aaaa") == 0) + view->preferred_glue = dns_rdatatype_aaaa; + else + view->preferred_glue = 0; + } else + view->preferred_glue = 0; + + obj = NULL; + result = ns_config_get(maps, "root-delegation-only", &obj); + if (result == ISC_R_SUCCESS) + dns_view_setrootdelonly(view, true); + if (result == ISC_R_SUCCESS && ! cfg_obj_isvoid(obj)) { + const cfg_obj_t *exclude; + dns_fixedname_t fixed; + dns_name_t *name; + + name = dns_fixedname_initname(&fixed); + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + exclude = cfg_listelt_value(element); + CHECK(dns_name_fromstring(name, + cfg_obj_asstring(exclude), + 0, NULL)); + CHECK(dns_view_excludedelegationonly(view, name)); + } + } else + dns_view_setrootdelonly(view, false); + + /* + * Load DynDB modules. + */ + dyndb_list = NULL; + if (voptions != NULL) + (void)cfg_map_get(voptions, "dyndb", &dyndb_list); + else + (void)cfg_map_get(config, "dyndb", &dyndb_list); + +#ifdef HAVE_DLOPEN + for (element = cfg_list_first(dyndb_list); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *dyndb = cfg_listelt_value(element); + + if (dctx == NULL) { + const void *hashinit = isc_hash_get_initializer(); + CHECK(dns_dyndb_createctx(mctx, hashinit, + ns_g_lctx, view, + ns_g_server->zonemgr, + ns_g_server->task, + ns_g_timermgr, &dctx)); + } + + CHECK(configure_dyndb(dyndb, mctx, dctx)); + } +#endif + + /* + * Setup automatic empty zones. If recursion is off then + * they are disabled by default. + */ + obj = NULL; + (void)ns_config_get(maps, "empty-zones-enable", &obj); + (void)ns_config_get(maps, "disable-empty-zone", &disablelist); + if (obj == NULL && disablelist == NULL && + view->rdclass == dns_rdataclass_in) { + empty_zones_enable = view->recursion; + } else if (view->rdclass == dns_rdataclass_in) { + if (obj != NULL) + empty_zones_enable = cfg_obj_asboolean(obj); + else + empty_zones_enable = view->recursion; + } else { + empty_zones_enable = false; + } + + if (empty_zones_enable && !lwresd_g_useresolvconf) { + const char *empty; + int empty_zone = 0; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t buffer; + char server[DNS_NAME_FORMATSIZE + 1]; + char contact[DNS_NAME_FORMATSIZE + 1]; + const char *empty_dbtype[4] = + { "_builtin", "empty", NULL, NULL }; + int empty_dbtypec = 4; + dns_zonestat_level_t statlevel; + + name = dns_fixedname_initname(&fixed); + + obj = NULL; + result = ns_config_get(maps, "empty-server", &obj); + if (result == ISC_R_SUCCESS) { + CHECK(dns_name_fromstring(name, cfg_obj_asstring(obj), + 0, NULL)); + isc_buffer_init(&buffer, server, sizeof(server) - 1); + CHECK(dns_name_totext(name, false, &buffer)); + server[isc_buffer_usedlength(&buffer)] = 0; + empty_dbtype[2] = server; + } else + empty_dbtype[2] = "@"; + + obj = NULL; + result = ns_config_get(maps, "empty-contact", &obj); + if (result == ISC_R_SUCCESS) { + CHECK(dns_name_fromstring(name, cfg_obj_asstring(obj), + 0, NULL)); + isc_buffer_init(&buffer, contact, sizeof(contact) - 1); + CHECK(dns_name_totext(name, false, &buffer)); + contact[isc_buffer_usedlength(&buffer)] = 0; + empty_dbtype[3] = contact; + } else + empty_dbtype[3] = "."; + + obj = NULL; + result = ns_config_get(maps, "zone-statistics", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) + statlevel = dns_zonestat_full; + else + statlevel = dns_zonestat_none; + } else { + const char *levelstr = cfg_obj_asstring(obj); + if (strcasecmp(levelstr, "full") == 0) + statlevel = dns_zonestat_full; + else if (strcasecmp(levelstr, "terse") == 0) + statlevel = dns_zonestat_terse; + else if (strcasecmp(levelstr, "none") == 0) + statlevel = dns_zonestat_none; + else + INSIST(0); + } + + for (empty = empty_zones[empty_zone]; + empty != NULL; + empty = empty_zones[++empty_zone]) + { + dns_forwarders_t *dnsforwarders = NULL; + + /* + * Look for zone on drop list. + */ + CHECK(dns_name_fromstring(name, empty, 0, NULL)); + if (disablelist != NULL && + on_disable_list(disablelist, name)) + continue; + + /* + * This zone already exists. + */ + (void)dns_view_findzone(view, name, &zone); + if (zone != NULL) { + dns_zone_detach(&zone); + continue; + } + + /* + * If we would forward this name don't add a + * empty zone for it. + */ + result = dns_fwdtable_find(view->fwdtable, name, + &dnsforwarders); + if (result == ISC_R_SUCCESS && + dnsforwarders->fwdpolicy == dns_fwdpolicy_only) + continue; + + /* + * See if we can re-use a existing zone. + */ + 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) { + (void)dns_view_findzone(pview, name, &zone); + dns_view_detach(&pview); + } + + CHECK(create_empty_zone(zone, name, view, zonelist, + empty_dbtype, empty_dbtypec, + statlevel)); + if (zone != NULL) + dns_zone_detach(&zone); + } + } + + obj = NULL; + result = ns_config_get(maps, "rate-limit", &obj); + if (result == ISC_R_SUCCESS) { + result = configure_rrl(view, config, obj); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + + /* + * Set the servfail-ttl. + */ + obj = NULL; + result = ns_config_get(maps, "servfail-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + fail_ttl = cfg_obj_asuint32(obj); + if (fail_ttl > 30) + fail_ttl = 30; + dns_view_setfailttl(view, fail_ttl); + + /* + * Name space to look up redirect information in. + */ + obj = NULL; + result = ns_config_get(maps, "nxdomain-redirect", &obj); + if (result == ISC_R_SUCCESS) { + dns_name_t *name = dns_fixedname_name(&view->redirectfixed); + CHECK(dns_name_fromstring(name, cfg_obj_asstring(obj), 0, + NULL)); + view->redirectzone = name; + } else + view->redirectzone = NULL; + +#ifdef HAVE_DNSTAP + /* + * Set up the dnstap environment and configure message + * types to log. + */ + CHECK(configure_dnstap(maps, view)); +#endif /* HAVE_DNSTAP */ + + 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) + dns_dispatch_detach(&dispatch4); + if (dispatch6 != NULL) + dns_dispatch_detach(&dispatch6); + if (resstats != NULL) + isc_stats_detach(&resstats); + if (resquerystats != NULL) + dns_stats_detach(&resquerystats); + if (order != NULL) + dns_order_detach(&order); + if (cmctx != NULL) + isc_mem_detach(&cmctx); + if (hmctx != NULL) + isc_mem_detach(&hmctx); + + if (cache != NULL) + dns_cache_detach(&cache); + if (dctx != NULL) + dns_dyndb_destroyctx(&dctx); + + return (result); +} + +static isc_result_t +configure_hints(dns_view_t *view, const char *filename) { + isc_result_t result; + dns_db_t *db; + + db = NULL; + result = dns_rootns_create(view->mctx, view->rdclass, filename, &db); + if (result == ISC_R_SUCCESS) { + dns_view_sethints(view, db); + dns_db_detach(&db); + } + + return (result); +} + +static isc_result_t +configure_alternates(const cfg_obj_t *config, dns_view_t *view, + const cfg_obj_t *alternates) +{ + const cfg_obj_t *portobj; + const cfg_obj_t *addresses; + const cfg_listelt_t *element; + isc_result_t result = ISC_R_SUCCESS; + in_port_t port; + + /* + * Determine which port to send requests to. + */ + if (ns_g_lwresdonly && ns_g_port != 0) + port = ns_g_port; + else + CHECKM(ns_config_getport(config, &port), "port"); + + if (alternates != NULL) { + portobj = cfg_tuple_get(alternates, "port"); + if (cfg_obj_isuint32(portobj)) { + uint32_t val = cfg_obj_asuint32(portobj); + if (val > UINT16_MAX) { + cfg_obj_log(portobj, ns_g_lctx, ISC_LOG_ERROR, + "port '%u' out of range", val); + return (ISC_R_RANGE); + } + port = (in_port_t) val; + } + } + + addresses = NULL; + if (alternates != NULL) + addresses = cfg_tuple_get(alternates, "addresses"); + + for (element = cfg_list_first(addresses); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *alternate = cfg_listelt_value(element); + isc_sockaddr_t sa; + + if (!cfg_obj_issockaddr(alternate)) { + dns_fixedname_t fixed; + dns_name_t *name; + const char *str = cfg_obj_asstring(cfg_tuple_get( + alternate, "name")); + isc_buffer_t buffer; + in_port_t myport = port; + + isc_buffer_constinit(&buffer, str, strlen(str)); + isc_buffer_add(&buffer, strlen(str)); + name = dns_fixedname_initname(&fixed); + CHECK(dns_name_fromtext(name, &buffer, dns_rootname, 0, + NULL)); + + portobj = cfg_tuple_get(alternate, "port"); + if (cfg_obj_isuint32(portobj)) { + uint32_t val = cfg_obj_asuint32(portobj); + if (val > UINT16_MAX) { + cfg_obj_log(portobj, ns_g_lctx, + ISC_LOG_ERROR, + "port '%u' out of range", + val); + return (ISC_R_RANGE); + } + myport = (in_port_t) val; + } + CHECK(dns_resolver_addalternate(view->resolver, NULL, + name, myport)); + continue; + } + + sa = *cfg_obj_assockaddr(alternate); + if (isc_sockaddr_getport(&sa) == 0) + isc_sockaddr_setport(&sa, port); + CHECK(dns_resolver_addalternate(view->resolver, &sa, + NULL, 0)); + } + + cleanup: + return (result); +} + +static isc_result_t +configure_forward(const cfg_obj_t *config, dns_view_t *view, dns_name_t *origin, + const cfg_obj_t *forwarders, const cfg_obj_t *forwardtype) +{ + const cfg_obj_t *portobj, *dscpobj; + const cfg_obj_t *faddresses; + const cfg_listelt_t *element; + dns_fwdpolicy_t fwdpolicy = dns_fwdpolicy_none; + dns_forwarderlist_t fwdlist; + dns_forwarder_t *fwd; + isc_result_t result; + in_port_t port; + isc_dscp_t dscp = -1; + + ISC_LIST_INIT(fwdlist); + + /* + * Determine which port to send forwarded requests to. + */ + if (ns_g_lwresdonly && ns_g_port != 0) + port = ns_g_port; + else + CHECKM(ns_config_getport(config, &port), "port"); + + if (forwarders != NULL) { + portobj = cfg_tuple_get(forwarders, "port"); + if (cfg_obj_isuint32(portobj)) { + uint32_t val = cfg_obj_asuint32(portobj); + if (val > UINT16_MAX) { + cfg_obj_log(portobj, ns_g_lctx, ISC_LOG_ERROR, + "port '%u' out of range", val); + return (ISC_R_RANGE); + } + port = (in_port_t) val; + } + } + + /* + * DSCP value for forwarded requests. + */ + dscp = ns_g_dscp; + if (forwarders != NULL) { + dscpobj = cfg_tuple_get(forwarders, "dscp"); + if (cfg_obj_isuint32(dscpobj)) { + if (cfg_obj_asuint32(dscpobj) > 63) { + cfg_obj_log(dscpobj, ns_g_lctx, ISC_LOG_ERROR, + "dscp value '%u' is out of range", + cfg_obj_asuint32(dscpobj)); + return (ISC_R_RANGE); + } + dscp = (isc_dscp_t)cfg_obj_asuint32(dscpobj); + } + } + + faddresses = NULL; + if (forwarders != NULL) + faddresses = cfg_tuple_get(forwarders, "addresses"); + + for (element = cfg_list_first(faddresses); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *forwarder = cfg_listelt_value(element); + fwd = isc_mem_get(view->mctx, sizeof(dns_forwarder_t)); + if (fwd == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + fwd->addr = *cfg_obj_assockaddr(forwarder); + if (isc_sockaddr_getport(&fwd->addr) == 0) + isc_sockaddr_setport(&fwd->addr, port); + fwd->dscp = cfg_obj_getdscp(forwarder); + if (fwd->dscp == -1) + fwd->dscp = dscp; + ISC_LINK_INIT(fwd, link); + ISC_LIST_APPEND(fwdlist, fwd, link); + } + + if (ISC_LIST_EMPTY(fwdlist)) { + if (forwardtype != NULL) + cfg_obj_log(forwardtype, ns_g_lctx, ISC_LOG_WARNING, + "no forwarders seen; disabling " + "forwarding"); + fwdpolicy = dns_fwdpolicy_none; + } else { + if (forwardtype == NULL) + fwdpolicy = dns_fwdpolicy_first; + else { + const char *forwardstr = cfg_obj_asstring(forwardtype); + if (strcasecmp(forwardstr, "first") == 0) + fwdpolicy = dns_fwdpolicy_first; + else if (strcasecmp(forwardstr, "only") == 0) + fwdpolicy = dns_fwdpolicy_only; + else + INSIST(0); + } + } + + result = dns_fwdtable_addfwd(view->fwdtable, origin, &fwdlist, + fwdpolicy); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namebuf, sizeof(namebuf)); + cfg_obj_log(forwarders, ns_g_lctx, ISC_LOG_WARNING, + "could not set up forwarding for domain '%s': %s", + namebuf, isc_result_totext(result)); + goto cleanup; + } + + result = ISC_R_SUCCESS; + + cleanup: + + while (!ISC_LIST_EMPTY(fwdlist)) { + fwd = ISC_LIST_HEAD(fwdlist); + ISC_LIST_UNLINK(fwdlist, fwd, link); + isc_mem_put(view->mctx, fwd, sizeof(dns_forwarder_t)); + } + + return (result); +} + +static isc_result_t +get_viewinfo(const cfg_obj_t *vconfig, const char **namep, + dns_rdataclass_t *classp) +{ + isc_result_t result = ISC_R_SUCCESS; + const char *viewname; + dns_rdataclass_t viewclass; + + REQUIRE(namep != NULL && *namep == NULL); + REQUIRE(classp != NULL); + + if (vconfig != NULL) { + const cfg_obj_t *classobj = NULL; + + viewname = cfg_obj_asstring(cfg_tuple_get(vconfig, "name")); + classobj = cfg_tuple_get(vconfig, "class"); + CHECK(ns_config_getclass(classobj, dns_rdataclass_in, + &viewclass)); + if (dns_rdataclass_ismeta(viewclass)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "view '%s': class must not be meta", + viewname); + CHECK(ISC_R_FAILURE); + } + } else { + viewname = "_default"; + viewclass = dns_rdataclass_in; + } + + *namep = viewname; + *classp = viewclass; + +cleanup: + return (result); +} + +/* + * Find a view based on its configuration info and attach to it. + * + * If 'vconfig' is NULL, attach to the default view. + */ +static isc_result_t +find_view(const cfg_obj_t *vconfig, dns_viewlist_t *viewlist, + dns_view_t **viewp) +{ + isc_result_t result; + const char *viewname = NULL; + dns_rdataclass_t viewclass; + dns_view_t *view = NULL; + + result = get_viewinfo(vconfig, &viewname, &viewclass); + if (result != ISC_R_SUCCESS) + return (result); + + result = dns_viewlist_find(viewlist, viewname, viewclass, &view); + if (result != ISC_R_SUCCESS) + return (result); + + *viewp = view; + return (ISC_R_SUCCESS); +} + +/* + * Create a new view and add it to the list. + * + * If 'vconfig' is NULL, create the default view. + * + * The view created is attached to '*viewp'. + */ +static isc_result_t +create_view(const cfg_obj_t *vconfig, dns_viewlist_t *viewlist, + dns_view_t **viewp) +{ + isc_result_t result; + const char *viewname = NULL; + dns_rdataclass_t viewclass; + dns_view_t *view = NULL; + + result = get_viewinfo(vconfig, &viewname, &viewclass); + if (result != ISC_R_SUCCESS) + return (result); + + result = dns_viewlist_find(viewlist, viewname, viewclass, &view); + if (result == ISC_R_SUCCESS) + return (ISC_R_EXISTS); + if (result != ISC_R_NOTFOUND) + return (result); + INSIST(view == NULL); + + result = dns_view_create(ns_g_mctx, viewclass, viewname, &view); + if (result != ISC_R_SUCCESS) + return (result); + + result = isc_entropy_getdata(ns_g_entropy, view->secret, + sizeof(view->secret), NULL, 0); + if (result != ISC_R_SUCCESS) { + dns_view_detach(&view); + return (result); + } + +#ifdef HAVE_GEOIP + view->aclenv.geoip = ns_g_geoip; +#endif + + ISC_LIST_APPEND(*viewlist, view, link); + dns_view_attach(view, viewp); + return (ISC_R_SUCCESS); +} + +/* + * Configure or reconfigure a zone. + */ +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, + dns_viewlist_t *viewlist, cfg_aclconfctx_t *aclconf, + bool added, bool old_rpz_ok, + bool modify) +{ + dns_view_t *pview = NULL; /* Production view */ + dns_zone_t *zone = NULL; /* New or reused zone */ + dns_zone_t *raw = NULL; /* New or reused raw zone */ + dns_zone_t *dupzone = NULL; + const cfg_obj_t *options = NULL; + const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *typeobj = NULL; + const cfg_obj_t *forwarders = NULL; + const cfg_obj_t *forwardtype = NULL; + const cfg_obj_t *ixfrfromdiffs = NULL; + const cfg_obj_t *only = NULL; + const cfg_obj_t *signing = NULL; + const cfg_obj_t *viewobj = NULL; + isc_result_t result; + isc_result_t tresult; + isc_buffer_t buffer; + dns_fixedname_t fixorigin; + dns_name_t *origin; + const char *zname; + dns_rdataclass_t zclass; + const char *ztypestr; + dns_rpz_num_t rpz_num; + bool zone_is_catz = false; + + options = NULL; + (void)cfg_map_get(config, "options", &options); + + zoptions = cfg_tuple_get(zconfig, "options"); + + /* + * Get the zone origin as a dns_name_t. + */ + zname = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + isc_buffer_constinit(&buffer, zname, strlen(zname)); + isc_buffer_add(&buffer, strlen(zname)); + dns_fixedname_init(&fixorigin); + CHECK(dns_name_fromtext(dns_fixedname_name(&fixorigin), + &buffer, dns_rootname, 0, NULL)); + origin = dns_fixedname_name(&fixorigin); + + CHECK(ns_config_getclass(cfg_tuple_get(zconfig, "class"), + view->rdclass, &zclass)); + if (zclass != view->rdclass) { + const char *vname = NULL; + if (vconfig != NULL) + vname = cfg_obj_asstring(cfg_tuple_get(vconfig, + "name")); + else + vname = "<default view>"; + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "zone '%s': wrong class for view '%s'", + zname, vname); + result = ISC_R_FAILURE; + goto cleanup; + } + + (void)cfg_map_get(zoptions, "in-view", &viewobj); + if (viewobj != NULL) { + const char *inview = cfg_obj_asstring(viewobj); + dns_view_t *otherview = NULL; + + if (viewlist == NULL) { + cfg_obj_log(zconfig, ns_g_lctx, ISC_LOG_ERROR, + "'in-view' option is not permitted in " + "dynamically added zones"); + result = ISC_R_FAILURE; + goto cleanup; + } + + result = dns_viewlist_find(viewlist, inview, view->rdclass, + &otherview); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(zconfig, ns_g_lctx, ISC_LOG_ERROR, + "view '%s' is not yet defined.", inview); + result = ISC_R_FAILURE; + goto cleanup; + } + + result = dns_view_findzone(otherview, origin, &zone); + dns_view_detach(&otherview); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(zconfig, ns_g_lctx, ISC_LOG_ERROR, + "zone '%s' not defined in view '%s'", + zname, inview); + result = ISC_R_FAILURE; + goto cleanup; + } + + CHECK(dns_view_addzone(view, zone)); + dns_zone_detach(&zone); + + /* + * If the zone contains a 'forwarders' statement, configure + * selective forwarding. Note: this is not inherited from the + * other view. + */ + forwarders = NULL; + result = cfg_map_get(zoptions, "forwarders", &forwarders); + if (result == ISC_R_SUCCESS) { + forwardtype = NULL; + (void)cfg_map_get(zoptions, "forward", &forwardtype); + CHECK(configure_forward(config, view, origin, + forwarders, forwardtype)); + } + result = ISC_R_SUCCESS; + goto cleanup; + } + + (void)cfg_map_get(zoptions, "type", &typeobj); + if (typeobj == NULL) { + cfg_obj_log(zconfig, ns_g_lctx, ISC_LOG_ERROR, + "zone '%s' 'type' not specified", zname); + result = ISC_R_FAILURE; + goto cleanup; + } + ztypestr = cfg_obj_asstring(typeobj); + + /* + * "hints zones" aren't zones. If we've got one, + * configure it and return. + */ + if (strcasecmp(ztypestr, "hint") == 0) { + const cfg_obj_t *fileobj = NULL; + if (cfg_map_get(zoptions, "file", &fileobj) != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "zone '%s': 'file' not specified", + zname); + result = ISC_R_FAILURE; + goto cleanup; + } + if (dns_name_equal(origin, dns_rootname)) { + const char *hintsfile = cfg_obj_asstring(fileobj); + + CHECK(configure_hints(view, hintsfile)); + + /* + * Hint zones may also refer to delegation only points. + */ + only = NULL; + tresult = cfg_map_get(zoptions, "delegation-only", + &only); + if (tresult == ISC_R_SUCCESS && cfg_obj_asboolean(only)) + CHECK(dns_view_adddelegationonly(view, origin)); + } else { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "ignoring non-root hint zone '%s'", + zname); + result = ISC_R_SUCCESS; + } + /* Skip ordinary zone processing. */ + goto cleanup; + } + + /* + * "forward zones" aren't zones either. Translate this syntax into + * the appropriate selective forwarding configuration and return. + */ + if (strcasecmp(ztypestr, "forward") == 0) { + forwardtype = NULL; + forwarders = NULL; + + (void)cfg_map_get(zoptions, "forward", &forwardtype); + (void)cfg_map_get(zoptions, "forwarders", &forwarders); + CHECK(configure_forward(config, view, origin, forwarders, + forwardtype)); + + /* + * Forward zones may also set delegation only. + */ + only = NULL; + tresult = cfg_map_get(zoptions, "delegation-only", &only); + if (tresult == ISC_R_SUCCESS && cfg_obj_asboolean(only)) + CHECK(dns_view_adddelegationonly(view, origin)); + goto cleanup; + } + + /* + * "delegation-only zones" aren't zones either. + */ + if (strcasecmp(ztypestr, "delegation-only") == 0) { + result = dns_view_adddelegationonly(view, origin); + goto cleanup; + } + + /* + * Redirect zones only require minimal configuration. + */ + if (strcasecmp(ztypestr, "redirect") == 0) { + if (view->redirect != NULL) { + cfg_obj_log(zconfig, ns_g_lctx, ISC_LOG_ERROR, + "redirect zone already exists"); + result = ISC_R_EXISTS; + goto cleanup; + } + result = dns_viewlist_find(viewlist, view->name, + view->rdclass, &pview); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + goto cleanup; + if (pview != NULL && pview->redirect != NULL) { + dns_zone_attach(pview->redirect, &zone); + dns_zone_setview(zone, view); + } else { + CHECK(dns_zonemgr_createzone(ns_g_server->zonemgr, + &zone)); + CHECK(dns_zone_setorigin(zone, origin)); + dns_zone_setview(zone, view); + CHECK(dns_zonemgr_managezone(ns_g_server->zonemgr, + zone)); + dns_zone_setstats(zone, ns_g_server->zonestats); + } + CHECK(ns_zone_configure(config, vconfig, zconfig, aclconf, + zone, NULL)); + dns_zone_attach(zone, &view->redirect); + goto cleanup; + } + + if (!modify) { + /* + * Check for duplicates in the new zone table. + */ + result = dns_view_findzone(view, origin, &dupzone); + if (result == ISC_R_SUCCESS) { + /* + * We already have this zone! + */ + cfg_obj_log(zconfig, ns_g_lctx, ISC_LOG_ERROR, + "zone '%s' already exists", zname); + dns_zone_detach(&dupzone); + result = ISC_R_EXISTS; + goto cleanup; + } + INSIST(dupzone == NULL); + } + + /* + * Note whether this is a response policy zone and which one if so. + */ + for (rpz_num = 0; ; ++rpz_num) { + if (view->rpzs == NULL || rpz_num >= view->rpzs->p.num_zones) { + rpz_num = DNS_RPZ_INVALID_NUM; + break; + } + if (dns_name_equal(&view->rpzs->zones[rpz_num]->origin, origin)) + break; + } + + if (view->catzs != NULL && + dns_catz_get_zone(view->catzs, origin) != NULL) + zone_is_catz = true; + + /* + * See if we can reuse an existing zone. This is + * only possible if all of these are true: + * - The zone's view exists + * - A zone with the right name exists in the view + * - The zone is compatible with the config + * options (e.g., an existing master zone cannot + * be reused if the options specify a slave zone) + * - The zone was not and is still not a response policy zone + * or the zone is a policy zone with an unchanged number + * and we are using the old policy zone summary data. + */ + 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) + result = dns_view_findzone(pview, origin, &zone); + if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) + goto cleanup; + + if (zone != NULL && !ns_zone_reusable(zone, zconfig)) + dns_zone_detach(&zone); + + if (zone != NULL && (rpz_num != dns_zone_get_rpz_num(zone) || + (rpz_num != DNS_RPZ_INVALID_NUM && !old_rpz_ok))) + dns_zone_detach(&zone); + + if (zone != NULL) { + /* + * We found a reusable zone. Make it use the + * new view. + */ + dns_zone_setview(zone, view); + if (view->acache != NULL) + dns_zone_setacache(zone, view->acache); + } else { + /* + * We cannot reuse an existing zone, we have + * to create a new one. + */ + CHECK(dns_zonemgr_createzone(ns_g_server->zonemgr, &zone)); + CHECK(dns_zone_setorigin(zone, origin)); + dns_zone_setview(zone, view); + if (view->acache != NULL) + dns_zone_setacache(zone, view->acache); + CHECK(dns_zonemgr_managezone(ns_g_server->zonemgr, zone)); + dns_zone_setstats(zone, ns_g_server->zonestats); + } + if (rpz_num != DNS_RPZ_INVALID_NUM) { + result = dns_zone_rpz_enable(zone, view->rpzs, rpz_num); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "zone '%s': incompatible" + " masterfile-format or database" + " for a response policy zone", + zname); + goto cleanup; + } + } + + if (zone_is_catz) + dns_zone_catz_enable(zone, view->catzs); + + /* + * If the zone contains a 'forwarders' statement, configure + * selective forwarding. + */ + forwarders = NULL; + if (cfg_map_get(zoptions, "forwarders", &forwarders) == ISC_R_SUCCESS) + { + forwardtype = NULL; + (void)cfg_map_get(zoptions, "forward", &forwardtype); + CHECK(configure_forward(config, view, origin, forwarders, + forwardtype)); + } + + /* + * Stub and forward zones may also refer to delegation only points. + */ + only = NULL; + if (cfg_map_get(zoptions, "delegation-only", &only) == ISC_R_SUCCESS) + { + if (cfg_obj_asboolean(only)) + CHECK(dns_view_adddelegationonly(view, origin)); + } + + /* + * Mark whether the zone was originally added at runtime or not + */ + dns_zone_setadded(zone, added); + + signing = NULL; + if ((strcasecmp(ztypestr, "master") == 0 || + strcasecmp(ztypestr, "slave") == 0) && + cfg_map_get(zoptions, "inline-signing", &signing) == ISC_R_SUCCESS && + cfg_obj_asboolean(signing)) + { + dns_zone_getraw(zone, &raw); + if (raw == NULL) { + CHECK(dns_zone_create(&raw, mctx)); + CHECK(dns_zone_setorigin(raw, origin)); + dns_zone_setview(raw, view); + if (view->acache != NULL) + dns_zone_setacache(raw, view->acache); + dns_zone_setstats(raw, ns_g_server->zonestats); + CHECK(dns_zone_link(zone, raw)); + } + if (cfg_map_get(zoptions, "ixfr-from-differences", + &ixfrfromdiffs) == ISC_R_SUCCESS) + { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "zone '%s': 'ixfr-from-differences' is " + "ignored for inline-signed zones", + zname); + } + } + + /* + * Configure the zone. + */ + CHECK(ns_zone_configure(config, vconfig, zconfig, aclconf, zone, raw)); + + /* + * Add the zone to its view in the new view list. + */ + if (!modify) + CHECK(dns_view_addzone(view, zone)); + + if (zone_is_catz) { + /* + * force catz reload if the zone is loaded; + * if it's not it'll get reloaded on zone load + */ + dns_db_t *db = NULL; + + tresult = dns_zone_getdb(zone, &db); + if (tresult == ISC_R_SUCCESS) { + dns_catz_dbupdate_callback(db, view->catzs); + dns_db_detach(&db); + } + + } + + /* + * Ensure that zone keys are reloaded on reconfig + */ + if ((dns_zone_getkeyopts(zone) & DNS_ZONEKEY_MAINTAIN) != 0) + dns_zone_rekey(zone, false); + + cleanup: + if (zone != NULL) + dns_zone_detach(&zone); + if (raw != NULL) + dns_zone_detach(&raw); + if (pview != NULL) + dns_view_detach(&pview); + + return (result); +} + +/* + * Configure built-in zone for storing managed-key data. + */ + +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]; + bool defaultview; + + 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); + dns_zone_synckeyzone(view->managed_keys); + return (ISC_R_SUCCESS); + } + + /* No existing keydata zone was found; create one */ + CHECK(dns_zonemgr_createzone(ns_g_server->zonemgr, &zone)); + CHECK(dns_zone_setorigin(zone, dns_rootname)); + + defaultview = (strcmp(view->name, "_default") == 0); + CHECK(isc_file_sanitize(directory, + defaultview ? "managed-keys" : view->name, + defaultview ? "bind" : "mkeys", + filename, sizeof(filename))); + 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, true); + dns_zone_setjournalsize(zone, 0); + + dns_zone_setstats(zone, ns_g_server->zonestats); + CHECK(setquerystats(zone, mctx, dns_zonestat_none)); + + 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 +configure_server_quota(const cfg_obj_t **maps, const char *name, + isc_quota_t *quota) +{ + const cfg_obj_t *obj = NULL; + isc_result_t result; + + result = ns_config_get(maps, name, &obj); + INSIST(result == ISC_R_SUCCESS); + isc_quota_max(quota, cfg_obj_asuint32(obj)); +} + +/* + * This function is called as soon as the 'directory' statement has been + * parsed. This can be extended to support other options if necessary. + */ +static isc_result_t +directory_callback(const char *clausename, const cfg_obj_t *obj, void *arg) { + isc_result_t result; + const char *directory; + + REQUIRE(strcasecmp("directory", clausename) == 0); + + UNUSED(arg); + UNUSED(clausename); + + /* + * Change directory. + */ + directory = cfg_obj_asstring(obj); + + if (! isc_file_ischdiridempotent(directory)) + cfg_obj_log(obj, ns_g_lctx, ISC_LOG_WARNING, + "option 'directory' contains relative path '%s'", + directory); + + result = isc_dir_chdir(directory); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR, + "change directory to '%s' failed: %s", + directory, isc_result_totext(result)); + return (result); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +scan_interfaces(ns_server_t *server, bool verbose) { + isc_result_t result; + bool match_mapped = server->aclenv.match_mapped; +#ifdef HAVE_GEOIP + bool use_ecs = server->aclenv.geoip_use_ecs; +#endif + + result = ns_interfacemgr_scan(server->interfacemgr, verbose); + /* + * Update the "localhost" and "localnets" ACLs to match the + * current set of network interfaces. + */ + dns_aclenv_copy(&server->aclenv, + ns_interfacemgr_getaclenv(server->interfacemgr)); + + server->aclenv.match_mapped = match_mapped; +#ifdef HAVE_GEOIP + server->aclenv.geoip_use_ecs = use_ecs; +#endif + + return (result); +} + +static isc_result_t +add_listenelt(isc_mem_t *mctx, ns_listenlist_t *list, isc_sockaddr_t *addr, + isc_dscp_t dscp, bool wcardport_ok) +{ + ns_listenelt_t *lelt = NULL; + dns_acl_t *src_acl = NULL; + isc_result_t result; + isc_sockaddr_t any_sa6; + isc_netaddr_t netaddr; + + REQUIRE(isc_sockaddr_pf(addr) == AF_INET6); + + isc_sockaddr_any6(&any_sa6); + if (!isc_sockaddr_equal(&any_sa6, addr) && + (wcardport_ok || isc_sockaddr_getport(addr) != 0)) { + isc_netaddr_fromin6(&netaddr, &addr->type.sin6.sin6_addr); + + result = dns_acl_create(mctx, 0, &src_acl); + if (result != ISC_R_SUCCESS) + return (result); + + result = dns_iptable_addprefix(src_acl->iptable, + &netaddr, 128, true); + if (result != ISC_R_SUCCESS) + goto clean; + + result = ns_listenelt_create(mctx, isc_sockaddr_getport(addr), + dscp, src_acl, &lelt); + if (result != ISC_R_SUCCESS) + goto clean; + ISC_LIST_APPEND(list->elts, lelt, link); + } + + return (ISC_R_SUCCESS); + + clean: + INSIST(lelt == NULL); + dns_acl_detach(&src_acl); + + return (result); +} + +/* + * Make a list of xxx-source addresses and call ns_interfacemgr_adjust() + * to update the listening interfaces accordingly. + * We currently only consider IPv6, because this only affects IPv6 wildcard + * sockets. + */ +static void +adjust_interfaces(ns_server_t *server, isc_mem_t *mctx) { + isc_result_t result; + ns_listenlist_t *list = NULL; + dns_view_t *view; + dns_zone_t *zone, *next; + isc_sockaddr_t addr, *addrp; + isc_dscp_t dscp = -1; + + result = ns_listenlist_create(mctx, &list); + if (result != ISC_R_SUCCESS) + return; + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) { + dns_dispatch_t *dispatch6; + + dispatch6 = dns_resolver_dispatchv6(view->resolver); + if (dispatch6 == NULL) + continue; + result = dns_dispatch_getlocaladdress(dispatch6, &addr); + if (result != ISC_R_SUCCESS) + goto fail; + + /* + * We always add non-wildcard address regardless of whether + * the port is 'any' (the fourth arg is TRUE): if the port is + * specific, we need to add it since it may conflict with a + * listening interface; if it's zero, we'll dynamically open + * query ports, and some of them may override an existing + * wildcard IPv6 port. + */ + /* XXXMPA fix dscp */ + result = add_listenelt(mctx, list, &addr, dscp, true); + if (result != ISC_R_SUCCESS) + goto fail; + } + + zone = NULL; + for (result = dns_zone_first(server->zonemgr, &zone); + result == ISC_R_SUCCESS; + next = NULL, result = dns_zone_next(zone, &next), zone = next) { + dns_view_t *zoneview; + + /* + * At this point the zone list may contain a stale zone + * just removed from the configuration. To see the validity, + * check if the corresponding view is in our current view list. + * There may also be old zones that are still in the process + * of shutting down and have detached from their old view + * (zoneview == NULL). + */ + zoneview = dns_zone_getview(zone); + if (zoneview == NULL) + continue; + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL && view != zoneview; + view = ISC_LIST_NEXT(view, link)) + ; + if (view == NULL) + continue; + + addrp = dns_zone_getnotifysrc6(zone); + dscp = dns_zone_getnotifysrc6dscp(zone); + result = add_listenelt(mctx, list, addrp, dscp, false); + if (result != ISC_R_SUCCESS) + goto fail; + + addrp = dns_zone_getxfrsource6(zone); + dscp = dns_zone_getxfrsource6dscp(zone); + result = add_listenelt(mctx, list, addrp, dscp, false); + if (result != ISC_R_SUCCESS) + goto fail; + } + + ns_interfacemgr_adjust(server->interfacemgr, list, true); + + clean: + ns_listenlist_detach(&list); + return; + + fail: + /* + * Even when we failed the procedure, most of other interfaces + * should work correctly. We therefore just warn it. + */ + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "could not adjust the listen-on list; " + "some interfaces may not work"); + goto clean; +} + +/* + * This event callback is invoked to do periodic network interface + * scanning. It is also called by ns_server_scan_interfaces(), + * invoked by "rndc scan" + */ + +static void +interface_timer_tick(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + ns_server_t *server = (ns_server_t *) event->ev_arg; + INSIST(task == server->task); + UNUSED(task); + + isc_event_free(&event); + + /* + * XXX should scan interfaces unlocked and get exclusive access + * only to replace ACLs. + */ + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + scan_interfaces(server, false); + isc_task_endexclusive(server->task); +} + +static void +heartbeat_timer_tick(isc_task_t *task, isc_event_t *event) { + ns_server_t *server = (ns_server_t *) event->ev_arg; + dns_view_t *view; + + UNUSED(task); + isc_event_free(&event); + view = ISC_LIST_HEAD(server->viewlist); + while (view != NULL) { + dns_view_dialup(view); + view = ISC_LIST_NEXT(view, link); + } +} + +typedef struct { + isc_mem_t *mctx; + isc_task_t *task; + dns_rdataset_t rdataset; + dns_rdataset_t sigrdataset; + dns_fetch_t *fetch; +} ns_tat_t; + +static int +cid(const void *a, const void *b) { + const uint16_t ida = *(const uint16_t *)a; + const uint16_t idb = *(const uint16_t *)b; + if (ida < idb) + return (-1); + else if (ida > idb) + return (1); + else + return (0); +} + +static void +tat_done(isc_task_t *task, isc_event_t *event) { + dns_fetchevent_t *devent; + ns_tat_t *tat; + + UNUSED(task); + INSIST(event != NULL && event->ev_type == DNS_EVENT_FETCHDONE); + INSIST(event->ev_arg != NULL); + + tat = event->ev_arg; + devent = (dns_fetchevent_t *) event; + + /* Free resources which are not of interest */ + if (devent->node != NULL) + dns_db_detachnode(devent->db, &devent->node); + if (devent->db != NULL) + dns_db_detach(&devent->db); + isc_event_free(&event); + dns_resolver_destroyfetch(&tat->fetch); + if (dns_rdataset_isassociated(&tat->rdataset)) + dns_rdataset_disassociate(&tat->rdataset); + if (dns_rdataset_isassociated(&tat->sigrdataset)) + dns_rdataset_disassociate(&tat->sigrdataset); + isc_task_detach(&tat->task); + isc_mem_putanddetach(&tat->mctx, tat, sizeof(*tat)); +} + +struct dotat_arg { + dns_view_t *view; + isc_task_t *task; +}; + +/*% + * Prepare the QNAME for the TAT query to be sent by processing the trust + * anchors present at 'keynode' of 'keytable'. Store the result in 'dst' and + * the domain name which 'keynode' is associated with in 'origin'. + * + * A maximum of 12 key IDs can be reported in a single TAT query due to the + * 63-octet length limit for any single label in a domain name. If there are + * more than 12 keys configured at 'keynode', only the first 12 will be + * reported in the TAT query. + */ +static isc_result_t +get_tat_qname(dns_name_t *dst, dns_name_t **origin, dns_keytable_t *keytable, + dns_keynode_t *keynode) +{ + dns_keynode_t *firstnode = keynode; + dns_keynode_t *nextnode; + unsigned int i, n = 0; + uint16_t ids[12]; + isc_textregion_t r; + char label[64]; + int m; + + REQUIRE(origin != NULL && *origin == NULL); + + do { + dst_key_t *key = dns_keynode_key(keynode); + if (key != NULL) { + *origin = dst_key_name(key); + if (n < (sizeof(ids)/sizeof(ids[0]))) { + ids[n] = dst_key_id(key); + n++; + } + } + nextnode = NULL; + (void)dns_keytable_nextkeynode(keytable, keynode, &nextnode); + if (keynode != firstnode) + dns_keytable_detachkeynode(keytable, &keynode); + keynode = nextnode; + } while (keynode != NULL); + + if (n == 0) { + return (DNS_R_EMPTYNAME); + } + + if (n > 1) + qsort(ids, n, sizeof(ids[0]), cid); + + /* + * Encoded as "_ta-xxxx\(-xxxx\)*" where xxxx is the hex version of + * of the keyid. + */ + label[0] = 0; + r.base = label; + r.length = sizeof(label);; + m = snprintf(r.base, r.length, "_ta"); + if (m < 0 || (unsigned)m > r.length) { + return (ISC_R_FAILURE); + } + isc_textregion_consume(&r, m); + for (i = 0; i < n; i++) { + m = snprintf(r.base, r.length, "-%04x", ids[i]); + if (m < 0 || (unsigned)m > r.length) { + return (ISC_R_FAILURE); + } + isc_textregion_consume(&r, m); + } + + return (dns_name_fromstring2(dst, label, *origin, 0, NULL)); +} + +static void +dotat(dns_keytable_t *keytable, dns_keynode_t *keynode, void *arg) { + struct dotat_arg *dotat_arg = arg; + char namebuf[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fixed, fdomain; + dns_name_t *tatname, *domain; + dns_rdataset_t nameservers; + dns_name_t *origin = NULL; + isc_result_t result; + dns_view_t *view; + isc_task_t *task; + ns_tat_t *tat; + + REQUIRE(keytable != NULL); + REQUIRE(keynode != NULL); + REQUIRE(arg != NULL); + + view = dotat_arg->view; + task = dotat_arg->task; + + tatname = dns_fixedname_initname(&fixed); + result = get_tat_qname(tatname, &origin, keytable, keynode); + if (result != ISC_R_SUCCESS) { + return; + } + + dns_name_format(tatname, namebuf, sizeof(namebuf)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, + "%s: sending trust-anchor-telemetry query '%s/NULL'", + view->name, namebuf); + + tat = isc_mem_get(dotat_arg->view->mctx, sizeof(*tat)); + if (tat == NULL) + return; + + tat->mctx = NULL; + tat->task = NULL; + tat->fetch = NULL; + dns_rdataset_init(&tat->rdataset); + dns_rdataset_init(&tat->sigrdataset); + isc_mem_attach(dotat_arg->view->mctx, &tat->mctx); + isc_task_attach(task, &tat->task); + + /* + * TAT queries should be sent to the authoritative servers for a given + * zone. If this function is called for a keytable node corresponding + * to a locally served zone, calling dns_resolver_createfetch() with + * NULL 'domain' and 'nameservers' arguments will cause 'tatname' to be + * resolved locally, without sending any TAT queries upstream. + * + * Work around this issue by calling dns_view_findzonecut() first. If + * the zone is served locally, the NS RRset for the given domain name + * will be retrieved from local data; if it is not, the deepest zone + * cut we have for it will be retrieved from cache. In either case, + * passing the results to dns_resolver_createfetch() will prevent it + * from returning NXDOMAIN for 'tatname' while still allowing it to + * chase down any potential delegations returned by upstream servers in + * order to eventually find the destination host to send the TAT query + * to. + * + * 'origin' holds the domain name at 'keynode', i.e. the domain name + * for which the trust anchors to be reported by this TAT query are + * defined. + * + * After the dns_view_findzonecut() call, 'domain' will hold the + * deepest zone cut we can find for 'origin' while 'nameservers' will + * hold the NS RRset at that zone cut. + */ + domain = dns_fixedname_initname(&fdomain); + dns_rdataset_init(&nameservers); + result = dns_view_findzonecut(view, origin, domain, 0, 0, true, + &nameservers, NULL); + if (result == ISC_R_SUCCESS) { + result = dns_resolver_createfetch(view->resolver, tatname, + dns_rdatatype_null, domain, + &nameservers, NULL, 0, + tat->task, tat_done, tat, + &tat->rdataset, + &tat->sigrdataset, + &tat->fetch); + } + + /* + * 'domain' holds the dns_name_t pointer inside a dst_key_t structure. + * dns_resolver_createfetch() creates its own copy of 'domain' if it + * succeeds. Thus, 'domain' is not freed here. + * + * Even if dns_view_findzonecut() returned something else than + * ISC_R_SUCCESS, it still could have associated 'nameservers'. + * dns_resolver_createfetch() creates its own copy of 'nameservers' if + * it succeeds. Thus, we need to check whether 'nameservers' is + * associated and release it if it is. + */ + if (dns_rdataset_isassociated(&nameservers)) { + dns_rdataset_disassociate(&nameservers); + } + + if (result != ISC_R_SUCCESS) { + isc_task_detach(&tat->task); + isc_mem_putanddetach(&tat->mctx, tat, sizeof(*tat)); + } +} + +static void +tat_timer_tick(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + ns_server_t *server = (ns_server_t *) event->ev_arg; + struct dotat_arg arg; + dns_view_t *view; + dns_keytable_t *secroots = NULL; + + isc_event_free(&event); + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (!view->trust_anchor_telemetry || + !view->enablevalidation) + { + continue; + } + + result = dns_view_getsecroots(view, &secroots); + if (result != ISC_R_SUCCESS) { + continue; + } + + arg.view = view; + arg.task = task; + (void)dns_keytable_forall(secroots, dotat, &arg); + dns_keytable_detach(&secroots); + } +} + +static void +pps_timer_tick(isc_task_t *task, isc_event_t *event) { + static unsigned int oldrequests = 0; + unsigned int requests = ns_client_requests; + + UNUSED(task); + isc_event_free(&event); + + /* + * Don't worry about wrapping as the overflow result will be right. + */ + dns_pps = (requests - oldrequests) / 1200; + oldrequests = requests; +} + +/* + * Replace the current value of '*field', a dynamically allocated + * string or NULL, with a dynamically allocated copy of the + * null-terminated string pointed to by 'value', or NULL. + */ +static isc_result_t +setstring(ns_server_t *server, char **field, const char *value) { + char *copy; + + if (value != NULL) { + copy = isc_mem_strdup(server->mctx, value); + if (copy == NULL) + return (ISC_R_NOMEMORY); + } else { + copy = NULL; + } + + if (*field != NULL) + isc_mem_free(server->mctx, *field); + + *field = copy; + return (ISC_R_SUCCESS); +} + +/* + * Replace the current value of '*field', a dynamically allocated + * string or NULL, with another dynamically allocated string + * or NULL if whether 'obj' is a string or void value, respectively. + */ +static isc_result_t +setoptstring(ns_server_t *server, char **field, const cfg_obj_t *obj) { + if (cfg_obj_isvoid(obj)) + return (setstring(server, field, NULL)); + else + return (setstring(server, field, cfg_obj_asstring(obj))); +} + +static void +set_limit(const cfg_obj_t **maps, const char *configname, + const char *description, isc_resource_t resourceid, + isc_resourcevalue_t defaultvalue) +{ + const cfg_obj_t *obj = NULL; + const char *resource; + isc_resourcevalue_t value; + isc_result_t result; + + if (ns_config_get(maps, configname, &obj) != ISC_R_SUCCESS) + return; + + if (cfg_obj_isstring(obj)) { + resource = cfg_obj_asstring(obj); + if (strcasecmp(resource, "unlimited") == 0) + value = ISC_RESOURCE_UNLIMITED; + else { + INSIST(strcasecmp(resource, "default") == 0); + value = defaultvalue; + } + } else + value = cfg_obj_asuint64(obj); + + result = isc_resource_setlimit(resourceid, value); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + result == ISC_R_SUCCESS ? + ISC_LOG_DEBUG(3) : ISC_LOG_WARNING, + "set maximum %s to %" PRIu64 ": %s", + description, value, isc_result_totext(result)); +} + +#define SETLIMIT(cfgvar, resource, description) \ + set_limit(maps, cfgvar, description, isc_resource_ ## resource, \ + ns_g_init ## resource) + +static void +set_limits(const cfg_obj_t **maps) { + SETLIMIT("stacksize", stacksize, "stack size"); + SETLIMIT("datasize", datasize, "data size"); + SETLIMIT("coresize", coresize, "core size"); + SETLIMIT("files", openfiles, "open files"); +} + +static void +portset_fromconf(isc_portset_t *portset, const cfg_obj_t *ports, + bool positive) +{ + const cfg_listelt_t *element; + + for (element = cfg_list_first(ports); + element != NULL; + element = cfg_list_next(element)) { + const cfg_obj_t *obj = cfg_listelt_value(element); + + if (cfg_obj_isuint32(obj)) { + in_port_t port = (in_port_t)cfg_obj_asuint32(obj); + + if (positive) + isc_portset_add(portset, port); + else + isc_portset_remove(portset, port); + } else { + const cfg_obj_t *obj_loport, *obj_hiport; + in_port_t loport, hiport; + + obj_loport = cfg_tuple_get(obj, "loport"); + loport = (in_port_t)cfg_obj_asuint32(obj_loport); + obj_hiport = cfg_tuple_get(obj, "hiport"); + hiport = (in_port_t)cfg_obj_asuint32(obj_hiport); + + if (positive) + isc_portset_addrange(portset, loport, hiport); + else { + isc_portset_removerange(portset, loport, + hiport); + } + } + } +} + +static isc_result_t +removed(dns_zone_t *zone, void *uap) { + const char *type; + + if (dns_zone_getview(zone) != uap) + return (ISC_R_SUCCESS); + + switch (dns_zone_gettype(zone)) { + case dns_zone_master: + type = "master"; + break; + case dns_zone_slave: + type = "slave"; + break; + case dns_zone_stub: + type = "stub"; + break; + case dns_zone_staticstub: + type = "static-stub"; + break; + case dns_zone_redirect: + type = "redirect"; + break; + default: + type = "other"; + break; + } + dns_zone_log(zone, ISC_LOG_INFO, "(%s) removed", type); + 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, + 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, + false, NULL, now, now, mctx, NULL, + &tsigkey)); + + /* Dump the key to the key file. */ + fp = ns_os_openfile(filename, S_IRUSR|S_IWUSR, 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)); + + CHECK(isc_stdio_flush(fp)); + result = isc_stdio_close(fp); + fp = NULL; + if (result != ISC_R_SUCCESS) + goto cleanup; + + 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 (fp != NULL) { + (void)isc_stdio_close(fp); + (void)isc_file_remove(filename); + } + 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; + uint16_t bits; + const cfg_obj_t *obj; + bool need_deleteold = false; + bool need_createnew = 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); + isc_buffer_constinit(&buffer, keynamestr, strlen(keynamestr)); + isc_buffer_add(&buffer, strlen(keynamestr)); + keyname = dns_fixedname_initname(&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 = true; + } else if (server->session_keyfile == NULL) + need_createnew = 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 = true; + need_createnew = 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); +} + +#ifndef HAVE_LMDB +static isc_result_t +count_newzones(dns_view_t *view, ns_cfgctx_t *nzcfg, int *num_zonesp) { + isc_result_t result; + + /* The new zone file may not exist. That is OK. */ + if (!isc_file_exists(view->new_zone_file)) { + *num_zonesp = 0; + return (ISC_R_SUCCESS); + } + + /* + * In the case of NZF files, we also parse the configuration in + * the file at this stage. + * + * This may be called in multiple views, so we reset + * the parser each time. + */ + cfg_parser_reset(ns_g_addparser); + result = cfg_parse_file(ns_g_addparser, view->new_zone_file, + &cfg_type_addzoneconf, &nzcfg->nzf_config); + if (result == ISC_R_SUCCESS) { + int num_zones; + + num_zones = count_zones(nzcfg->nzf_config); + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, + "NZF file '%s' contains %d zones", + view->new_zone_file, num_zones); + if (num_zonesp != NULL) + *num_zonesp = num_zones; + } else { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "Error parsing NZF file '%s': %s", + view->new_zone_file, + isc_result_totext(result)); + } + + return (result); +} + +#else /* HAVE_LMDB */ + +static isc_result_t +count_newzones(dns_view_t *view, ns_cfgctx_t *nzcfg, int *num_zonesp) { + isc_result_t result; + int n; + + UNUSED(nzcfg); + + REQUIRE(num_zonesp != NULL); + + CHECK(migrate_nzf(view)); + + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, "loading NZD zone count from '%s' " + "for view '%s'", + view->new_zone_db, view->name); + + CHECK(nzd_count(view, &n)); + + *num_zonesp = n; + + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, + "NZD database '%s' contains %d zones", + view->new_zone_db, n); + + cleanup: + if (result != ISC_R_SUCCESS) + *num_zonesp = 0; + + return (ISC_R_SUCCESS); +} + +#endif /* HAVE_LMDB */ + +static isc_result_t +setup_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + cfg_parser_t *conf_parser, cfg_aclconfctx_t *actx, + int *num_zones) +{ + isc_result_t result = ISC_R_SUCCESS; + bool allow = false; + ns_cfgctx_t *nzcfg = NULL; + const cfg_obj_t *maps[4]; + const cfg_obj_t *options = NULL, *voptions = NULL; + const cfg_obj_t *nz = NULL; + const cfg_obj_t *obj = NULL; + int i = 0; + uint64_t mapsize = 0ULL; + + REQUIRE(config != NULL); + + if (vconfig != NULL) + voptions = cfg_tuple_get(vconfig, "options"); + if (voptions != NULL) + maps[i++] = voptions; + result = cfg_map_get(config, "options", &options); + if (result == ISC_R_SUCCESS) + maps[i++] = options; + maps[i++] = ns_g_defaults; + maps[i] = NULL; + + result = ns_config_get(maps, "allow-new-zones", &nz); + if (result == ISC_R_SUCCESS) + allow = cfg_obj_asboolean(nz); + +#ifdef HAVE_LMDB + result = ns_config_get(maps, "lmdb-mapsize", &obj); + if (result == ISC_R_SUCCESS && obj != NULL) { + mapsize = cfg_obj_asuint64(obj); + if (mapsize < (1ULL << 20)) { /* 1 megabyte */ + cfg_obj_log(obj, ns_g_lctx, + ISC_LOG_ERROR, + "'lmdb-mapsize " + "%" PRId64 "' " + "is too small", + mapsize); + return (ISC_R_FAILURE); + } else if (mapsize > (1ULL << 40)) { /* 1 terabyte */ + cfg_obj_log(obj, ns_g_lctx, + ISC_LOG_ERROR, + "'lmdb-mapsize " + "%" PRId64 "' " + "is too large", + mapsize); + return (ISC_R_FAILURE); + } + } +#else + UNUSED(obj); +#endif /* HAVE_LMDB */ + + /* + * A non-empty catalog-zones statement implies allow-new-zones + */ + if (!allow) { + const cfg_obj_t *cz = NULL; + result = ns_config_get(maps, "catalog-zones", &cz); + if (result == ISC_R_SUCCESS) { + const cfg_listelt_t *e = + cfg_list_first(cfg_tuple_get(cz, "zone list")); + if (e != NULL) + allow = true; + } + } + + if (!allow) { + dns_view_setnewzones(view, false, NULL, NULL, 0ULL); + if (num_zones != NULL) + *num_zones = 0; + return (ISC_R_SUCCESS); + } + + nzcfg = isc_mem_get(view->mctx, sizeof(*nzcfg)); + if (nzcfg == NULL) { + dns_view_setnewzones(view, false, NULL, NULL, 0ULL); + return (ISC_R_NOMEMORY); + } + + /* + * We attach the parser that was used for config as well + * as the one that will be used for added zones, to avoid + * a shutdown race later. + */ + memset(nzcfg, 0, sizeof(*nzcfg)); + cfg_parser_attach(conf_parser, &nzcfg->conf_parser); + cfg_parser_attach(ns_g_addparser, &nzcfg->add_parser); + isc_mem_attach(view->mctx, &nzcfg->mctx); + cfg_aclconfctx_attach(actx, &nzcfg->actx); + + result = dns_view_setnewzones(view, allow, nzcfg, + newzone_cfgctx_destroy, mapsize); + if (result != ISC_R_SUCCESS) { + dns_view_setnewzones(view, false, NULL, NULL, 0ULL); + return (result); + } + + cfg_obj_attach(config, &nzcfg->config); + if (vconfig != NULL) + cfg_obj_attach(vconfig, &nzcfg->vconfig); + + result = count_newzones(view, nzcfg, num_zones); + return (result); +} + +static void +configure_zone_setviewcommit(isc_result_t result, const cfg_obj_t *zconfig, + dns_view_t *view) +{ + const char *zname; + dns_fixedname_t fixorigin; + dns_name_t *origin; + isc_result_t result2; + dns_view_t *pview = NULL; + dns_zone_t *zone = NULL; + dns_zone_t *raw = NULL; + + zname = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + origin = dns_fixedname_initname(&fixorigin); + + result2 = dns_name_fromstring(origin, zname, 0, NULL); + if (result2 != ISC_R_SUCCESS) { + return; + } + + result2 = dns_viewlist_find(&ns_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result2 != ISC_R_SUCCESS) { + return; + } + + result2 = dns_view_findzone(pview, origin, &zone); + if (result2 != ISC_R_SUCCESS) { + dns_view_detach(&pview); + return; + } + + dns_zone_getraw(zone, &raw); + + if (result == ISC_R_SUCCESS) { + dns_zone_setviewcommit(zone); + if (raw != NULL) + dns_zone_setviewcommit(raw); + } else { + dns_zone_setviewrevert(zone); + if (raw != NULL) + dns_zone_setviewrevert(raw); + } + + if (raw != NULL) { + dns_zone_detach(&raw); + } + + dns_zone_detach(&zone); + dns_view_detach(&pview); +} + +#ifndef HAVE_LMDB + +static isc_result_t +configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + isc_mem_t *mctx, cfg_aclconfctx_t *actx) +{ + isc_result_t result; + ns_cfgctx_t *nzctx; + const cfg_obj_t *zonelist; + const cfg_listelt_t *element; + + nzctx = view->new_zone_config; + if (nzctx == NULL || nzctx->nzf_config == NULL) { + return (ISC_R_SUCCESS); + } + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "loading additional zones for view '%s'", + view->name); + + zonelist = NULL; + cfg_map_get(nzctx->nzf_config, "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, &ns_g_server->viewlist, actx, + true, false, false)); + } + + result = ISC_R_SUCCESS; + + cleanup: + for (element = cfg_list_first(zonelist); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *zconfig = cfg_listelt_value(element); + configure_zone_setviewcommit(result, zconfig, view); + } + + return (result); +} + +#else /* HAVE_LMDB */ + +static isc_result_t +data_to_cfg(dns_view_t *view, MDB_val *key, MDB_val *data, + isc_buffer_t **text, cfg_obj_t **zoneconfig) +{ + isc_result_t result; + const char *zone_name; + size_t zone_name_len; + const char *zone_config; + size_t zone_config_len; + cfg_obj_t *zoneconf = NULL; + + REQUIRE(view != NULL); + REQUIRE(key != NULL); + REQUIRE(data != NULL); + REQUIRE(text != NULL); + REQUIRE(zoneconfig != NULL && *zoneconfig == NULL); + + if (*text == NULL) { + result = isc_buffer_allocate(view->mctx, text, 256); + if (result != ISC_R_SUCCESS) + goto cleanup; + } else { + isc_buffer_clear(*text); + } + + zone_name = (const char *) key->mv_data; + zone_name_len = key->mv_size; + INSIST(zone_name != NULL && zone_name_len > 0); + + zone_config = (const char *) data->mv_data; + zone_config_len = data->mv_size; + INSIST(zone_config != NULL && zone_config_len > 0); + + /* zone zonename { config; }; */ + result = isc_buffer_reserve(text, 5 + zone_name_len + 1 + + zone_config_len + 2); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + putstr(text, "zone "); + putmem(text, (const void *) zone_name, zone_name_len); + putstr(text, " "); + putmem(text, (const void *) zone_config, zone_config_len); + putstr(text, ";\n"); + + cfg_parser_reset(ns_g_addparser); + result = cfg_parse_buffer3(ns_g_addparser, *text, zone_name, 0, + &cfg_type_addzoneconf, &zoneconf); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "parsing config for zone '%.*s' in " + "NZD database '%s' failed", + (int) zone_name_len, zone_name, + view->new_zone_db); + goto cleanup; + } + + *zoneconfig = zoneconf; + zoneconf = NULL; + result = ISC_R_SUCCESS; + + cleanup: + if (zoneconf != NULL) { + cfg_obj_destroy(ns_g_addparser, &zoneconf); + } + + return (result); +} + +/*% + * Prototype for a callback which can be used with for_all_newzone_cfgs(). + */ +typedef isc_result_t (*newzone_cfg_cb_t)(const cfg_obj_t *zconfig, + cfg_obj_t *config, cfg_obj_t *vconfig, + isc_mem_t *mctx, dns_view_t *view, + cfg_aclconfctx_t *actx); + +/*% + * For each zone found in a NZD opened by the caller, create an object + * representing its configuration and invoke "callback" with the created + * object, "config", "vconfig", "mctx", "view" and "actx" as arguments (all + * these are non-global variables required to invoke configure_zone()). + * Immediately interrupt processing if an error is encountered while + * transforming NZD data into a zone configuration object or if "callback" + * returns an error. + */ +static isc_result_t +for_all_newzone_cfgs(newzone_cfg_cb_t callback, cfg_obj_t *config, + cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, + cfg_aclconfctx_t *actx, MDB_txn *txn, MDB_dbi dbi) +{ + const cfg_obj_t *zconfig, *zlist; + isc_result_t result = ISC_R_SUCCESS; + cfg_obj_t *zconfigobj = NULL; + isc_buffer_t *text = NULL; + MDB_cursor *cursor = NULL; + MDB_val data, key; + int status; + + status = mdb_cursor_open(txn, dbi, &cursor); + if (status != MDB_SUCCESS) { + return (ISC_R_FAILURE); + } + + for (status = mdb_cursor_get(cursor, &key, &data, MDB_FIRST); + status == MDB_SUCCESS; + status = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) + { + /* + * Create a configuration object from data fetched from NZD. + */ + result = data_to_cfg(view, &key, &data, &text, &zconfigobj); + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Extract zone configuration from configuration object. + */ + zlist = NULL; + result = cfg_map_get(zconfigobj, "zone", &zlist); + if (result != ISC_R_SUCCESS) { + break; + } else if (!cfg_obj_islist(zlist)) { + result = ISC_R_FAILURE; + break; + } + zconfig = cfg_listelt_value(cfg_list_first(zlist)); + + /* + * Invoke callback. + */ + result = callback(zconfig, config, vconfig, mctx, view, actx); + if (result != ISC_R_SUCCESS) { + break; + } + + /* + * Destroy the configuration object created in this iteration. + */ + cfg_obj_destroy(ns_g_addparser, &zconfigobj); + } + + if (text != NULL) { + isc_buffer_free(&text); + } + if (zconfigobj != NULL) { + cfg_obj_destroy(ns_g_addparser, &zconfigobj); + } + mdb_cursor_close(cursor); + + return (result); +} + +/*% + * Attempt to configure a zone found in NZD and return the result. + */ +static isc_result_t +configure_newzone(const cfg_obj_t *zconfig, cfg_obj_t *config, + cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, + cfg_aclconfctx_t *actx) +{ + return (configure_zone(config, zconfig, vconfig, mctx, view, + &ns_g_server->viewlist, actx, true, + false, false)); +} + +/*% + * Revert new view assignment for a zone found in NZD. + */ +static isc_result_t +configure_newzone_revert(const cfg_obj_t *zconfig, cfg_obj_t *config, + cfg_obj_t *vconfig, isc_mem_t *mctx, dns_view_t *view, + cfg_aclconfctx_t *actx) +{ + UNUSED(config); + UNUSED(vconfig); + UNUSED(mctx); + UNUSED(actx); + + configure_zone_setviewcommit(ISC_R_FAILURE, zconfig, view); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, + isc_mem_t *mctx, cfg_aclconfctx_t *actx) +{ + isc_result_t result; + MDB_txn *txn = NULL; + MDB_dbi dbi; + + if (view->new_zone_config == NULL) { + return (ISC_R_SUCCESS); + } + + result = nzd_open(view, MDB_RDONLY, &txn, &dbi); + if (result != ISC_R_SUCCESS) { + return (ISC_R_SUCCESS); + } + + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, "loading NZD configs from '%s' " + "for view '%s'", + view->new_zone_db, view->name); + + result = for_all_newzone_cfgs(configure_newzone, config, vconfig, mctx, + view, actx, txn, dbi); + if (result != ISC_R_SUCCESS) { + /* + * An error was encountered while attempting to configure zones + * found in NZD. As this error may have been caused by a + * configure_zone() failure, try restoring a sane configuration + * by reattaching all zones found in NZD to the old view. If + * this also fails, too bad, there is nothing more we can do in + * terms of trying to make things right. + */ + (void) for_all_newzone_cfgs(configure_newzone_revert, config, + vconfig, mctx, view, actx, txn, + dbi); + } + + (void) nzd_close(&txn, false); + return (result); +} + +static isc_result_t +get_newzone_config(dns_view_t *view, const char *zonename, + cfg_obj_t **zoneconfig) +{ + isc_result_t result; + int status; + cfg_obj_t *zoneconf = NULL; + isc_buffer_t *text = NULL; + MDB_txn *txn = NULL; + MDB_dbi dbi; + MDB_val key, data; + char zname[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fname; + dns_name_t *name; + isc_buffer_t b; + + INSIST(zoneconfig != NULL && *zoneconfig == NULL); + + CHECK(nzd_open(view, MDB_RDONLY, &txn, &dbi)); + + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, "loading NZD config from '%s' " + "for zone '%s'", + view->new_zone_db, zonename); + + /* Normalize zone name */ + isc_buffer_constinit(&b, zonename, strlen(zonename)); + isc_buffer_add(&b, strlen(zonename)); + name = dns_fixedname_initname(&fname); + CHECK(dns_name_fromtext(name, &b, dns_rootname, + DNS_NAME_DOWNCASE, NULL)); + dns_name_format(name, zname, sizeof(zname)); + + key.mv_data = zname; + key.mv_size = strlen(zname); + + status = mdb_get(txn, dbi, &key, &data); + if (status != MDB_SUCCESS) { + CHECK(ISC_R_FAILURE); + } + + CHECK(data_to_cfg(view, &key, &data, &text, &zoneconf)); + + *zoneconfig = zoneconf; + zoneconf = NULL; + result = ISC_R_SUCCESS; + + cleanup: + (void) nzd_close(&txn, false); + + if (zoneconf != NULL) { + cfg_obj_destroy(ns_g_addparser, &zoneconf); + } + if (text != NULL) { + isc_buffer_free(&text); + } + + return (result); +} + +#endif /* HAVE_LMDB */ + +static int +count_zones(const cfg_obj_t *conf) { + const cfg_obj_t *zonelist = NULL; + const cfg_listelt_t *element; + int n = 0; + + REQUIRE(conf != NULL); + + cfg_map_get(conf, "zone", &zonelist); + for (element = cfg_list_first(zonelist); + element != NULL; + element = cfg_list_next(element)) + n++; + + return (n); +} + +static isc_result_t +check_lockfile(ns_server_t *server, const cfg_obj_t *config, + bool first_time) +{ + isc_result_t result; + const char *filename = NULL; + const cfg_obj_t *maps[3]; + const cfg_obj_t *options; + const cfg_obj_t *obj; + int i; + + i = 0; + options = NULL; + result = cfg_map_get(config, "options", &options); + if (result == ISC_R_SUCCESS) + maps[i++] = options; + maps[i++] = ns_g_defaults; + maps[i] = NULL; + + obj = NULL; + (void) ns_config_get(maps, "lock-file", &obj); + + if (!first_time) { + if (obj != NULL && !cfg_obj_isstring(obj) && + server->lockfile != NULL && + strcmp(cfg_obj_asstring(obj), server->lockfile) != 0) + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, + "changing 'lock-file' " + "has no effect until the " + "server is restarted"); + + return (ISC_R_SUCCESS); + } + + if (obj != NULL) { + if (cfg_obj_isvoid(obj)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "skipping lock-file check "); + return (ISC_R_SUCCESS); + } else if (ns_g_forcelock) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "'lock-file' has no effect " + "because the server was run with -X"); + server->lockfile = isc_mem_strdup(server->mctx, + ns_g_defaultlockfile); + } else { + filename = cfg_obj_asstring(obj); + server->lockfile = isc_mem_strdup(server->mctx, + filename); + } + + if (server->lockfile == NULL) + return (ISC_R_NOMEMORY); + } + + if (ns_g_forcelock && ns_g_defaultlockfile != NULL) { + INSIST(server->lockfile == NULL); + server->lockfile = isc_mem_strdup(server->mctx, + ns_g_defaultlockfile); + } + + if (server->lockfile == NULL) + return (ISC_R_SUCCESS); + + if (ns_os_issingleton(server->lockfile)) + return (ISC_R_SUCCESS); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "could not lock %s; another named " + "process may be running", server->lockfile); + return (ISC_R_FAILURE); +} + +static isc_result_t +load_configuration(const char *filename, ns_server_t *server, + bool first_time) +{ + 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]; + const cfg_obj_t *obj; + const cfg_obj_t *options; + const cfg_obj_t *usev4ports, *avoidv4ports, *usev6ports, *avoidv6ports; + const cfg_obj_t *views; + dns_view_t *view = NULL; + dns_view_t *view_next; + dns_viewlist_t tmpviewlist; + dns_viewlist_t viewlist, builtin_viewlist; + in_port_t listen_port, udpport_low, udpport_high; + int i; + int num_zones = 0; + bool exclusive = false; + isc_interval_t interval; + isc_logconfig_t *logc = NULL; + isc_portset_t *v4portset = NULL; + isc_portset_t *v6portset = NULL; + isc_resourcevalue_t nfiles; + isc_result_t result, tresult; + uint32_t heartbeat_interval; + uint32_t interface_interval; + uint32_t reserved; + uint32_t udpsize; + uint32_t transfer_message_size; + ns_cache_t *nsc; + ns_cachelist_t cachelist, tmpcachelist; + ns_altsecret_t *altsecret; + ns_altsecretlist_t altsecrets, tmpaltsecrets; + unsigned int maxsocks; + uint32_t softquota = 0; + + ISC_LIST_INIT(viewlist); + ISC_LIST_INIT(builtin_viewlist); + ISC_LIST_INIT(cachelist); + ISC_LIST_INIT(altsecrets); + + /* Create the ACL configuration context */ + if (ns_g_aclconfctx != NULL) { + cfg_aclconfctx_detach(&ns_g_aclconfctx); + } + CHECK(cfg_aclconfctx_create(ns_g_mctx, &ns_g_aclconfctx)); + + /* + * Shut down all dyndb instances. + */ + dns_dyndb_cleanup(false); + + /* + * Parse the global default pseudo-config file. + */ + if (first_time) { + result = ns_config_parsedefaults(ns_g_parser, &ns_g_config); + if (result != ISC_R_SUCCESS) { + ns_main_earlyfatal("unable to load " + "internal defaults: %s", + isc_result_totext(result)); + } + RUNTIME_CHECK(cfg_map_get(ns_g_config, "options", + &ns_g_defaults) == ISC_R_SUCCESS); + } + + /* + * Parse the configuration file using the new config code. + */ + config = NULL; + + /* + * Unless this is lwresd with the -C option, parse the config file. + */ + if (!(ns_g_lwresdonly && lwresd_g_useresolvconf)) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, "loading configuration from '%s'", + filename); + 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); + } + + /* + * If this is lwresd with the -C option, or lwresd with no -C or -c + * option where the above parsing failed, parse resolv.conf. + */ + if (ns_g_lwresdonly && + (lwresd_g_useresolvconf || + (!ns_g_conffileset && result == ISC_R_FILENOTFOUND))) + { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, "loading configuration from '%s'", + lwresd_g_resolvconffile); + 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); + + /* + * Check the validity of the configuration. + */ + CHECK(bind9_check_namedconf(config, ns_g_lctx, ns_g_mctx)); + + /* + * Fill in the maps array, used for resolving defaults. + */ + i = 0; + options = NULL; + result = cfg_map_get(config, "options", &options); + if (result == ISC_R_SUCCESS) { + maps[i++] = options; + } + maps[i++] = ns_g_defaults; + maps[i] = NULL; + + /* + * If bind.keys exists, load it. If "dnssec-validation auto" + * is turned on, the root key found there will be used as a + * default trust anchor. + */ + 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 trust anchors " + "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); + } else { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "unable to open '%s'; using built-in keys " + "instead", server->bindkeysfile); + } + + /* Ensure exclusive access to configuration data. */ + if (!exclusive) { + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + exclusive = true; + } + + /* + * Set process limits, which (usually) needs to be done as root. + */ + set_limits(maps); + + /* + * Check the process lockfile. + */ + CHECK(check_lockfile(server, config, first_time)); + + /* + * Check if max number of open sockets that the system allows is + * sufficiently large. Failing this condition is not necessarily fatal, + * but may cause subsequent runtime failures for a busy recursive + * server. + */ + result = isc_socketmgr_getmaxsockets(ns_g_socketmgr, &maxsocks); + if (result != ISC_R_SUCCESS) { + maxsocks = 0; + } + result = isc_resource_getcurlimit(isc_resource_openfiles, &nfiles); + if (result == ISC_R_SUCCESS && (isc_resourcevalue_t)maxsocks > nfiles) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "max open files (%" PRIu64 ")" + " is smaller than max sockets (%u)", + nfiles, maxsocks); + } + + /* + * Set the number of socket reserved for TCP, stdio etc. + */ + obj = NULL; + result = ns_config_get(maps, "reserved-sockets", &obj); + INSIST(result == ISC_R_SUCCESS); + reserved = cfg_obj_asuint32(obj); + if (maxsocks != 0) { + if (maxsocks < 128U) { /* Prevent underflow. */ + reserved = 0; + } else if (reserved > maxsocks - 128U) { /* Minimum UDP space. */ + reserved = maxsocks - 128; + } + } + /* Minimum TCP/stdio space. */ + if (reserved < 128U) { + reserved = 128; + } + if (reserved + 128U > maxsocks && maxsocks != 0) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "less than 128 UDP sockets available after " + "applying 'reserved-sockets' and 'maxsockets'"); + } + isc__socketmgr_setreserved(ns_g_socketmgr, reserved); + +#ifdef HAVE_GEOIP + /* + * Initialize GeoIP databases from the configured location. + * This should happen before configuring any ACLs, so that we + * know what databases are available and can reject any GeoIP + * ACLs that can't work. + */ + obj = NULL; + result = ns_config_get(maps, "geoip-directory", &obj); + if (result == ISC_R_SUCCESS && cfg_obj_isstring(obj)) { + char *dir; + DE_CONST(cfg_obj_asstring(obj), dir); + ns_geoip_load(dir); + } else { + ns_geoip_load(NULL); + } + ns_g_aclconfctx->geoip = ns_g_geoip; + + obj = NULL; + result = ns_config_get(maps, "geoip-use-ecs", &obj); + INSIST(result == ISC_R_SUCCESS); + ns_g_server->aclenv.geoip_use_ecs = cfg_obj_asboolean(obj); +#endif /* HAVE_GEOIP */ + + /* + * Configure various server options. + */ + configure_server_quota(maps, "transfers-out", &server->xfroutquota); + configure_server_quota(maps, "tcp-clients", &server->tcpquota); + configure_server_quota(maps, "recursive-clients", + &server->recursionquota); + + if (server->recursionquota.max > 1000) { + int margin = ISC_MAX(100, ns_g_cpus + 1); + if (margin > server->recursionquota.max - 100) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "'recursive-clients %d' too low when " + "running with %d worker threads", + server->recursionquota.max, ns_g_cpus); + CHECK(ISC_R_RANGE); + } + softquota = server->recursionquota.max - margin; + } else { + softquota = (server->recursionquota.max * 90) / 100; + } + + isc_quota_soft(&server->recursionquota, softquota); + + /* + * Set "blackhole". Only legal at options level; there is + * no default. + */ + CHECK(configure_view_acl(NULL, config, NULL, "blackhole", NULL, + ns_g_aclconfctx, ns_g_mctx, + &server->blackholeacl)); + if (server->blackholeacl != NULL) { + dns_dispatchmgr_setblackhole(ns_g_dispatchmgr, + server->blackholeacl); + } + + /* + * Set "keep-response-order". Only legal at options or + * global defaults level. + */ + CHECK(configure_view_acl(NULL, config, ns_g_config, + "keep-response-order", NULL, + ns_g_aclconfctx, ns_g_mctx, + &server->keepresporder)); + + obj = NULL; + result = ns_config_get(maps, "match-mapped-addresses", &obj); + INSIST(result == ISC_R_SUCCESS); + server->aclenv.match_mapped = cfg_obj_asboolean(obj); + + CHECKM(ns_statschannels_configure(ns_g_server, config, ns_g_aclconfctx), + "configuring statistics server(s)"); + + /* + * Configure sets of UDP query source ports. + */ + CHECKM(isc_portset_create(ns_g_mctx, &v4portset), + "creating UDP port set"); + CHECKM(isc_portset_create(ns_g_mctx, &v6portset), + "creating UDP port set"); + + usev4ports = NULL; + usev6ports = NULL; + avoidv4ports = NULL; + avoidv6ports = NULL; + + (void)ns_config_get(maps, "use-v4-udp-ports", &usev4ports); + if (usev4ports != NULL) { + portset_fromconf(v4portset, usev4ports, true); + } else { + CHECKM(isc_net_getudpportrange(AF_INET, &udpport_low, + &udpport_high), + "get the default UDP/IPv4 port range"); + if (udpport_low == udpport_high) { + isc_portset_add(v4portset, udpport_low); + } else { + isc_portset_addrange(v4portset, udpport_low, + udpport_high); + } + if (!ns_g_disable4) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "using default UDP/IPv4 port range: " + "[%d, %d]", udpport_low, udpport_high); + } + } + (void)ns_config_get(maps, "avoid-v4-udp-ports", &avoidv4ports); + if (avoidv4ports != NULL) { + portset_fromconf(v4portset, avoidv4ports, false); + } + + (void)ns_config_get(maps, "use-v6-udp-ports", &usev6ports); + if (usev6ports != NULL) { + portset_fromconf(v6portset, usev6ports, true); + } else { + CHECKM(isc_net_getudpportrange(AF_INET6, &udpport_low, + &udpport_high), + "get the default UDP/IPv6 port range"); + if (udpport_low == udpport_high) { + isc_portset_add(v6portset, udpport_low); + } else { + isc_portset_addrange(v6portset, udpport_low, + udpport_high); + } + if (!ns_g_disable6) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "using default UDP/IPv6 port range: " + "[%d, %d]", udpport_low, udpport_high); + } + } + (void)ns_config_get(maps, "avoid-v6-udp-ports", &avoidv6ports); + if (avoidv6ports != NULL) { + portset_fromconf(v6portset, avoidv6ports, false); + } + + dns_dispatchmgr_setavailports(ns_g_dispatchmgr, v4portset, v6portset); + + /* + * Set the EDNS UDP size when we don't match a view. + */ + obj = NULL; + result = ns_config_get(maps, "edns-udp-size", &obj); + INSIST(result == ISC_R_SUCCESS); + udpsize = cfg_obj_asuint32(obj); + if (udpsize < 512) { + udpsize = 512; + } + if (udpsize > 4096) { + udpsize = 4096; + } + ns_g_udpsize = (uint16_t)udpsize; + + /* Set the transfer message size for TCP */ + obj = NULL; + result = ns_config_get(maps, "transfer-message-size", &obj); + INSIST(result == ISC_R_SUCCESS); + transfer_message_size = cfg_obj_asuint32(obj); + if (transfer_message_size < 512) { + transfer_message_size = 512; + } else if (transfer_message_size > 65535) { + transfer_message_size = 65535; + } + server->transfer_tcp_message_size = (uint16_t) transfer_message_size; + + /* + * Configure the zone manager. + */ + obj = NULL; + result = ns_config_get(maps, "transfers-in", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_settransfersin(server->zonemgr, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "transfers-per-ns", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_settransfersperns(server->zonemgr, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "notify-rate", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_setnotifyrate(server->zonemgr, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "startup-notify-rate", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_setstartupnotifyrate(server->zonemgr, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "serial-query-rate", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zonemgr_setserialqueryrate(server->zonemgr, cfg_obj_asuint32(obj)); + + /* + * Determine which port to use for listening for incoming connections. + */ + if (ns_g_port != 0) { + listen_port = ns_g_port; + } else { + CHECKM(ns_config_getport(config, &listen_port), "port"); + } + + /* + * Determing the default DSCP code point. + */ + CHECKM(ns_config_getdscp(config, &ns_g_dscp), "dscp"); + + /* + * Find the listen queue depth. + */ + obj = NULL; + result = ns_config_get(maps, "tcp-listen-queue", &obj); + INSIST(result == ISC_R_SUCCESS); + ns_g_listen = cfg_obj_asuint32(obj); + if ((ns_g_listen > 0) && (ns_g_listen < 10)) { + ns_g_listen = 10; + } + + /* + * Configure the interface manager according to the "listen-on" + * statement. + */ + { + const cfg_obj_t *clistenon = NULL; + ns_listenlist_t *listenon = NULL; + + clistenon = NULL; + /* + * Even though listen-on is present in the default + * configuration, we can't use it here, since it isn't + * used if we're in lwresd mode. This way is easier. + */ + if (options != NULL) { + (void)cfg_map_get(options, "listen-on", &clistenon); + } + if (clistenon != NULL) { + /* check return code? */ + (void)ns_listenlist_fromconfig(clistenon, config, + ns_g_aclconfctx, + ns_g_mctx, AF_INET, + &listenon); + } else if (!ns_g_lwresdonly) { + /* + * Not specified, use default. + */ + CHECK(ns_listenlist_default(ns_g_mctx, listen_port, + -1, true, &listenon)); + } + if (listenon != NULL) { + ns_interfacemgr_setlistenon4(server->interfacemgr, + listenon); + ns_listenlist_detach(&listenon); + } + } + /* + * Ditto for IPv6. + */ + { + const cfg_obj_t *clistenon = NULL; + ns_listenlist_t *listenon = NULL; + + if (options != NULL) { + (void)cfg_map_get(options, "listen-on-v6", &clistenon); + } + if (clistenon != NULL) { + /* check return code? */ + (void)ns_listenlist_fromconfig(clistenon, config, + ns_g_aclconfctx, + ns_g_mctx, AF_INET6, + &listenon); + } else if (!ns_g_lwresdonly) { + /* + * Not specified, use default. + */ + CHECK(ns_listenlist_default(ns_g_mctx, listen_port, + -1, true, &listenon)); + } + if (listenon != NULL) { + ns_interfacemgr_setlistenon6(server->interfacemgr, + listenon); + ns_listenlist_detach(&listenon); + } + } + + /* + * Rescan the interface list to pick up changes in the + * listen-on option. It's important that we do this before we try + * to configure the query source, since the dispatcher we use might + * be shared with an interface. + */ + result = scan_interfaces(server, true); + + /* + * Check that named is able to TCP listen on at least one + * interface. Otherwise, another named process could be running + * and we should fail. + */ + if (first_time && (result == ISC_R_ADDRINUSE)) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "unable to listen on any configured interfaces"); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* + * Arrange for further interface scanning to occur periodically + * as specified by the "interface-interval" option. + */ + obj = NULL; + result = ns_config_get(maps, "interface-interval", &obj); + INSIST(result == ISC_R_SUCCESS); + interface_interval = cfg_obj_asuint32(obj) * 60; + if (interface_interval == 0) { + CHECK(isc_timer_reset(server->interface_timer, + isc_timertype_inactive, + NULL, NULL, true)); + } else if (server->interface_interval != interface_interval) { + isc_interval_set(&interval, interface_interval, 0); + CHECK(isc_timer_reset(server->interface_timer, + isc_timertype_ticker, + NULL, &interval, false)); + } + server->interface_interval = interface_interval; + + /* + * Enable automatic interface scans. + */ + obj = NULL; + result = ns_config_get(maps, "automatic-interface-scan", &obj); + INSIST(result == ISC_R_SUCCESS); + server->interface_auto = cfg_obj_asboolean(obj); + + /* + * Configure the dialup heartbeat timer. + */ + obj = NULL; + result = ns_config_get(maps, "heartbeat-interval", &obj); + INSIST(result == ISC_R_SUCCESS); + heartbeat_interval = cfg_obj_asuint32(obj) * 60; + if (heartbeat_interval == 0) { + CHECK(isc_timer_reset(server->heartbeat_timer, + isc_timertype_inactive, + NULL, NULL, true)); + } else if (server->heartbeat_interval != heartbeat_interval) { + isc_interval_set(&interval, heartbeat_interval, 0); + CHECK(isc_timer_reset(server->heartbeat_timer, + isc_timertype_ticker, + NULL, &interval, false)); + } + server->heartbeat_interval = heartbeat_interval; + + isc_interval_set(&interval, 1200, 0); + CHECK(isc_timer_reset(server->pps_timer, isc_timertype_ticker, NULL, + &interval, false)); + + isc_interval_set(&interval, ns_g_tat_interval, 0); + CHECK(isc_timer_reset(server->tat_timer, isc_timertype_ticker, NULL, + &interval, 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); + + views = NULL; + (void)cfg_map_get(config, "view", &views); + + /* + * Create the views and count all the configured zones in + * order to correctly size the zone manager's task table. + * (We only count zones for configured views; the built-in + * "bind" view can be ignored as it only adds a negligible + * number of zones.) + * + * If we're allowing new zones, we need to be able to find the + * new zone file and count those as well. So we setup the new + * zone configuration context, but otherwise view configuration + * waits until after the zone manager's task list has been sized. + */ + for (element = cfg_list_first(views); + element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *vconfig = cfg_listelt_value(element); + const cfg_obj_t *voptions = cfg_tuple_get(vconfig, "options"); + int nzf_num_zones; + + view = NULL; + + CHECK(create_view(vconfig, &viewlist, &view)); + INSIST(view != NULL); + + num_zones += count_zones(voptions); + + CHECK(setup_newzones(view, config, vconfig, conf_parser, + ns_g_aclconfctx, &nzf_num_zones)); + num_zones += nzf_num_zones; + + dns_view_detach(&view); + } + + /* + * If there were no explicit views then we do the default + * view here. + */ + if (views == NULL) { + int nzf_num_zones; + + CHECK(create_view(NULL, &viewlist, &view)); + INSIST(view != NULL); + + num_zones = count_zones(config); + + CHECK(setup_newzones(view, config, NULL, conf_parser, + ns_g_aclconfctx, &nzf_num_zones)); + num_zones += nzf_num_zones; + + dns_view_detach(&view); + } + + /* + * Zones have been counted; set the zone manager task pool size. + */ + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "sizing zone task pool based on %d zones", num_zones); + CHECK(dns_zonemgr_setsize(ns_g_server->zonemgr, num_zones)); + + /* + * 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. + */ + for (element = cfg_list_first(views); + element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *vconfig = cfg_listelt_value(element); + + view = NULL; + CHECK(find_view(vconfig, &viewlist, &view)); + CHECK(configure_view(view, &viewlist, config, vconfig, + &cachelist, bindkeys, ns_g_mctx, + ns_g_aclconfctx, true)); + dns_view_freeze(view); + dns_view_detach(&view); + } + + /* + * Make sure we have a default view if and only if there + * were no explicit views. + */ + if (views == NULL) { + view = NULL; + CHECK(find_view(NULL, &viewlist, &view)); + CHECK(configure_view(view, &viewlist, config, NULL, + &cachelist, bindkeys, + ns_g_mctx, ns_g_aclconfctx, true)); + dns_view_freeze(view); + dns_view_detach(&view); + } + + /* + * Create (or recreate) the built-in views. + */ + builtin_views = NULL; + RUNTIME_CHECK(cfg_map_get(ns_g_config, "view", + &builtin_views) == ISC_R_SUCCESS); + for (element = cfg_list_first(builtin_views); + element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *vconfig = cfg_listelt_value(element); + + CHECK(create_view(vconfig, &builtin_viewlist, &view)); + CHECK(configure_view(view, &viewlist, config, vconfig, + &cachelist, bindkeys, + ns_g_mctx, ns_g_aclconfctx, false)); + dns_view_freeze(view); + dns_view_detach(&view); + view = NULL; + } + + /* Now combine the two viewlists into one */ + ISC_LIST_APPENDLIST(viewlist, builtin_viewlist, link); + + /* + * Commit any dns_zone_setview() calls on all zones in the new + * view. + */ + for (view = ISC_LIST_HEAD(viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + dns_view_setviewcommit(view); + } + + /* Swap our new view list with the production one. */ + tmpviewlist = server->viewlist; + server->viewlist = viewlist; + viewlist = tmpviewlist; + + /* 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, + &t), + "configuring TKEY"); + if (server->tkeyctx != NULL) { + dns_tkeyctx_destroy(&server->tkeyctx); + } + server->tkeyctx = t; + } + + /* + * Bind the control port(s). + */ + CHECKM(ns_controls_configure(ns_g_server->controls, config, + ns_g_aclconfctx), + "binding control channel(s)"); + + /* + * Bind the lwresd port(s). + */ + CHECKM(ns_lwresd_configure(ns_g_mctx, config), + "binding lightweight resolver ports"); + + /* + * Open the source of entropy. + */ + if (first_time) { + obj = NULL; + result = ns_config_get(maps, "random-device", &obj); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "no source of entropy found"); + } else { + const char *randomdev = cfg_obj_asstring(obj); + int level = ISC_LOG_ERROR; + result = isc_entropy_createfilesource(ns_g_entropy, + randomdev); +#ifdef PATH_RANDOMDEV + if (ns_g_fallbackentropy != NULL) { + level = ISC_LOG_INFO; + } +#endif + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + level, + "could not open entropy source " + "%s: %s", + randomdev, + isc_result_totext(result)); + } +#ifdef PATH_RANDOMDEV + if (ns_g_fallbackentropy != NULL) { + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_INFO, + "using pre-chroot entropy source " + "%s", + PATH_RANDOMDEV); + isc_entropy_detach(&ns_g_entropy); + isc_entropy_attach(ns_g_fallbackentropy, + &ns_g_entropy); + } + isc_entropy_detach(&ns_g_fallbackentropy); + } +#endif + } + } + +#ifdef HAVE_LMDB + /* + * If we're using LMDB, we may have created newzones databases + * as root, making it impossible to reopen them later after + * switching to a new userid. We close them now, and reopen + * after relinquishing privileges them. + */ + if (first_time) { + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + nzd_env_close(view); + } + } +#endif /* HAVE_LMDB */ + + /* + * Relinquish root privileges. + */ + if (first_time) { + ns_os_changeuser(); + } + + /* + * Check that the working directory is writable. + */ + if (!isc_file_isdirwritable(".")) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "the working directory is not writable"); + } + +#ifdef HAVE_LMDB + /* + * Reopen NZD databases. + */ + if (first_time) { + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + nzd_env_reopen(view); + } + } +#endif /* HAVE_LMDB */ + + /* + * Configure the logging system. + * + * Do this after changing UID to make sure that any log + * files specified in named.conf get created by the + * unprivileged user, not root. + */ + if (ns_g_logstderr) { + const cfg_obj_t *logobj = NULL; + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "not using config file logging " + "statement for logging due to " + "-g option"); + + (void)cfg_map_get(config, "logging", &logobj); + if (logobj != NULL) { + result = ns_log_configure(NULL, logobj); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "checking logging configuration " + "failed: %s", + isc_result_totext(result)); + goto cleanup; + } + } + } else { + const cfg_obj_t *logobj = NULL; + + CHECKM(isc_logconfig_create(ns_g_lctx, &logc), + "creating new logging configuration"); + + logobj = NULL; + (void)cfg_map_get(config, "logging", &logobj); + if (logobj != NULL) { + CHECKM(ns_log_configure(logc, logobj), + "configuring logging"); + } else { + CHECKM(ns_log_setdefaultchannels(logc), + "setting up default logging channels"); + CHECKM(ns_log_setunmatchedcategory(logc), + "setting up default 'category unmatched'"); + CHECKM(ns_log_setdefaultcategory(logc), + "setting up default 'category default'"); + } + + CHECKM(isc_logconfig_use(ns_g_lctx, logc), + "installing logging configuration"); + logc = NULL; + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "now using logging configuration from " + "config file"); + } + + /* + * Set the default value of the query logging flag depending + * whether a "queries" category has been defined. This is + * a disgusting hack, but we need to do this for BIND 8 + * compatibility. + */ + if (first_time) { + const cfg_obj_t *logobj = NULL; + const cfg_obj_t *categories = NULL; + + obj = NULL; + if (ns_config_get(maps, "querylog", &obj) == ISC_R_SUCCESS) { + server->log_queries = cfg_obj_asboolean(obj); + } else { + + (void)cfg_map_get(config, "logging", &logobj); + if (logobj != NULL) + (void)cfg_map_get(logobj, "category", + &categories); + if (categories != NULL) { + for (element = cfg_list_first(categories); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *catobj; + const char *str; + + obj = cfg_listelt_value(element); + catobj = cfg_tuple_get(obj, "name"); + str = cfg_obj_asstring(catobj); + if (strcasecmp(str, "queries") == 0) + server->log_queries = true; + } + } + } + } + + obj = NULL; + if (options != NULL && + cfg_map_get(options, "memstatistics", &obj) == ISC_R_SUCCESS) { + ns_g_memstatistics = cfg_obj_asboolean(obj); + } else { + ns_g_memstatistics = (isc_mem_debugging & ISC_MEM_DEBUGRECORD); + } + + obj = NULL; + if (ns_config_get(maps, "memstatistics-file", &obj) == ISC_R_SUCCESS) { + ns_main_setmemstats(cfg_obj_asstring(obj)); + } else if (ns_g_memstatistics) { + ns_main_setmemstats("named.memstats"); + } else { + ns_main_setmemstats(NULL); + } + + obj = NULL; + result = ns_config_get(maps, "statistics-file", &obj); + INSIST(result == ISC_R_SUCCESS); + CHECKM(setstring(server, &server->statsfile, cfg_obj_asstring(obj)), + "strdup"); + + obj = NULL; + result = ns_config_get(maps, "dump-file", &obj); + INSIST(result == ISC_R_SUCCESS); + CHECKM(setstring(server, &server->dumpfile, cfg_obj_asstring(obj)), + "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)), + "strdup"); + + obj = NULL; + result = ns_config_get(maps, "version", &obj); + if (result == ISC_R_SUCCESS) { + CHECKM(setoptstring(server, &server->version, obj), "strdup"); + server->version_set = true; + } else { + server->version_set = false; + } + + obj = NULL; + result = ns_config_get(maps, "hostname", &obj); + if (result == ISC_R_SUCCESS) { + CHECKM(setoptstring(server, &server->hostname, obj), "strdup"); + server->hostname_set = true; + } else { + server->hostname_set = false; + } + + obj = NULL; + result = ns_config_get(maps, "server-id", &obj); + server->server_usehostname = false; + if (result == ISC_R_SUCCESS && cfg_obj_isboolean(obj)) { + /* The parser translates "hostname" to true */ + server->server_usehostname = cfg_obj_asboolean(obj); + result = setstring(server, &server->server_id, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } else if (result == ISC_R_SUCCESS) { + /* Found a quoted string */ + CHECKM(setoptstring(server, &server->server_id, obj), "strdup"); + } else { + result = setstring(server, &server->server_id, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + obj = NULL; + result = ns_config_get(maps, "flush-zones-on-shutdown", &obj); + if (result == ISC_R_SUCCESS) { + server->flushonshutdown = cfg_obj_asboolean(obj); + } else { + server->flushonshutdown = false; + } + + obj = NULL; + result = ns_config_get(maps, "answer-cookie", &obj); + INSIST(result == ISC_R_SUCCESS); + server->answercookie = cfg_obj_asboolean(obj); + + obj = NULL; + result = ns_config_get(maps, "cookie-algorithm", &obj); + INSIST(result == ISC_R_SUCCESS); + if (strcasecmp(cfg_obj_asstring(obj), "aes") == 0) { +#if defined(HAVE_OPENSSL_AES) || defined(HAVE_OPENSSL_EVP_AES) + server->cookiealg = ns_cookiealg_aes; +#else + INSIST(0); +#endif + } else if (strcasecmp(cfg_obj_asstring(obj), "sha1") == 0) { + server->cookiealg = ns_cookiealg_sha1; + } else if (strcasecmp(cfg_obj_asstring(obj), "sha256") == 0) { + server->cookiealg = ns_cookiealg_sha256; + } else { + INSIST(0); + } + + obj = NULL; + result = ns_config_get(maps, "cookie-secret", &obj); + if (result == ISC_R_SUCCESS) { + const char *str; + bool first = true; + isc_buffer_t b; + unsigned int usedlength; + + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(obj); + + if (first) { + memset(server->secret, 0, + sizeof(server->secret)); + isc_buffer_init(&b, server->secret, + sizeof(server->secret)); + result = isc_hex_decodestring(str, &b); + if (result != ISC_R_SUCCESS && + result != ISC_R_NOSPACE) + goto cleanup; + first = false; + } else { + altsecret = isc_mem_get(server->mctx, + sizeof(*altsecret)); + if (altsecret == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + isc_buffer_init(&b, altsecret->secret, + sizeof(altsecret->secret)); + result = isc_hex_decodestring(str, &b); + if (result != ISC_R_SUCCESS && + result != ISC_R_NOSPACE) { + isc_mem_put(server->mctx, altsecret, + sizeof(*altsecret)); + goto cleanup; + } + ISC_LIST_INITANDAPPEND(altsecrets, + altsecret, link); + } + + usedlength = isc_buffer_usedlength(&b); + switch (server->cookiealg) { + case ns_cookiealg_aes: + if (usedlength != ISC_AES128_KEYLENGTH) { + CHECKM(ISC_R_RANGE, + "AES cookie-secret must be " + "128 bits"); + } + break; + case ns_cookiealg_sha1: + if (usedlength != ISC_SHA1_DIGESTLENGTH) { + CHECKM(ISC_R_RANGE, + "SHA1 cookie-secret must be " + "160 bits"); + } + break; + case ns_cookiealg_sha256: + if (usedlength != ISC_SHA256_DIGESTLENGTH) { + CHECKM(ISC_R_RANGE, + "SHA256 cookie-secret must be " + "256 bits"); + } + break; + } + } + } else { + result = isc_entropy_getdata(ns_g_entropy, + server->secret, + sizeof(server->secret), + NULL, + 0); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + + /* + * Swap altsecrets lists. + */ + tmpaltsecrets = server->altsecrets; + server->altsecrets = altsecrets; + altsecrets = tmpaltsecrets; + + (void) ns_server_loadnta(server); + + result = ISC_R_SUCCESS; + + cleanup: + if (logc != NULL) { + isc_logconfig_destroy(&logc); + } + + if (v4portset != NULL) { + isc_portset_destroy(ns_g_mctx, &v4portset); + } + + if (v6portset != NULL) { + isc_portset_destroy(ns_g_mctx, &v6portset); + } + + if (conf_parser != NULL) { + if (config != NULL) { + 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) { + dns_view_detach(&view); + } + + ISC_LIST_APPENDLIST(viewlist, builtin_viewlist, link); + + /* + * This cleans up either the old production view list + * or our temporary list depending on whether they + * were swapped above or not. + */ + for (view = ISC_LIST_HEAD(viewlist); + view != NULL; + view = view_next) { + view_next = ISC_LIST_NEXT(view, link); + ISC_LIST_UNLINK(viewlist, view, link); + if (result == ISC_R_SUCCESS && + strcmp(view->name, "_bind") != 0) + { + dns_view_setviewrevert(view); + (void)dns_zt_apply(view->zonetable, false, + removed, view); + } + 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)); + } + + /* Same cleanup for altsecrets list. */ + while ((altsecret = ISC_LIST_HEAD(altsecrets)) != NULL) { + ISC_LIST_UNLINK(altsecrets, altsecret, link); + isc_mem_put(server->mctx, altsecret, sizeof(*altsecret)); + } + + /* + * Adjust the listening interfaces in accordance with the source + * addresses specified in views and zones. + */ + if (isc_net_probeipv6() == ISC_R_SUCCESS) { + adjust_interfaces(server, ns_g_mctx); + } + + /* + * Record the time of most recent configuration + */ + tresult = isc_time_now(&ns_g_configtime); + if (tresult != ISC_R_SUCCESS) { + ns_main_earlyfatal("isc_time_now() failed: %s", + isc_result_totext(result)); + } + + /* Relinquish exclusive access to configuration data. */ + if (exclusive) { + isc_task_endexclusive(server->task); + } + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_DEBUG(1), "load_configuration: %s", + isc_result_totext(result)); + + return (result); +} + +static isc_result_t +view_loaded(void *arg) { + isc_result_t result; + ns_zoneload_t *zl = (ns_zoneload_t *) arg; + ns_server_t *server = zl->server; + bool reconfig = zl->reconfig; + unsigned int refs; + + + /* + * 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. + * + * We use the zoneload reference counter to let us + * know when all views are finished. + */ + isc_refcount_decrement(&zl->refs, &refs); + if (refs != 0) + return (ISC_R_SUCCESS); + + isc_refcount_destroy(&zl->refs); + isc_mem_put(server->mctx, zl, sizeof (*zl)); + + /* + * To maintain compatibility with log parsing tools that might + * be looking for this string after "rndc reconfig", we keep it + * as it is + */ + if (reconfig) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "any newly configured zones are now loaded"); + } else { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_NOTICE, + "all zones loaded"); + } + + CHECKFATAL(dns_zonemgr_forcemaint(server->zonemgr), + "forcing zone maintenance"); + + ns_os_started(); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_NOTICE, "running"); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_zones(ns_server_t *server, bool init, bool reconfig) { + isc_result_t result; + dns_view_t *view; + ns_zoneload_t *zl; + unsigned int refs = 0; + + zl = isc_mem_get(server->mctx, sizeof (*zl)); + if (zl == NULL) + return (ISC_R_NOMEMORY); + zl->server = server; + zl->reconfig = reconfig; + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_refcount_init(&zl->refs, 1); + + /* + * Schedule zones to be loaded from disk. + */ + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (view->managed_keys != NULL) { + result = dns_zone_load(view->managed_keys); + if (result != ISC_R_SUCCESS && + result != DNS_R_UPTODATE && + result != DNS_R_CONTINUE) + goto cleanup; + } + if (view->redirect != NULL) { + result = dns_zone_load(view->redirect); + if (result != ISC_R_SUCCESS && + result != DNS_R_UPTODATE && + result != DNS_R_CONTINUE) + goto cleanup; + } + + /* + * 'dns_view_asyncload' calls view_loaded if there are no + * zones. + */ + isc_refcount_increment(&zl->refs, NULL); + CHECK(dns_view_asyncload2(view, view_loaded, zl, reconfig)); + } + + cleanup: + isc_refcount_decrement(&zl->refs, &refs); + if (refs == 0) { + isc_refcount_destroy(&zl->refs); + isc_mem_put(server->mctx, zl, sizeof (*zl)); + } else if (init) { + /* + * Place the task manager into privileged mode. This + * ensures that after we leave task-exclusive mode, no + * other tasks will be able to run except for the ones + * that are loading zones. (This should only be done during + * the initial server setup; it isn't necessary during + * a reload.) + */ + isc_taskmgr_setmode(ns_g_taskmgr, isc_taskmgrmode_privileged); + } + + isc_task_endexclusive(server->task); + return (result); +} + +static void +run_server(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + ns_server_t *server = (ns_server_t *)event->ev_arg; + + INSIST(task == server->task); + + isc_event_free(&event); + + CHECKFATAL(dns_dispatchmgr_create(ns_g_mctx, ns_g_entropy, + &ns_g_dispatchmgr), + "creating dispatch manager"); + + dns_dispatchmgr_setstats(ns_g_dispatchmgr, server->resolverstats); + + CHECKFATAL(ns_interfacemgr_create(ns_g_mctx, ns_g_taskmgr, + ns_g_socketmgr, ns_g_dispatchmgr, + server->task, &server->interfacemgr), + "creating interface manager"); + + CHECKFATAL(isc_timer_create(ns_g_timermgr, isc_timertype_inactive, + NULL, NULL, server->task, + interface_timer_tick, + server, &server->interface_timer), + "creating interface timer"); + + CHECKFATAL(isc_timer_create(ns_g_timermgr, isc_timertype_inactive, + NULL, NULL, server->task, + heartbeat_timer_tick, + server, &server->heartbeat_timer), + "creating heartbeat timer"); + + CHECKFATAL(isc_timer_create(ns_g_timermgr, isc_timertype_inactive, + NULL, NULL, server->task, tat_timer_tick, + server, &server->tat_timer), + "creating trust anchor telemetry timer"); + + CHECKFATAL(isc_timer_create(ns_g_timermgr, isc_timertype_inactive, + NULL, NULL, server->task, pps_timer_tick, + server, &server->pps_timer), + "creating pps timer"); + + CHECKFATAL(cfg_parser_create(ns_g_mctx, ns_g_lctx, &ns_g_parser), + "creating default configuration parser"); + + CHECKFATAL(cfg_parser_create(ns_g_mctx, ns_g_lctx, &ns_g_addparser), + "creating additional configuration parser"); + + if (ns_g_lwresdonly) + CHECKFATAL(load_configuration(lwresd_g_conffile, server, + true), + "loading configuration"); + else + CHECKFATAL(load_configuration(ns_g_conffile, server, true), + "loading configuration"); + + isc_hash_init(); + + CHECKFATAL(load_zones(server, true, false), "loading zones"); +#ifdef ENABLE_AFL + ns_g_run_done = true; +#endif +} + +void +ns_server_flushonshutdown(ns_server_t *server, bool flush) { + + REQUIRE(NS_SERVER_VALID(server)); + + server->flushonshutdown = flush; +} + +static void +shutdown_server(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + dns_view_t *view, *view_next; + ns_server_t *server = (ns_server_t *)event->ev_arg; + bool flush = server->flushonshutdown; + ns_cache_t *nsc; + ns_altsecret_t *altsecret; + + UNUSED(task); + INSIST(task == server->task); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, "shutting down%s", + flush ? ": flushing changes" : ""); + + ns_statschannels_shutdown(server); + ns_controls_shutdown(server->controls); + end_reserved_dispatches(server, true); + cleanup_session_key(server, server->mctx); + + if (ns_g_aclconfctx != NULL) + cfg_aclconfctx_detach(&ns_g_aclconfctx); + + cfg_obj_destroy(ns_g_parser, &ns_g_config); + cfg_parser_destroy(&ns_g_parser); + cfg_parser_destroy(&ns_g_addparser); + + (void) ns_server_saventa(server); + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = view_next) { + view_next = ISC_LIST_NEXT(view, link); + ISC_LIST_UNLINK(server->viewlist, view, link); + if (flush) + dns_view_flushanddetach(&view); + else + dns_view_detach(&view); + } + + dns_dyndb_cleanup(true); + + 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)); + } + + while ((altsecret = ISC_LIST_HEAD(server->altsecrets)) != NULL) { + ISC_LIST_UNLINK(server->altsecrets, altsecret, link); + isc_mem_put(server->mctx, altsecret, sizeof(*altsecret)); + } + + isc_timer_detach(&server->interface_timer); + isc_timer_detach(&server->heartbeat_timer); + isc_timer_detach(&server->pps_timer); + isc_timer_detach(&server->tat_timer); + + ns_interfacemgr_shutdown(server->interfacemgr); + ns_interfacemgr_detach(&server->interfacemgr); + + dns_dispatchmgr_destroy(&ns_g_dispatchmgr); + + 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->keepresporder != NULL) + dns_acl_detach(&server->keepresporder); + + if (server->blackholeacl != NULL) + dns_acl_detach(&server->blackholeacl); + +#ifdef HAVE_DNSTAP + dns_dt_shutdown(); +#endif +#ifdef HAVE_GEOIP + dns_geoip_shutdown(); +#endif + + dns_db_detach(&server->in_roothints); + + isc_task_endexclusive(server->task); + + isc_task_detach(&server->task); + + isc_event_free(&event); +} + +void +ns_server_create(isc_mem_t *mctx, ns_server_t **serverp) { + isc_result_t result; + ns_server_t *server = isc_mem_get(mctx, sizeof(*server)); + + if (server == NULL) + fatal("allocating server object", ISC_R_NOMEMORY); + + server->mctx = mctx; + server->task = NULL; + + /* Initialize configuration data with default values. */ + result = isc_quota_init(&server->xfroutquota, 10); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = isc_quota_init(&server->tcpquota, 10); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = isc_quota_init(&server->recursionquota, 100); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + result = dns_aclenv_init(mctx, &server->aclenv); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + +#ifdef HAVE_GEOIP + /* Initialize GeoIP before using ACL environment */ + ns_geoip_init(); + server->aclenv.geoip = ns_g_geoip; +#endif + + /* Initialize server data structures. */ + server->zonemgr = NULL; + server->interfacemgr = NULL; + ISC_LIST_INIT(server->viewlist); + server->in_roothints = NULL; + server->blackholeacl = NULL; + server->keepresporder = NULL; + + /* Must be first. */ + CHECKFATAL(dst_lib_init2(ns_g_mctx, ns_g_entropy, + ns_g_engine, ISC_ENTROPY_GOODONLY), + "initializing DST"); + + CHECKFATAL(dns_rootns_create(mctx, dns_rdataclass_in, NULL, + &server->in_roothints), + "setting up root hints"); + + CHECKFATAL(isc_mutex_init(&server->reload_event_lock), + "initializing reload event lock"); + server->reload_event = + isc_event_allocate(ns_g_mctx, server, + NS_EVENT_RELOAD, + ns_server_reload, + server, + sizeof(isc_event_t)); + CHECKFATAL(server->reload_event == NULL ? + ISC_R_NOMEMORY : ISC_R_SUCCESS, + "allocating reload event"); + + server->tkeyctx = NULL; + CHECKFATAL(dns_tkeyctx_create(ns_g_mctx, ns_g_entropy, + &server->tkeyctx), + "creating TKEY context"); + + /* + * Setup the server task, which is responsible for coordinating + * startup and shutdown of the server, as well as all exclusive + * tasks. + */ + CHECKFATAL(isc_task_create(ns_g_taskmgr, 0, &server->task), + "creating server task"); + isc_task_setname(server->task, "server", server); + isc_taskmgr_setexcltask(ns_g_taskmgr, server->task); + CHECKFATAL(isc_task_onshutdown(server->task, shutdown_server, server), + "isc_task_onshutdown"); + CHECKFATAL(isc_app_onrun(ns_g_mctx, server->task, run_server, server), + "isc_app_onrun"); + + server->interface_timer = NULL; + server->heartbeat_timer = NULL; + server->pps_timer = NULL; + server->tat_timer = NULL; + + server->interface_interval = 0; + server->heartbeat_interval = 0; + + CHECKFATAL(dns_zonemgr_create(ns_g_mctx, ns_g_taskmgr, ns_g_timermgr, + ns_g_socketmgr, &server->zonemgr), + "dns_zonemgr_create"); + CHECKFATAL(dns_zonemgr_setsize(server->zonemgr, 1000), + "dns_zonemgr_setsize"); + + server->statsfile = isc_mem_strdup(server->mctx, "named.stats"); + CHECKFATAL(server->statsfile == NULL ? ISC_R_NOMEMORY : ISC_R_SUCCESS, + "isc_mem_strdup"); + server->nsstats = NULL; + server->rcvquerystats = NULL; + server->opcodestats = NULL; + server->rcodestats = NULL; + server->zonestats = NULL; + server->resolverstats = NULL; + server->sockstats = NULL; + server->udpinstats4 = NULL; + server->udpoutstats4 = NULL; + server->udpinstats6 = NULL; + server->udpoutstats6 = NULL; + server->tcpinstats4 = NULL; + server->tcpoutstats4 = NULL; + server->tcpinstats6 = NULL; + server->tcpoutstats6 = NULL; + CHECKFATAL(isc_stats_create(server->mctx, &server->sockstats, + isc_sockstatscounter_max), + "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"); + + server->hostname_set = false; + server->hostname = NULL; + server->version_set = false; + server->version = NULL; + server->server_usehostname = false; + server->server_id = NULL; + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->nsstats, + dns_nsstatscounter_max), + "dns_stats_create (server)"); + + CHECKFATAL(dns_rdatatypestats_create(ns_g_mctx, + &server->rcvquerystats), + "dns_stats_create (rcvquery)"); + + CHECKFATAL(dns_opcodestats_create(ns_g_mctx, &server->opcodestats), + "dns_stats_create (opcode)"); + + CHECKFATAL(dns_rcodestats_create(ns_g_mctx, &server->rcodestats), + "dns_stats_create (rcode)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->zonestats, + dns_zonestatscounter_max), + "dns_stats_create (zone)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->resolverstats, + dns_resstatscounter_max), + "dns_stats_create (resolver)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->udpinstats4, + dns_sizecounter_in_max), + "dns_stats_create (inbound UDP IPv4 traffic size)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->udpoutstats4, + dns_sizecounter_out_max), + "dns_stats_create (outbound UDP IPv4 traffic size)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->udpinstats6, + dns_sizecounter_in_max), + "dns_stats_create (inbound UDP IPv6 traffic size)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->udpoutstats6, + dns_sizecounter_out_max), + "dns_stats_create (outbound UDP IPv6 traffic size)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->tcpinstats4, + dns_sizecounter_in_max), + "dns_stats_create (inbound TCP IPv4 traffic size)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->tcpoutstats4, + dns_sizecounter_out_max), + "dns_stats_create (outbound TCP IPv4 traffic size)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->tcpinstats6, + dns_sizecounter_in_max), + "dns_stats_create (inbound TCP IPv6 traffic size)"); + + CHECKFATAL(isc_stats_create(ns_g_mctx, &server->tcpoutstats6, + dns_sizecounter_out_max), + "dns_stats_create (outbound TCP IPv6 traffic size)"); + + server->flushonshutdown = false; + server->log_queries = false; + + server->controls = NULL; + CHECKFATAL(ns_controls_create(server, &server->controls), + "ns_controls_create"); + server->dispatchgen = 0; + ISC_LIST_INIT(server->dispatches); + + ISC_LIST_INIT(server->statschannels); + + ISC_LIST_INIT(server->cachelist); + + ISC_LIST_INIT(server->altsecrets); + + server->sessionkey = NULL; + server->session_keyfile = NULL; + server->session_keyname = NULL; + server->session_keyalg = DST_ALG_UNKNOWN; + server->session_keybits = 0; + + server->lockfile = NULL; + + server->dtenv = NULL; + server->answercookie = true; + + server->magic = NS_SERVER_MAGIC; + *serverp = server; +} + +void +ns_server_destroy(ns_server_t **serverp) { + ns_server_t *server = *serverp; + REQUIRE(NS_SERVER_VALID(server)); + +#ifdef HAVE_DNSTAP + if (server->dtenv != NULL) + dns_dt_detach(&server->dtenv); +#endif /* HAVE_DNSTAP */ + + ns_controls_destroy(&server->controls); + + isc_stats_detach(&server->nsstats); + dns_stats_detach(&server->rcvquerystats); + dns_stats_detach(&server->opcodestats); + dns_stats_detach(&server->rcodestats); + isc_stats_detach(&server->zonestats); + isc_stats_detach(&server->resolverstats); + isc_stats_detach(&server->sockstats); + isc_stats_detach(&server->udpinstats4); + isc_stats_detach(&server->udpoutstats4); + isc_stats_detach(&server->udpinstats6); + isc_stats_detach(&server->udpoutstats6); + isc_stats_detach(&server->tcpinstats4); + isc_stats_detach(&server->tcpoutstats4); + isc_stats_detach(&server->tcpinstats6); + isc_stats_detach(&server->tcpoutstats6); + + 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) + isc_mem_free(server->mctx, server->version); + if (server->hostname != NULL) + isc_mem_free(server->mctx, server->hostname); + if (server->server_id != NULL) + isc_mem_free(server->mctx, server->server_id); + if (server->lockfile != NULL) + isc_mem_free(server->mctx, server->lockfile); + + if (server->zonemgr != NULL) + dns_zonemgr_detach(&server->zonemgr); + + if (server->tkeyctx != NULL) + dns_tkeyctx_destroy(&server->tkeyctx); + + dst_lib_destroy(); + + isc_event_free(&server->reload_event); + + INSIST(ISC_LIST_EMPTY(server->viewlist)); + INSIST(ISC_LIST_EMPTY(server->cachelist)); + + dns_aclenv_destroy(&server->aclenv); + + isc_quota_destroy(&server->recursionquota); + isc_quota_destroy(&server->tcpquota); + isc_quota_destroy(&server->xfroutquota); + + server->magic = 0; + isc_mem_put(server->mctx, server, sizeof(*server)); + *serverp = NULL; +} + +static void +fatal(const char *msg, isc_result_t result) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_CRITICAL, "%s: %s", msg, + isc_result_totext(result)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_CRITICAL, "exiting (due to fatal error)"); + ns_os_shutdown(); + exit(1); +} + +static void +start_reserved_dispatches(ns_server_t *server) { + + REQUIRE(NS_SERVER_VALID(server)); + + server->dispatchgen++; +} + +static void +end_reserved_dispatches(ns_server_t *server, bool all) { + ns_dispatch_t *dispatch, *nextdispatch; + + REQUIRE(NS_SERVER_VALID(server)); + + for (dispatch = ISC_LIST_HEAD(server->dispatches); + dispatch != NULL; + dispatch = nextdispatch) { + nextdispatch = ISC_LIST_NEXT(dispatch, link); + if (!all && server->dispatchgen == dispatch-> dispatchgen) + continue; + ISC_LIST_UNLINK(server->dispatches, dispatch, link); + dns_dispatch_detach(&dispatch->dispatch); + isc_mem_put(server->mctx, dispatch, sizeof(*dispatch)); + } +} + +void +ns_add_reserved_dispatch(ns_server_t *server, const isc_sockaddr_t *addr) { + ns_dispatch_t *dispatch; + in_port_t port; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_result_t result; + unsigned int attrs, attrmask; + + REQUIRE(NS_SERVER_VALID(server)); + + port = isc_sockaddr_getport(addr); + if (port == 0 || port >= 1024) + return; + + for (dispatch = ISC_LIST_HEAD(server->dispatches); + dispatch != NULL; + dispatch = ISC_LIST_NEXT(dispatch, link)) { + if (isc_sockaddr_equal(&dispatch->addr, addr)) + break; + } + if (dispatch != NULL) { + dispatch->dispatchgen = server->dispatchgen; + return; + } + + dispatch = isc_mem_get(server->mctx, sizeof(*dispatch)); + if (dispatch == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + dispatch->addr = *addr; + dispatch->dispatchgen = server->dispatchgen; + dispatch->dispatch = NULL; + + attrs = 0; + attrs |= DNS_DISPATCHATTR_UDP; + switch (isc_sockaddr_pf(addr)) { + case AF_INET: + attrs |= DNS_DISPATCHATTR_IPV4; + break; + case AF_INET6: + attrs |= DNS_DISPATCHATTR_IPV6; + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + attrmask = 0; + attrmask |= DNS_DISPATCHATTR_UDP; + attrmask |= DNS_DISPATCHATTR_TCP; + attrmask |= DNS_DISPATCHATTR_IPV4; + attrmask |= DNS_DISPATCHATTR_IPV6; + + result = dns_dispatch_getudp(ns_g_dispatchmgr, ns_g_socketmgr, + ns_g_taskmgr, &dispatch->addr, 4096, + UDPBUFFERS, 32768, 16411, 16433, + attrs, attrmask, &dispatch->dispatch); + if (result != ISC_R_SUCCESS) + goto cleanup; + + ISC_LIST_INITANDPREPEND(server->dispatches, dispatch, link); + + return; + + cleanup: + if (dispatch != NULL) + isc_mem_put(server->mctx, dispatch, sizeof(*dispatch)); + isc_sockaddr_format(addr, addrbuf, sizeof(addrbuf)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "unable to create dispatch for reserved port %s: %s", + addrbuf, isc_result_totext(result)); +} + + +static isc_result_t +loadconfig(ns_server_t *server) { + isc_result_t result; + start_reserved_dispatches(server); + result = load_configuration(ns_g_lwresdonly ? + lwresd_g_conffile : ns_g_conffile, + server, false); + if (result == ISC_R_SUCCESS) { + end_reserved_dispatches(server, false); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "reloading configuration succeeded"); + } else { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "reloading configuration failed: %s", + isc_result_totext(result)); + } + + return (result); +} + +static isc_result_t +reload(ns_server_t *server) { + isc_result_t result; + CHECK(loadconfig(server)); + + result = load_zones(server, false, false); + if (result == ISC_R_SUCCESS) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "reloading zones succeeded"); + else + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "reloading zones failed: %s", + isc_result_totext(result)); + + cleanup: + return (result); +} + +/* + * Handle a reload event (from SIGHUP). + */ +static void +ns_server_reload(isc_task_t *task, isc_event_t *event) { + ns_server_t *server = (ns_server_t *)event->ev_arg; + + INSIST(task == server->task); + UNUSED(task); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "received SIGHUP signal to reload zones"); + (void)reload(server); + + LOCK(&server->reload_event_lock); + INSIST(server->reload_event == NULL); + server->reload_event = event; + UNLOCK(&server->reload_event_lock); +} + +void +ns_server_reloadwanted(ns_server_t *server) { + LOCK(&server->reload_event_lock); + if (server->reload_event != NULL) + isc_task_send(server->task, &server->reload_event); + UNLOCK(&server->reload_event_lock); +} + +void +ns_server_scan_interfaces(ns_server_t *server) { + isc_result_t result; + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), + "automatic interface rescan"); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + scan_interfaces(server, true); + isc_task_endexclusive(server->task); +} + +/* + * Get the next token from lexer 'lex'. + * + * NOTE: the token value for string tokens always uses the same pointer + * value. Multiple calls to this function on the same lexer will always + * return either that value (lex->data) or NULL. It is necessary to copy + * the token into local storage if it needs to be referenced after the next + * call to next_token(). + */ +static char * +next_token(isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result; + isc_token_t token; + + token.type = isc_tokentype_unknown; + result = isc_lex_gettoken(lex, ISC_LEXOPT_EOF|ISC_LEXOPT_QSTRING, + &token); + + switch (result) { + case ISC_R_NOMORE: + (void) isc_lex_close(lex); + break; + case ISC_R_SUCCESS: + if (token.type == isc_tokentype_eof) + (void) isc_lex_close(lex); + break; + case ISC_R_NOSPACE: + if (text != NULL) { + (void) putstr(text, "token too large"); + (void) putnull(text); + } + return (NULL); + default: + if (text != NULL) { + (void) putstr(text, isc_result_totext(result)); + (void) putnull(text); + } + return (NULL); + } + + if (token.type == isc_tokentype_string || + token.type == isc_tokentype_qstring) + return (token.value.as_textregion.base); + + return (NULL); +} + +/* + * Find the zone specified in the control channel command, if any. + * If a zone is specified, point '*zonep' at it, otherwise + * set '*zonep' to NULL, and f 'zonename' is not NULL, copy + * the zone name into it (N.B. 'zonename' must have space to hold + * a full DNS name). + * + * If 'zonetxt' is set, the caller has already pulled a token + * off the command line that is to be used as the zone name. (This + * is sometimes done when it's necessary to check for an optional + * argument before the zone name, as in "rndc sync [-clean] zone".) + */ +static isc_result_t +zone_from_args(ns_server_t *server, isc_lex_t *lex, const char *zonetxt, + dns_zone_t **zonep, char *zonename, + isc_buffer_t **text, bool skip) +{ + char *ptr; + char *classtxt; + const char *viewtxt = NULL; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + dns_view_t *view = NULL; + dns_rdataclass_t rdclass; + char problem[DNS_NAME_FORMATSIZE + 500] = ""; + char zonebuf[DNS_NAME_FORMATSIZE]; + + REQUIRE(zonep != NULL && *zonep == NULL); + + if (skip) { + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + } + + /* Look for the zone name. */ + if (zonetxt == NULL) + zonetxt = next_token(lex, text); + if (zonetxt == NULL) + return (ISC_R_SUCCESS); + + /* Copy zonetxt because it'll be overwritten by next_token() */ + strlcpy(zonebuf, zonetxt, DNS_NAME_FORMATSIZE); + if (zonename != NULL) + strlcpy(zonename, zonetxt, DNS_NAME_FORMATSIZE); + + name = dns_fixedname_initname(&fname); + CHECK(dns_name_fromstring(name, zonebuf, 0, NULL)); + + /* Look for the optional class name. */ + classtxt = next_token(lex, text); + if (classtxt != NULL) { + isc_textregion_t r; + r.base = classtxt; + r.length = strlen(classtxt); + CHECK(dns_rdataclass_fromtext(&rdclass, &r)); + + /* Look for the optional view name. */ + viewtxt = next_token(lex, text); + } else + rdclass = dns_rdataclass_in; + + if (viewtxt == NULL) { + result = dns_viewlist_findzone(&server->viewlist, name, + (classtxt == NULL), + rdclass, zonep); + if (result == ISC_R_NOTFOUND) + snprintf(problem, sizeof(problem), + "no matching zone '%s' in any view", + zonebuf); + else if (result == ISC_R_MULTIPLE) + snprintf(problem, sizeof(problem), + "zone '%s' was found in multiple views", + zonebuf); + } else { + result = dns_viewlist_find(&server->viewlist, viewtxt, + rdclass, &view); + if (result != ISC_R_SUCCESS) { + snprintf(problem, sizeof(problem), + "no matching view '%s'", viewtxt); + goto report; + } + + result = dns_zt_find(view->zonetable, name, 0, NULL, zonep); + if (result != ISC_R_SUCCESS) + snprintf(problem, sizeof(problem), + "no matching zone '%s' in view '%s'", + zonebuf, viewtxt); + } + + /* Partial match? */ + if (result != ISC_R_SUCCESS && *zonep != NULL) + dns_zone_detach(zonep); + if (result == DNS_R_PARTIALMATCH) + result = ISC_R_NOTFOUND; + report: + if (result != ISC_R_SUCCESS) { + isc_result_t tresult; + + tresult = putstr(text, problem); + if (tresult == ISC_R_SUCCESS) + (void) putnull(text); + } + + cleanup: + if (view != NULL) + dns_view_detach(&view); + + return (result); +} + +/* + * Act on a "retransfer" command from the command channel. + */ +isc_result_t +ns_server_retransfercommand(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) +{ + isc_result_t result; + dns_zone_t *zone = NULL; + dns_zone_t *raw = NULL; + dns_zonetype_t type; + + result = zone_from_args(server, lex, NULL, &zone, NULL, + text, true); + if (result != ISC_R_SUCCESS) + return (result); + if (zone == NULL) + return (ISC_R_UNEXPECTEDEND); + dns_zone_getraw(zone, &raw); + if (raw != NULL) { + dns_zone_detach(&zone); + dns_zone_attach(raw, &zone); + dns_zone_detach(&raw); + } + type = dns_zone_gettype(zone); + if (type == dns_zone_slave || type == dns_zone_stub) + dns_zone_forcereload(zone); + else + result = ISC_R_NOTFOUND; + dns_zone_detach(&zone); + return (result); +} + +/* + * Act on a "reload" command from the command channel. + */ +isc_result_t +ns_server_reloadcommand(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) +{ + isc_result_t result; + dns_zone_t *zone = NULL; + dns_zonetype_t type; + const char *msg = NULL; + + result = zone_from_args(server, lex, NULL, &zone, NULL, + text, true); + if (result != ISC_R_SUCCESS) + return (result); + if (zone == NULL) { + result = reload(server); + if (result == ISC_R_SUCCESS) + msg = "server reload successful"; + } else { + type = dns_zone_gettype(zone); + if (type == dns_zone_slave || type == dns_zone_stub) { + dns_zone_refresh(zone); + dns_zone_detach(&zone); + msg = "zone refresh queued"; + } else { + result = dns_zone_load(zone); + dns_zone_detach(&zone); + switch (result) { + case ISC_R_SUCCESS: + msg = "zone reload successful"; + break; + case DNS_R_CONTINUE: + msg = "zone reload queued"; + result = ISC_R_SUCCESS; + break; + case DNS_R_UPTODATE: + msg = "zone reload up-to-date"; + result = ISC_R_SUCCESS; + break; + default: + /* failure message will be generated by rndc */ + break; + } + } + } + if (msg != NULL) { + (void) putstr(text, msg); + (void) putnull(text); + } + return (result); +} + +/* + * Act on a "reconfig" command from the command channel. + */ +isc_result_t +ns_server_reconfigcommand(ns_server_t *server) { + isc_result_t result; + + CHECK(loadconfig(server)); + + result = load_zones(server, false, true); + if (result == ISC_R_SUCCESS) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "scheduled loading new zones"); + else + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "loading new zones failed: %s", + isc_result_totext(result)); +cleanup: + return (result); +} + +/* + * Act on a "notify" command from the command channel. + */ +isc_result_t +ns_server_notifycommand(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) +{ + isc_result_t result; + dns_zone_t *zone = NULL; + const char msg[] = "zone notify queued"; + + result = zone_from_args(server, lex, NULL, &zone, NULL, + text, true); + if (result != ISC_R_SUCCESS) + return (result); + if (zone == NULL) + return (ISC_R_UNEXPECTEDEND); + + dns_zone_notify(zone); + dns_zone_detach(&zone); + (void) putstr(text, msg); + (void) putnull(text); + + return (ISC_R_SUCCESS); +} + +/* + * Act on a "refresh" command from the command channel. + */ +isc_result_t +ns_server_refreshcommand(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) +{ + isc_result_t result; + dns_zone_t *zone = NULL, *raw = NULL; + const char msg1[] = "zone refresh queued"; + const char msg2[] = "not a slave or stub zone"; + dns_zonetype_t type; + + result = zone_from_args(server, lex, NULL, &zone, NULL, + text, true); + if (result != ISC_R_SUCCESS) + return (result); + if (zone == NULL) + return (ISC_R_UNEXPECTEDEND); + + dns_zone_getraw(zone, &raw); + if (raw != NULL) { + dns_zone_detach(&zone); + dns_zone_attach(raw, &zone); + dns_zone_detach(&raw); + } + + type = dns_zone_gettype(zone); + if (type == dns_zone_slave || type == dns_zone_stub) { + dns_zone_refresh(zone); + dns_zone_detach(&zone); + (void) putstr(text, msg1); + (void) putnull(text); + return (ISC_R_SUCCESS); + } + + dns_zone_detach(&zone); + (void) putstr(text, msg2); + (void) putnull(text); + return (ISC_R_FAILURE); +} + +isc_result_t +ns_server_togglequerylog(ns_server_t *server, isc_lex_t *lex) { + bool value; + char *ptr; + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + ptr = next_token(lex, NULL); + if (ptr == NULL) { + value = server->log_queries ? false : true; + } else if (!strcasecmp(ptr, "on") || !strcasecmp(ptr, "yes") || + !strcasecmp(ptr, "enable") || !strcasecmp(ptr, "true")) { + value = true; + } else if (!strcasecmp(ptr, "off") || !strcasecmp(ptr, "no") || + !strcasecmp(ptr, "disable") || !strcasecmp(ptr, "false")) { + value = false; + } else { + return (DNS_R_SYNTAX); + } + + if (server->log_queries == value) + return (ISC_R_SUCCESS); + + server->log_queries = value; + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "query logging is now %s", + server->log_queries ? "on" : "off"); + return (ISC_R_SUCCESS); +} + +static isc_result_t +ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, + uint16_t family, ns_listenlist_t **target) +{ + isc_result_t result; + const cfg_listelt_t *element; + ns_listenlist_t *dlist = NULL; + + REQUIRE(target != NULL && *target == NULL); + + result = ns_listenlist_create(mctx, &dlist); + if (result != ISC_R_SUCCESS) + return (result); + + for (element = cfg_list_first(listenlist); + element != NULL; + element = cfg_list_next(element)) + { + ns_listenelt_t *delt = NULL; + const cfg_obj_t *listener = cfg_listelt_value(element); + result = ns_listenelt_fromconfig(listener, config, actx, + mctx, family, &delt); + if (result != ISC_R_SUCCESS) + goto cleanup; + ISC_LIST_APPEND(dlist->elts, delt, link); + } + *target = dlist; + return (ISC_R_SUCCESS); + + cleanup: + ns_listenlist_detach(&dlist); + return (result); +} + +/* + * Create a listen list from the corresponding configuration + * data structure. + */ +static isc_result_t +ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, + uint16_t family, ns_listenelt_t **target) +{ + isc_result_t result; + const cfg_obj_t *portobj, *dscpobj; + in_port_t port; + isc_dscp_t dscp = -1; + ns_listenelt_t *delt = NULL; + REQUIRE(target != NULL && *target == NULL); + + portobj = cfg_tuple_get(listener, "port"); + if (!cfg_obj_isuint32(portobj)) { + if (ns_g_port != 0) { + port = ns_g_port; + } else { + result = ns_config_getport(config, &port); + if (result != ISC_R_SUCCESS) + return (result); + } + } else { + if (cfg_obj_asuint32(portobj) >= UINT16_MAX) { + cfg_obj_log(portobj, ns_g_lctx, ISC_LOG_ERROR, + "port value '%u' is out of range", + cfg_obj_asuint32(portobj)); + return (ISC_R_RANGE); + } + port = (in_port_t)cfg_obj_asuint32(portobj); + } + + dscpobj = cfg_tuple_get(listener, "dscp"); + if (!cfg_obj_isuint32(dscpobj)) + dscp = ns_g_dscp; + else { + if (cfg_obj_asuint32(dscpobj) > 63) { + cfg_obj_log(dscpobj, ns_g_lctx, ISC_LOG_ERROR, + "dscp value '%u' is out of range", + cfg_obj_asuint32(dscpobj)); + return (ISC_R_RANGE); + } + dscp = (isc_dscp_t)cfg_obj_asuint32(dscpobj); + } + + result = ns_listenelt_create(mctx, port, dscp, NULL, &delt); + if (result != ISC_R_SUCCESS) + return (result); + + result = cfg_acl_fromconfig2(cfg_tuple_get(listener, "acl"), + config, ns_g_lctx, actx, mctx, 0, + family, &delt->acl); + if (result != ISC_R_SUCCESS) { + ns_listenelt_destroy(delt); + return (result); + } + *target = delt; + return (ISC_R_SUCCESS); +} + +isc_result_t +ns_server_dumpstats(ns_server_t *server) { + isc_result_t result; + FILE *fp = NULL; + + CHECKMF(isc_stdio_open(server->statsfile, "a", &fp), + "could not open statistics dump file", server->statsfile); + + result = ns_stats_dump(server, fp); + + cleanup: + 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, + "dumpstats complete"); + else + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dumpstats failed: %s", + dns_result_totext(result)); + return (result); +} + +static isc_result_t +add_zone_tolist(dns_zone_t *zone, void *uap) { + struct dumpcontext *dctx = uap; + struct zonelistentry *zle; + + zle = isc_mem_get(dctx->mctx, sizeof *zle); + if (zle == NULL) + return (ISC_R_NOMEMORY); + zle->zone = NULL; + dns_zone_attach(zone, &zle->zone); + ISC_LINK_INIT(zle, link); + ISC_LIST_APPEND(ISC_LIST_TAIL(dctx->viewlist)->zonelist, zle, link); + return (ISC_R_SUCCESS); +} + +static isc_result_t +add_view_tolist(struct dumpcontext *dctx, dns_view_t *view) { + struct viewlistentry *vle; + isc_result_t result = ISC_R_SUCCESS; + + /* + * Prevent duplicate views. + */ + for (vle = ISC_LIST_HEAD(dctx->viewlist); + vle != NULL; + vle = ISC_LIST_NEXT(vle, link)) + if (vle->view == view) + return (ISC_R_SUCCESS); + + vle = isc_mem_get(dctx->mctx, sizeof *vle); + if (vle == NULL) + return (ISC_R_NOMEMORY); + vle->view = NULL; + dns_view_attach(view, &vle->view); + ISC_LINK_INIT(vle, link); + ISC_LIST_INIT(vle->zonelist); + ISC_LIST_APPEND(dctx->viewlist, vle, link); + if (dctx->dumpzones) + result = dns_zt_apply(view->zonetable, true, + add_zone_tolist, dctx); + return (result); +} + +static void +dumpcontext_destroy(struct dumpcontext *dctx) { + struct viewlistentry *vle; + struct zonelistentry *zle; + + vle = ISC_LIST_HEAD(dctx->viewlist); + while (vle != NULL) { + ISC_LIST_UNLINK(dctx->viewlist, vle, link); + zle = ISC_LIST_HEAD(vle->zonelist); + while (zle != NULL) { + ISC_LIST_UNLINK(vle->zonelist, zle, link); + dns_zone_detach(&zle->zone); + isc_mem_put(dctx->mctx, zle, sizeof *zle); + zle = ISC_LIST_HEAD(vle->zonelist); + } + dns_view_detach(&vle->view); + isc_mem_put(dctx->mctx, vle, sizeof *vle); + vle = ISC_LIST_HEAD(dctx->viewlist); + } + if (dctx->version != NULL) + dns_db_closeversion(dctx->db, &dctx->version, false); + if (dctx->db != NULL) + dns_db_detach(&dctx->db); + if (dctx->cache != NULL) + dns_db_detach(&dctx->cache); + if (dctx->task != NULL) + isc_task_detach(&dctx->task); + if (dctx->fp != NULL) + (void)isc_stdio_close(dctx->fp); + if (dctx->mdctx != NULL) + dns_dumpctx_detach(&dctx->mdctx); + isc_mem_put(dctx->mctx, dctx, sizeof *dctx); +} + +static void +dumpdone(void *arg, isc_result_t result) { + struct dumpcontext *dctx = arg; + char buf[1024+32]; + const dns_master_style_t *style; + + if (result != ISC_R_SUCCESS) + goto cleanup; + if (dctx->mdctx != NULL) + dns_dumpctx_detach(&dctx->mdctx); + if (dctx->view == NULL) { + dctx->view = ISC_LIST_HEAD(dctx->viewlist); + if (dctx->view == NULL) + goto done; + INSIST(dctx->zone == NULL); + } else + goto resume; + nextview: + fprintf(dctx->fp, ";\n; Start view %s\n;\n", dctx->view->view->name); + resume: + 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' (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, + dctx->task, + dumpdone, dctx, + &dctx->mdctx); + if (result == DNS_R_CONTINUE) + return; + if (result == ISC_R_NOTIMPLEMENTED) + fprintf(dctx->fp, "; %s\n", + dns_result_totext(result)); + else if (result != ISC_R_SUCCESS) + goto cleanup; + } + } + + if ((dctx->dumpadb || dctx->dumpbad || dctx->dumpfail) && + dctx->cache == NULL && dctx->view->view->cachedb != NULL) + dns_db_attach(dctx->view->view->cachedb, &dctx->cache); + + if (dctx->cache != NULL) { + if (dctx->dumpadb) + dns_adb_dump(dctx->view->view->adb, dctx->fp); + if (dctx->dumpbad) + dns_resolver_printbadcache(dctx->view->view->resolver, + dctx->fp); + if (dctx->dumpfail) + dns_badcache_print(dctx->view->view->failcache, + "SERVFAIL cache", dctx->fp); + dns_db_detach(&dctx->cache); + } + if (dctx->dumpzones) { + style = &dns_master_style_full; + nextzone: + if (dctx->version != NULL) + dns_db_closeversion(dctx->db, &dctx->version, + false); + if (dctx->db != NULL) + dns_db_detach(&dctx->db); + if (dctx->zone == NULL) + dctx->zone = ISC_LIST_HEAD(dctx->view->zonelist); + else + dctx->zone = ISC_LIST_NEXT(dctx->zone, link); + if (dctx->zone != NULL) { + /* start zone dump */ + dns_zone_name(dctx->zone->zone, buf, sizeof(buf)); + fprintf(dctx->fp, ";\n; Zone dump of '%s'\n;\n", buf); + result = dns_zone_getdb(dctx->zone->zone, &dctx->db); + if (result != ISC_R_SUCCESS) { + fprintf(dctx->fp, "; %s\n", + dns_result_totext(result)); + goto nextzone; + } + dns_db_currentversion(dctx->db, &dctx->version); + result = dns_master_dumptostreaminc(dctx->mctx, + dctx->db, + dctx->version, + style, dctx->fp, + dctx->task, + dumpdone, dctx, + &dctx->mdctx); + if (result == DNS_R_CONTINUE) + return; + if (result == ISC_R_NOTIMPLEMENTED) { + fprintf(dctx->fp, "; %s\n", + dns_result_totext(result)); + result = ISC_R_SUCCESS; + POST(result); + goto nextzone; + } + if (result != ISC_R_SUCCESS) + goto cleanup; + } + } + if (dctx->view != NULL) + dctx->view = ISC_LIST_NEXT(dctx->view, link); + if (dctx->view != NULL) + goto nextview; + done: + fprintf(dctx->fp, "; Dump complete\n"); + result = isc_stdio_flush(dctx->fp); + if (result == ISC_R_SUCCESS) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumpdb complete"); + cleanup: + if (result != ISC_R_SUCCESS) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dumpdb failed: %s", dns_result_totext(result)); + dumpcontext_destroy(dctx); +} + +isc_result_t +ns_server_dumpdb(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { + struct dumpcontext *dctx = NULL; + dns_view_t *view; + isc_result_t result; + char *ptr; + const char *sep; + bool found; + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + dctx = isc_mem_get(server->mctx, sizeof(*dctx)); + if (dctx == NULL) + return (ISC_R_NOMEMORY); + + dctx->mctx = server->mctx; + dctx->dumpcache = true; + dctx->dumpadb = true; + dctx->dumpbad = true; + dctx->dumpfail = true; + dctx->dumpzones = false; + dctx->fp = NULL; + ISC_LIST_INIT(dctx->viewlist); + dctx->view = NULL; + dctx->zone = NULL; + dctx->cache = NULL; + dctx->mdctx = NULL; + dctx->db = NULL; + dctx->cache = NULL; + dctx->task = NULL; + dctx->version = NULL; + isc_task_attach(server->task, &dctx->task); + + CHECKMF(isc_stdio_open(server->dumpfile, "w", &dctx->fp), + "could not open dump file", server->dumpfile); + + ptr = next_token(lex, NULL); + sep = (ptr == NULL) ? "" : ": "; + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumpdb started%s%s", sep, (ptr != NULL) ? ptr : ""); + + if (ptr != NULL && strcmp(ptr, "-all") == 0) { + /* also dump zones */ + dctx->dumpzones = true; + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-cache") == 0) { + /* this is the default */ + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-zones") == 0) { + /* only dump zones, suppress caches */ + dctx->dumpadb = false; + dctx->dumpbad = false; + dctx->dumpcache = false; + dctx->dumpfail = false; + dctx->dumpzones = true; + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-adb") == 0) { + /* only dump adb, suppress other caches */ + dctx->dumpbad = false; + dctx->dumpcache = false; + dctx->dumpfail = false; + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-bad") == 0) { + /* only dump badcache, suppress other caches */ + dctx->dumpadb = false; + dctx->dumpcache = false; + dctx->dumpfail = false; + ptr = next_token(lex, NULL); + } else if (ptr != NULL && strcmp(ptr, "-fail") == 0) { + /* only dump servfail cache, suppress other caches */ + dctx->dumpadb = false; + dctx->dumpbad = false; + dctx->dumpcache = false; + ptr = next_token(lex, NULL); + } + + nextview: + found = false; + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (ptr != NULL && strcmp(view->name, ptr) != 0) + continue; + found = true; + CHECK(add_view_tolist(dctx, view)); + } + if (ptr != NULL) { + if (!found) { + putstr(text, "view '"); + putstr(text, ptr); + putstr(text, "' not found"); + putnull(text); + result = ISC_R_NOTFOUND; + dumpdone(dctx, result); + return (result); + } + ptr = next_token(lex, NULL); + if (ptr != NULL) + goto nextview; + } + dumpdone(dctx, ISC_R_SUCCESS); + return (ISC_R_SUCCESS); + + cleanup: + if (dctx != NULL) + dumpcontext_destroy(dctx); + return (result); +} + +isc_result_t +ns_server_dumpsecroots(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) +{ + dns_view_t *view; + dns_keytable_t *secroots = NULL; + dns_ntatable_t *ntatable = NULL; + isc_result_t result; + char *ptr; + FILE *fp = NULL; + isc_time_t now; + char tbuf[64]; + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + /* "-" here means print the output instead of dumping to file */ + ptr = next_token(lex, text); + if (ptr != NULL && strcmp(ptr, "-") == 0) + ptr = next_token(lex, text); + else { + result = isc_stdio_open(server->secrootsfile, "w", &fp); + if (result != ISC_R_SUCCESS) { + (void) putstr(text, "could not open "); + (void) putstr(text, server->secrootsfile); + CHECKMF(result, "could not open secroots dump file", + server->secrootsfile); + } + } + + TIME_NOW(&now); + isc_time_formattimestamp(&now, tbuf, sizeof(tbuf)); + CHECK(putstr(text, "secure roots as of ")); + CHECK(putstr(text, tbuf)); + CHECK(putstr(text, ":\n")); + + do { + 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; + } + CHECK(putstr(text, "\n Start view ")); + CHECK(putstr(text, view->name)); + CHECK(putstr(text, "\n Secure roots:\n\n")); + CHECK(dns_keytable_totext(secroots, text)); + + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + continue; + } + CHECK(putstr(text, "\n Negative trust anchors:\n\n")); + CHECK(dns_ntatable_totext(ntatable, text)); + } + if (ptr != NULL) + ptr = next_token(lex, text); + } while (ptr != NULL); + + cleanup: + if (isc_buffer_usedlength(*text) > 0) { + if (fp != NULL) + (void)putstr(text, "\n"); + else + (void)putnull(text); + } + if (secroots != NULL) + dns_keytable_detach(&secroots); + if (ntatable != NULL) + dns_ntatable_detach(&ntatable); + if (fp != NULL) { + fprintf(fp, "%.*s", (int) isc_buffer_usedlength(*text), + (char *) isc_buffer_base(*text)); + isc_buffer_clear(*text); + (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; + dns_view_t *view; + isc_result_t result; + + CHECKMF(isc_stdio_open(server->recfile, "w", &fp), + "could not open dump file", server->recfile); + fprintf(fp, ";\n; Recursing Queries\n;\n"); + ns_interfacemgr_dumprecursing(fp, server->interfacemgr); + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + fprintf(fp, ";\n; Active fetch domains [view: %s]\n;\n", + view->name); + dns_resolver_dumpfetches(view->resolver, + isc_statsformat_file, fp); + } + + fprintf(fp, "; Dump complete\n"); + + cleanup: + if (fp != NULL) + result = isc_stdio_close(fp); + if (result == ISC_R_SUCCESS) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumprecursing complete"); + else + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "dumprecursing failed: %s", + dns_result_totext(result)); + return (result); +} + +isc_result_t +ns_server_setdebuglevel(ns_server_t *server, isc_lex_t *lex) { + char *ptr; + char *endp; + long newlevel; + + UNUSED(server); + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + /* Look for the new level name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) { + if (ns_g_debuglevel < 99) + ns_g_debuglevel++; + } else { + newlevel = strtol(ptr, &endp, 10); + if (*endp != '\0' || newlevel < 0 || newlevel > 99) + return (ISC_R_RANGE); + ns_g_debuglevel = (unsigned int)newlevel; + } + isc_log_setdebuglevel(ns_g_lctx, ns_g_debuglevel); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "debug level is now %u", ns_g_debuglevel); + return (ISC_R_SUCCESS); +} + +isc_result_t +ns_server_validation(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) +{ + char *ptr; + dns_view_t *view; + bool changed = false; + isc_result_t result; + bool enable = true, set = true, first = true; + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + /* Find out what we are to do. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + if (!strcasecmp(ptr, "on") || !strcasecmp(ptr, "yes") || + !strcasecmp(ptr, "enable") || !strcasecmp(ptr, "true")) { + enable = true; + } else if (!strcasecmp(ptr, "off") || !strcasecmp(ptr, "no") || + !strcasecmp(ptr, "disable") || !strcasecmp(ptr, "false")) { + enable = false; + } else if (!strcasecmp(ptr, "check") || !strcasecmp(ptr, "status")) { + set = false; + } else { + return (DNS_R_SYNTAX); + } + + /* Look for the view name. */ + ptr = next_token(lex, text); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (ptr != NULL && strcasecmp(ptr, view->name) != 0) + continue; + CHECK(dns_view_flushcache(view)); + + if (set) { + view->enablevalidation = enable; + changed = true; + } else { + if (!first) + CHECK(putstr(text, "\n")); + CHECK(putstr(text, "DNSSEC validation is ")); + CHECK(putstr(text, view->enablevalidation + ? "enabled" : "disabled")); + CHECK(putstr(text, " (view ")); + CHECK(putstr(text, view->name)); + CHECK(putstr(text, ")")); + CHECK(putnull(text)); + first = false; + } + } + + if (!set) + result = ISC_R_SUCCESS; + else if (changed) + result = ISC_R_SUCCESS; + else + result = ISC_R_FAILURE; + cleanup: + isc_task_endexclusive(server->task); + return (result); +} + +isc_result_t +ns_server_flushcache(ns_server_t *server, isc_lex_t *lex) { + char *ptr; + dns_view_t *view; + bool flushed; + bool found; + isc_result_t result; + ns_cache_t *nsc; + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + /* Look for the view name. */ + ptr = next_token(lex, NULL); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + flushed = true; + found = false; + + /* + * 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 (ptr != 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(ptr, view->name) != 0) + continue; + found = 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 = true; + } + } else + found = true; + + /* Perform flush */ + for (nsc = ISC_LIST_HEAD(server->cachelist); + nsc != NULL; + nsc = ISC_LIST_NEXT(nsc, link)) { + if (ptr != NULL && !nsc->needflush) + continue; + nsc->needflush = true; + result = dns_view_flushcache2(nsc->primaryview, false); + if (result != ISC_R_SUCCESS) { + flushed = false; + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "flushing cache in view '%s' failed: %s", + 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, true); + if (result != ISC_R_SUCCESS) { + flushed = 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 = false; + } + + if (flushed && found) { + if (ptr != NULL) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "flushing cache in view '%s' succeeded", + ptr); + else + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "flushing caches in all views succeeded"); + result = ISC_R_SUCCESS; + } else { + if (!found) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "flushing cache in view '%s' failed: " + "view not found", ptr); + result = ISC_R_NOTFOUND; + } else + result = ISC_R_FAILURE; + } + isc_task_endexclusive(server->task); + return (result); +} + +isc_result_t +ns_server_flushnode(ns_server_t *server, isc_lex_t *lex, bool tree) { + char *ptr, *viewname; + char target[DNS_NAME_FORMATSIZE]; + dns_view_t *view; + bool flushed; + bool found; + isc_result_t result; + isc_buffer_t b; + dns_fixedname_t fixed; + dns_name_t *name; + + /* Skip the command name. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + /* Find the domain name to flush. */ + ptr = next_token(lex, NULL); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + strlcpy(target, ptr, DNS_NAME_FORMATSIZE); + isc_buffer_constinit(&b, target, strlen(target)); + isc_buffer_add(&b, strlen(target)); + name = dns_fixedname_initname(&fixed); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) + return (result); + + /* Look for the view name. */ + viewname = next_token(lex, NULL); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + flushed = true; + found = 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; + found = 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_flushnode(view, name, tree); + if (result != ISC_R_SUCCESS) { + flushed = false; + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "flushing %s '%s' in cache view '%s' " + "failed: %s", + tree ? "tree" : "name", + target, view->name, + isc_result_totext(result)); + } + } + if (flushed && found) { + if (viewname != NULL) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "flushing %s '%s' in cache view '%s' " + "succeeded", + tree ? "tree" : "name", + target, viewname); + else + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "flushing %s '%s' in all cache views " + "succeeded", + tree ? "tree" : "name", + target); + result = ISC_R_SUCCESS; + } else { + if (!found) + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "flushing %s '%s' in cache view '%s' " + "failed: view not found", + tree ? "tree" : "name", + target, viewname); + result = ISC_R_FAILURE; + } + isc_task_endexclusive(server->task); + return (result); +} + +isc_result_t +ns_server_status(ns_server_t *server, isc_buffer_t **text) { + isc_result_t result; + unsigned int zonecount, xferrunning, xferdeferred, soaqueries; + unsigned int automatic; + const char *ob = "", *cb = "", *alt = ""; + char boottime[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char configtime[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char line[1024], hostname[256]; + + if (ns_g_server->version_set) { + ob = " ("; + cb = ")"; + if (ns_g_server->version == NULL) + alt = "version.bind/txt/ch disabled"; + else + alt = ns_g_server->version; + } + zonecount = dns_zonemgr_getcount(server->zonemgr, DNS_ZONESTATE_ANY); + xferrunning = dns_zonemgr_getcount(server->zonemgr, + DNS_ZONESTATE_XFERRUNNING); + xferdeferred = dns_zonemgr_getcount(server->zonemgr, + DNS_ZONESTATE_XFERDEFERRED); + soaqueries = dns_zonemgr_getcount(server->zonemgr, + DNS_ZONESTATE_SOAQUERY); + automatic = dns_zonemgr_getcount(server->zonemgr, + DNS_ZONESTATE_AUTOMATIC); + + isc_time_formathttptimestamp(&ns_g_boottime, boottime, + sizeof(boottime)); + isc_time_formathttptimestamp(&ns_g_configtime, configtime, + sizeof(configtime)); + + snprintf(line, sizeof(line), "version: %s %s%s%s <id:%s>%s%s%s\n", + ns_g_product, ns_g_version, + (*ns_g_description != '\0') ? " " : "", + ns_g_description, ns_g_srcid, ob, alt, cb); + CHECK(putstr(text, line)); + + result = ns_os_gethostname(hostname, sizeof(hostname)); + if (result != ISC_R_SUCCESS) + strlcpy(hostname, "localhost", sizeof(hostname)); + snprintf(line, sizeof(line), "running on %s: %s\n", + hostname, ns_os_uname()); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "boot time: %s\n", boottime); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "last configured: %s\n", configtime); + CHECK(putstr(text, line)); + + if (ns_g_chrootdir != NULL) { + snprintf(line, sizeof(line), "configuration file: %s (%s%s)\n", + ns_g_conffile, ns_g_chrootdir, ns_g_conffile); + } else { + snprintf(line, sizeof(line), "configuration file: %s\n", + ns_g_conffile); + } + CHECK(putstr(text, line)); + +#ifdef ISC_PLATFORM_USETHREADS + snprintf(line, sizeof(line), "CPUs found: %u\n", ns_g_cpus_detected); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "worker threads: %u\n", ns_g_cpus); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "UDP listeners per interface: %u\n", + ns_g_udpdisp); + CHECK(putstr(text, line)); +#else + snprintf(line, sizeof(line), "CPUs found: N/A (threads disabled)\n"); + CHECK(putstr(text, line)); +#endif + + snprintf(line, sizeof(line), "number of zones: %u (%u automatic)\n", + zonecount, automatic); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "debug level: %u\n", ns_g_debuglevel); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "xfers running: %u\n", xferrunning); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "xfers deferred: %u\n", xferdeferred); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "soa queries in progress: %u\n", + soaqueries); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "query logging is %s\n", + server->log_queries ? "ON" : "OFF"); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "recursive clients: %d/%d/%d\n", + server->recursionquota.used, server->recursionquota.soft, + server->recursionquota.max); + CHECK(putstr(text, line)); + + snprintf(line, sizeof(line), "tcp clients: %d/%d\n", + server->tcpquota.used, server->tcpquota.max); + CHECK(putstr(text, line)); + + CHECK(putstr(text, "server is up and running")); + CHECK(putnull(text)); + + return (ISC_R_SUCCESS); + cleanup: + return (result); +} + +isc_result_t +ns_server_testgen(isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result; + char *ptr; + unsigned long count; + unsigned long i; + const unsigned char chars[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + ptr = next_token(lex, text); + if (ptr == NULL) + count = 26; + else + count = strtoul(ptr, NULL, 10); + + CHECK(isc_buffer_reserve(text, count)); + for (i = 0; i < count; i++) + CHECK(putuint8(text, chars[i % (sizeof(chars) - 1)])); + + CHECK(putnull(text)); + + cleanup: + return (result); +} + +static isc_result_t +delete_keynames(dns_tsig_keyring_t *ring, char *target, + unsigned int *foundkeys) +{ + char namestr[DNS_NAME_FORMATSIZE]; + isc_result_t result; + dns_rbtnodechain_t chain; + dns_name_t foundname; + dns_fixedname_t fixedorigin; + dns_name_t *origin; + dns_rbtnode_t *node; + dns_tsigkey_t *tkey; + + dns_name_init(&foundname, NULL); + origin = dns_fixedname_initname(&fixedorigin); + + again: + dns_rbtnodechain_init(&chain, ring->mctx); + result = dns_rbtnodechain_first(&chain, ring->keys, &foundname, + origin); + if (result == ISC_R_NOTFOUND) { + dns_rbtnodechain_invalidate(&chain); + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + return (result); + } + + for (;;) { + node = NULL; + dns_rbtnodechain_current(&chain, &foundname, origin, &node); + tkey = node->data; + + if (tkey != NULL) { + if (!tkey->generated) + goto nextkey; + + dns_name_format(&tkey->name, namestr, sizeof(namestr)); + if (strcmp(namestr, target) == 0) { + (*foundkeys)++; + dns_rbtnodechain_invalidate(&chain); + (void)dns_rbt_deletename(ring->keys, + &tkey->name, + false); + goto again; + } + } + + nextkey: + result = dns_rbtnodechain_next(&chain, &foundname, origin); + if (result == ISC_R_NOMORE) + break; + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + return (result); + } + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +ns_server_tsigdelete(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) +{ + isc_result_t result; + dns_view_t *view; + unsigned int foundkeys = 0; + char *ptr, *viewname; + char target[DNS_NAME_FORMATSIZE]; + char fbuf[16]; + + (void)next_token(lex, text); /* skip command name */ + + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + strlcpy(target, ptr, DNS_NAME_FORMATSIZE); + + viewname = next_token(lex, text); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) { + if (viewname == NULL || strcmp(view->name, viewname) == 0) { + RWLOCK(&view->dynamickeys->lock, isc_rwlocktype_write); + result = delete_keynames(view->dynamickeys, target, + &foundkeys); + RWUNLOCK(&view->dynamickeys->lock, + isc_rwlocktype_write); + if (result != ISC_R_SUCCESS) { + isc_task_endexclusive(server->task); + return (result); + } + } + } + isc_task_endexclusive(server->task); + + snprintf(fbuf, sizeof(fbuf), "%u", foundkeys); + + CHECK(putstr(text, fbuf)); + CHECK(putstr(text, " tsig keys deleted.")); + CHECK(putnull(text)); + + cleanup: + return (result); +} + +static isc_result_t +list_keynames(dns_view_t *view, dns_tsig_keyring_t *ring, isc_buffer_t **text, + unsigned int *foundkeys) +{ + char namestr[DNS_NAME_FORMATSIZE]; + char creatorstr[DNS_NAME_FORMATSIZE]; + isc_result_t result; + dns_rbtnodechain_t chain; + dns_name_t foundname; + dns_fixedname_t fixedorigin; + dns_name_t *origin; + dns_rbtnode_t *node; + dns_tsigkey_t *tkey; + const char *viewname; + + if (view != NULL) + viewname = view->name; + else + viewname = "(global)"; + + dns_name_init(&foundname, NULL); + origin = dns_fixedname_initname(&fixedorigin); + dns_rbtnodechain_init(&chain, ring->mctx); + result = dns_rbtnodechain_first(&chain, ring->keys, &foundname, + origin); + if (result == ISC_R_NOTFOUND) { + dns_rbtnodechain_invalidate(&chain); + return (ISC_R_SUCCESS); + } + if (result != ISC_R_SUCCESS && result != DNS_R_NEWORIGIN) { + dns_rbtnodechain_invalidate(&chain); + return (result); + } + + for (;;) { + node = NULL; + dns_rbtnodechain_current(&chain, &foundname, origin, &node); + tkey = node->data; + + if (tkey != NULL) { + dns_name_format(&tkey->name, namestr, sizeof(namestr)); + if (tkey->generated) { + dns_name_format(tkey->creator, creatorstr, + sizeof(creatorstr)); + if (*foundkeys != 0) + CHECK(putstr(text, "\n")); + CHECK(putstr(text, "view \"")); + CHECK(putstr(text, viewname)); + CHECK(putstr(text, + "\"; type \"dynamic\"; key \"")); + CHECK(putstr(text, namestr)); + CHECK(putstr(text, "\"; creator \"")); + CHECK(putstr(text, creatorstr)); + CHECK(putstr(text, "\";")); + } else { + if (*foundkeys != 0) + CHECK(putstr(text, "\n")); + CHECK(putstr(text, "view \"")); + CHECK(putstr(text, viewname)); + CHECK(putstr(text, + "\"; type \"static\"; key \"")); + CHECK(putstr(text, namestr)); + CHECK(putstr(text, "\";")); + } + (*foundkeys)++; + } + result = dns_rbtnodechain_next(&chain, &foundname, origin); + if (result == ISC_R_NOMORE || result == DNS_R_NEWORIGIN) + break; + } + + return (ISC_R_SUCCESS); + cleanup: + dns_rbtnodechain_invalidate(&chain); + return (result); +} + +isc_result_t +ns_server_tsiglist(ns_server_t *server, isc_buffer_t **text) { + isc_result_t result; + dns_view_t *view; + unsigned int foundkeys = 0; + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) { + RWLOCK(&view->statickeys->lock, isc_rwlocktype_read); + result = list_keynames(view, view->statickeys, text, + &foundkeys); + RWUNLOCK(&view->statickeys->lock, isc_rwlocktype_read); + if (result != ISC_R_SUCCESS) { + isc_task_endexclusive(server->task); + return (result); + } + RWLOCK(&view->dynamickeys->lock, isc_rwlocktype_read); + result = list_keynames(view, view->dynamickeys, text, + &foundkeys); + RWUNLOCK(&view->dynamickeys->lock, isc_rwlocktype_read); + if (result != ISC_R_SUCCESS) { + isc_task_endexclusive(server->task); + return (result); + } + } + isc_task_endexclusive(server->task); + + if (foundkeys == 0) + CHECK(putstr(text, "no tsig keys found.")); + + if (isc_buffer_usedlength(*text) > 0) + CHECK(putnull(text)); + + cleanup: + return (result); +} + +/* + * Act on a "sign" or "loadkeys" command from the command channel. + */ +isc_result_t +ns_server_rekey(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result; + dns_zone_t *zone = NULL; + dns_zonetype_t type; + uint16_t keyopts; + bool fullsign = false; + char *ptr; + + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + if (strcasecmp(ptr, NS_COMMAND_SIGN) == 0) + fullsign = true; + + result = zone_from_args(server, lex, NULL, &zone, NULL, + text, false); + 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 "sync" command from the command channel. +*/ +static isc_result_t +synczone(dns_zone_t *zone, void *uap) { + bool cleanup = *(bool *)uap; + isc_result_t result; + dns_zone_t *raw = NULL; + char *journal; + + dns_zone_getraw(zone, &raw); + if (raw != NULL) { + synczone(raw, uap); + dns_zone_detach(&raw); + } + + result = dns_zone_flush(zone); + if (result != ISC_R_SUCCESS) + cleanup = false; + if (cleanup) { + journal = dns_zone_getjournal(zone); + if (journal != NULL) + (void)isc_file_remove(journal); + } + + return (result); +} + +isc_result_t +ns_server_sync(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result, tresult; + dns_view_t *view; + dns_zone_t *zone = NULL; + char classstr[DNS_RDATACLASS_FORMATSIZE]; + char zonename[DNS_NAME_FORMATSIZE]; + const char *vname, *sep, *arg; + bool cleanup = false; + + (void) next_token(lex, text); + + arg = next_token(lex, text); + if (arg != NULL && + (strcmp(arg, "-clean") == 0 || strcmp(arg, "-clear") == 0)) { + cleanup = true; + arg = next_token(lex, text); + } + + result = zone_from_args(server, lex, arg, &zone, NULL, + text, false); + if (result != ISC_R_SUCCESS) + return (result); + + if (zone == NULL) { + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + tresult = ISC_R_SUCCESS; + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) { + result = dns_zt_apply(view->zonetable, false, + synczone, &cleanup); + if (result != ISC_R_SUCCESS && + tresult == ISC_R_SUCCESS) + tresult = result; + } + isc_task_endexclusive(server->task); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "dumping all zones%s: %s", + cleanup ? ", removing journal files" : "", + isc_result_totext(result)); + return (tresult); + } + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = synczone(zone, &cleanup); + isc_task_endexclusive(server->task); + + view = dns_zone_getview(zone); + if (strcmp(view->name, "_default") == 0 || + strcmp(view->name, "_bind") == 0) + { + vname = ""; + sep = ""; + } else { + vname = view->name; + sep = " "; + } + dns_rdataclass_format(dns_zone_getclass(zone), classstr, + sizeof(classstr)); + dns_name_format(dns_zone_getorigin(zone), + zonename, sizeof(zonename)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "sync: dumping zone '%s/%s'%s%s%s: %s", + zonename, classstr, sep, vname, + cleanup ? ", removing journal file" : "", + isc_result_totext(result)); + dns_zone_detach(&zone); + return (result); +} + +/* + * Act on a "freeze" or "thaw" command from the command channel. + */ +isc_result_t +ns_server_freeze(ns_server_t *server, bool freeze, + isc_lex_t *lex, isc_buffer_t **text) +{ + isc_result_t result, tresult; + dns_zone_t *mayberaw = NULL, *raw = NULL; + dns_zonetype_t type; + char classstr[DNS_RDATACLASS_FORMATSIZE]; + char zonename[DNS_NAME_FORMATSIZE]; + dns_view_t *view; + const char *vname, *sep; + bool frozen; + const char *msg = NULL; + + result = zone_from_args(server, lex, NULL, &mayberaw, NULL, + text, true); + if (result != ISC_R_SUCCESS) + return (result); + if (mayberaw == NULL) { + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + tresult = ISC_R_SUCCESS; + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) { + result = dns_view_freezezones(view, freeze); + if (result != ISC_R_SUCCESS && + tresult == ISC_R_SUCCESS) + tresult = result; + } + isc_task_endexclusive(server->task); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "%s all zones: %s", + freeze ? "freezing" : "thawing", + isc_result_totext(tresult)); + return (tresult); + } + dns_zone_getraw(mayberaw, &raw); + if (raw != NULL) { + dns_zone_detach(&mayberaw); + dns_zone_attach(raw, &mayberaw); + dns_zone_detach(&raw); + } + type = dns_zone_gettype(mayberaw); + if (type != dns_zone_master) { + dns_zone_detach(&mayberaw); + return (DNS_R_NOTMASTER); + } + + if (freeze && !dns_zone_isdynamic(mayberaw, true)) { + dns_zone_detach(&mayberaw); + return (DNS_R_NOTDYNAMIC); + } + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + frozen = dns_zone_getupdatedisabled(mayberaw); + if (freeze) { + if (frozen) { + msg = "WARNING: The zone was already frozen.\n" + "Someone else may be editing it or " + "it may still be re-loading."; + result = DNS_R_FROZEN; + } + if (result == ISC_R_SUCCESS) { + result = dns_zone_flush(mayberaw); + if (result != ISC_R_SUCCESS) + msg = "Flushing the zone updates to " + "disk failed."; + } + if (result == ISC_R_SUCCESS) + dns_zone_setupdatedisabled(mayberaw, freeze); + } else { + if (frozen) { + result = dns_zone_loadandthaw(mayberaw); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_UPTODATE: + msg = "The zone reload and thaw was " + "successful."; + result = ISC_R_SUCCESS; + break; + case DNS_R_CONTINUE: + msg = "A zone reload and thaw was started.\n" + "Check the logs to see the result."; + result = ISC_R_SUCCESS; + break; + } + } + } + isc_task_endexclusive(server->task); + + if (msg != NULL) { + (void) putstr(text, msg); + (void) putnull(text); + } + + view = dns_zone_getview(mayberaw); + if (strcmp(view->name, "_default") == 0 || + strcmp(view->name, "_bind") == 0) + { + vname = ""; + sep = ""; + } else { + vname = view->name; + sep = " "; + } + dns_rdataclass_format(dns_zone_getclass(mayberaw), classstr, + sizeof(classstr)); + dns_name_format(dns_zone_getorigin(mayberaw), + zonename, sizeof(zonename)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "%s zone '%s/%s'%s%s: %s", + freeze ? "freezing" : "thawing", + zonename, classstr, sep, vname, + isc_result_totext(result)); + dns_zone_detach(&mayberaw); + return (result); +} + +#ifdef HAVE_LIBSCF +/* + * This function adds a message for rndc to echo if named + * is managed by smf and is also running chroot. + */ +isc_result_t +ns_smf_add_message(isc_buffer_t **text) { + return (putstr(text, "use svcadm(1M) to manage named")); +} +#endif /* HAVE_LIBSCF */ + +#ifndef HAVE_LMDB + +/* + * Emit a comment at the top of the nzf file containing the viewname + * Expects the fp to already be open for writing + */ +#define HEADER1 "# New zone file for view: " +#define HEADER2 "\n# This file contains configuration for zones added by\n" \ + "# the 'rndc addzone' command. DO NOT EDIT BY HAND.\n" +static isc_result_t +add_comment(FILE *fp, const char *viewname) { + isc_result_t result; + CHECK(isc_stdio_write(HEADER1, sizeof(HEADER1) - 1, 1, fp, NULL)); + CHECK(isc_stdio_write(viewname, strlen(viewname), 1, fp, NULL)); + CHECK(isc_stdio_write(HEADER2, sizeof(HEADER2) - 1, 1, fp, NULL)); + cleanup: + return (result); +} + +static void +dumpzone(void *arg, const char *buf, int len) { + FILE *fp = arg; + + (void) isc_stdio_write(buf, len, 1, fp, NULL); +} + +static isc_result_t +nzf_append(dns_view_t *view, const cfg_obj_t *zconfig) { + isc_result_t result; + off_t offset; + FILE *fp = NULL; + bool offsetok = false; + + LOCK(&view->new_zone_lock); + + CHECK(isc_stdio_open(view->new_zone_file, "a", &fp)); + CHECK(isc_stdio_seek(fp, 0, SEEK_END)); + + CHECK(isc_stdio_tell(fp, &offset)); + offsetok = true; + if (offset == 0) + CHECK(add_comment(fp, view->name)); + + CHECK(isc_stdio_write("zone ", 5, 1, fp, NULL)); + cfg_printx(zconfig, CFG_PRINTER_ONELINE, dumpzone, fp); + CHECK(isc_stdio_write(";\n", 2, 1, fp, NULL)); + CHECK(isc_stdio_flush(fp)); + result = isc_stdio_close(fp); + fp = NULL; + + cleanup: + if (fp != NULL) { + (void)isc_stdio_close(fp); + if (offsetok) { + isc_result_t result2; + + result2 = isc_file_truncate(view->new_zone_file, + offset); + if (result2 != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "Error truncating NZF file '%s' " + "during rollback from append: " + "%s", + view->new_zone_file, + isc_result_totext(result2)); + } + } + } + UNLOCK(&view->new_zone_lock); + return (result); +} + +static isc_result_t +nzf_writeconf(const cfg_obj_t *config, dns_view_t *view) { + const cfg_obj_t *zl = NULL; + cfg_list_t *list; + const cfg_listelt_t *elt; + + FILE *fp = NULL; + char tmp[1024]; + isc_result_t result; + + result = isc_file_template("", "nzf-XXXXXXXX", tmp, sizeof(tmp)); + if (result == ISC_R_SUCCESS) + result = isc_file_openunique(tmp, &fp); + if (result != ISC_R_SUCCESS) + return (result); + + cfg_map_get(config, "zone", &zl); + if (!cfg_obj_islist(zl)) + CHECK(ISC_R_FAILURE); + + DE_CONST(&zl->value.list, list); + + CHECK(add_comment(fp, view->name)); /* force a comment */ + + for (elt = ISC_LIST_HEAD(*list); + elt != NULL; + elt = ISC_LIST_NEXT(elt, link)) + { + const cfg_obj_t *zconfig = cfg_listelt_value(elt); + + CHECK(isc_stdio_write("zone ", 5, 1, fp, NULL)); + cfg_printx(zconfig, CFG_PRINTER_ONELINE, dumpzone, fp); + CHECK(isc_stdio_write(";\n", 2, 1, fp, NULL)); + } + + CHECK(isc_stdio_flush(fp)); + result = isc_stdio_close(fp); + fp = NULL; + if (result != ISC_R_SUCCESS) + goto cleanup; + CHECK(isc_file_rename(tmp, view->new_zone_file)); + return (result); + + cleanup: + if (fp != NULL) + (void)isc_stdio_close(fp); + (void)isc_file_remove(tmp); + return (result); +} + +#else /* HAVE_LMDB */ + +static void +nzd_setkey(MDB_val *key, dns_name_t *name, char *namebuf, size_t buflen) { + dns_fixedname_t fixed; + + dns_fixedname_init(&fixed); + dns_name_downcase(name, dns_fixedname_name(&fixed), NULL); + dns_name_format(dns_fixedname_name(&fixed), namebuf, buflen); + + key->mv_data = namebuf; + key->mv_size = strlen(namebuf); +} + +static void +dumpzone(void *arg, const char *buf, int len) { + isc_buffer_t **text = arg; + + putmem(text, buf, len); +} + +static isc_result_t +nzd_save(MDB_txn **txnp, MDB_dbi dbi, dns_zone_t *zone, + const cfg_obj_t *zconfig) +{ + isc_result_t result; + int status; + dns_view_t *view; + bool commit = false; + isc_buffer_t *text = NULL; + char namebuf[1024]; + MDB_val key, data; + + view = dns_zone_getview(zone); + + nzd_setkey(&key, dns_zone_getorigin(zone), namebuf, sizeof(namebuf)); + + LOCK(&view->new_zone_lock); + + if (zconfig == NULL) { + /* We're deleting the zone from the database */ + status = mdb_del(*txnp, dbi, &key, NULL); + if (status != MDB_SUCCESS && status != MDB_NOTFOUND) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "Error deleting zone %s " + "from NZD database: %s", + namebuf, mdb_strerror(status)); + result = ISC_R_FAILURE; + goto cleanup; + } else if (status != MDB_NOTFOUND) { + commit = true; + } + } else { + /* We're creating or overwriting the zone */ + const cfg_obj_t *zoptions; + + result = isc_buffer_allocate(view->mctx, &text, 256); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "Unable to allocate buffer in " + "nzd_save(): %s", + isc_result_totext(result)); + goto cleanup; + } + + zoptions = cfg_tuple_get(zconfig, "options"); + if (zoptions == NULL) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "Unable to get options from config in " + "nzd_save()"); + result = ISC_R_FAILURE; + goto cleanup; + } + + cfg_printx(zoptions, CFG_PRINTER_ONELINE, dumpzone, &text); + + data.mv_data = isc_buffer_base(text); + data.mv_size = isc_buffer_usedlength(text); + + status = mdb_put(*txnp, dbi, &key, &data, 0); + if (status != MDB_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "Error inserting zone in " + "NZD database: %s", + mdb_strerror(status)); + result = ISC_R_FAILURE; + goto cleanup; + } + + commit = true; + } + + result = ISC_R_SUCCESS; + + cleanup: + if (!commit || result != ISC_R_SUCCESS) { + (void) mdb_txn_abort(*txnp); + } else { + status = mdb_txn_commit(*txnp); + if (status != MDB_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "Error committing " + "NZD database: %s", + mdb_strerror(status)); + result = ISC_R_FAILURE; + } + } + *txnp = NULL; + + UNLOCK(&view->new_zone_lock); + + if (text != NULL) { + isc_buffer_free(&text); + } + + return (result); +} + +static isc_result_t +nzd_writable(dns_view_t *view) { + isc_result_t result = ISC_R_SUCCESS; + int status; + MDB_dbi dbi; + MDB_txn *txn = NULL; + + REQUIRE(view != NULL); + + status = mdb_txn_begin((MDB_env *) view->new_zone_dbenv, 0, 0, &txn); + if (status != MDB_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, "mdb_txn_begin: %s", + mdb_strerror(status)); + return (ISC_R_FAILURE); + } + + status = mdb_dbi_open(txn, NULL, 0, &dbi); + if (status != MDB_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, "mdb_dbi_open: %s", + mdb_strerror(status)); + result = ISC_R_FAILURE; + } + + mdb_txn_abort(txn); + return (result); +} + +static isc_result_t +nzd_open(dns_view_t *view, unsigned int flags, MDB_txn **txnp, MDB_dbi *dbi) { + int status; + MDB_txn *txn = NULL; + + REQUIRE(view != NULL); + REQUIRE(txnp != NULL && *txnp == NULL); + REQUIRE(dbi != NULL); + + status = mdb_txn_begin((MDB_env *) view->new_zone_dbenv, 0, + flags, &txn); + if (status != MDB_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, "mdb_txn_begin: %s", + mdb_strerror(status)); + goto cleanup; + } + + status = mdb_dbi_open(txn, NULL, 0, dbi); + if (status != MDB_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, "mdb_dbi_open: %s", + mdb_strerror(status)); + goto cleanup; + } + + *txnp = txn; + + cleanup: + if (status != MDB_SUCCESS) { + if (txn != NULL) { + mdb_txn_abort(txn); + } + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + +/* + * nzd_env_close() and nzd_env_reopen are a kluge to address the + * problem of an NZD file possibly being created before we drop + * root privileges. + */ +static void +nzd_env_close(dns_view_t *view) { + const char *dbpath = NULL; + char dbpath_copy[PATH_MAX]; + char lockpath[PATH_MAX]; + int status, ret; + + if (view->new_zone_dbenv == NULL) { + return; + } + + status = mdb_env_get_path(view->new_zone_dbenv, &dbpath); + INSIST(status == MDB_SUCCESS); + snprintf(lockpath, sizeof(lockpath), "%s-lock", dbpath); + strlcpy(dbpath_copy, dbpath, sizeof(dbpath_copy)); + mdb_env_close((MDB_env *) view->new_zone_dbenv); + + /* + * Database files must be owned by the eventual user, not by root. + */ + ret = chown(dbpath_copy, ns_os_uid(), -1); + UNUSED(ret); + + /* + * Some platforms need the lockfile not to exist when we reopen the + * environment. + */ + (void) isc_file_remove(lockpath); + + view->new_zone_dbenv = NULL; +} + +static isc_result_t +nzd_env_reopen(dns_view_t *view) { + isc_result_t result; + MDB_env *env = NULL; + int status; + + if (view->new_zone_db == NULL) { + return (ISC_R_SUCCESS); + } + + nzd_env_close(view); + + status = mdb_env_create(&env); + if (status != MDB_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_ERROR, + "mdb_env_create failed: %s", + mdb_strerror(status)); + CHECK(ISC_R_FAILURE); + } + + if (view->new_zone_mapsize != 0ULL) { + status = mdb_env_set_mapsize(env, view->new_zone_mapsize); + if (status != MDB_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_ERROR, + "mdb_env_set_mapsize failed: %s", + mdb_strerror(status)); + CHECK(ISC_R_FAILURE); + } + } + + status = mdb_env_open(env, view->new_zone_db, DNS_LMDB_FLAGS, 0600); + if (status != MDB_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_ERROR, + "mdb_env_open of '%s' failed: %s", + view->new_zone_db, mdb_strerror(status)); + CHECK(ISC_R_FAILURE); + } + + view->new_zone_dbenv = env; + env = NULL; + result = ISC_R_SUCCESS; + + cleanup: + if (env != NULL) { + mdb_env_close(env); + } + return (result); +} + +static isc_result_t +nzd_close(MDB_txn **txnp, bool commit) { + isc_result_t result = ISC_R_SUCCESS; + int status; + + REQUIRE(txnp != NULL); + + if (*txnp != NULL) { + if (commit) { + status = mdb_txn_commit(*txnp); + if (status != MDB_SUCCESS) { + result = ISC_R_FAILURE; + } + } else { + mdb_txn_abort(*txnp); + } + *txnp = NULL; + } + + return (result); +} + +static isc_result_t +nzd_count(dns_view_t *view, int *countp) { + isc_result_t result; + int status; + MDB_txn *txn = NULL; + MDB_dbi dbi; + MDB_stat statbuf; + + REQUIRE(countp != NULL); + + result = nzd_open(view, MDB_RDONLY, &txn, &dbi); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + status = mdb_stat(txn, dbi, &statbuf); + if (status != MDB_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, "mdb_stat: %s", + mdb_strerror(status)); + result = ISC_R_FAILURE; + goto cleanup; + } + + *countp = statbuf.ms_entries; + + cleanup: + (void) nzd_close(&txn, false); + + return (result); +} + +static isc_result_t +migrate_nzf(dns_view_t *view) { + isc_result_t result; + cfg_obj_t *nzf_config = NULL; + int status, n; + isc_buffer_t *text = NULL; + bool commit = false; + const cfg_obj_t *zonelist; + const cfg_listelt_t *element; + char tempname[PATH_MAX]; + MDB_txn *txn = NULL; + MDB_dbi dbi; + MDB_val key, data; + + /* + * If NZF file doesn't exist, or NZD DB exists and already + * has data, return without attempting migration. + */ + if (!isc_file_exists(view->new_zone_file)) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + result = nzd_count(view, &n); + if (result == ISC_R_SUCCESS && n > 0) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_INFO, + "Migrating zones from NZF file '%s' to " + "NZD database '%s'", + view->new_zone_file, view->new_zone_db); + /* + * Instead of blindly copying lines, we parse the NZF file using + * the configuration parser, because it validates it against the + * config type, giving us a guarantee that valid configuration + * will be written to DB. + */ + cfg_parser_reset(ns_g_addparser); + result = cfg_parse_file(ns_g_addparser, view->new_zone_file, + &cfg_type_addzoneconf, &nzf_config); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "Error parsing NZF file '%s': %s", + view->new_zone_file, + isc_result_totext(result)); + goto cleanup; + } + + zonelist = NULL; + CHECK(cfg_map_get(nzf_config, "zone", &zonelist)); + if (!cfg_obj_islist(zonelist)) { + CHECK(ISC_R_FAILURE); + } + + CHECK(nzd_open(view, 0, &txn, &dbi)); + + CHECK(isc_buffer_allocate(view->mctx, &text, 256)); + + for (element = cfg_list_first(zonelist); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *zconfig; + const cfg_obj_t *zoptions; + char zname[DNS_NAME_FORMATSIZE]; + dns_fixedname_t fname; + dns_name_t *name; + const char *origin; + isc_buffer_t b; + + zconfig = cfg_listelt_value(element); + + origin = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + if (origin == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + /* Normalize zone name */ + isc_buffer_constinit(&b, origin, strlen(origin)); + isc_buffer_add(&b, strlen(origin)); + name = dns_fixedname_initname(&fname); + CHECK(dns_name_fromtext(name, &b, dns_rootname, + DNS_NAME_DOWNCASE, NULL)); + dns_name_format(name, zname, sizeof(zname)); + + key.mv_data = zname; + key.mv_size = strlen(zname); + + zoptions = cfg_tuple_get(zconfig, "options"); + if (zoptions == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + isc_buffer_clear(text); + cfg_printx(zoptions, CFG_PRINTER_ONELINE, dumpzone, &text); + + data.mv_data = isc_buffer_base(text); + data.mv_size = isc_buffer_usedlength(text); + + status = mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE); + if (status != MDB_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "Error inserting zone in " + "NZD database: %s", + mdb_strerror(status)); + result = ISC_R_FAILURE; + goto cleanup; + } + + commit = true; + } + + result = ISC_R_SUCCESS; + + /* + * Leaving the NZF file in place is harmless as we won't use it + * if an NZD database is found for the view. But we rename NZF file + * to a backup name here. + */ + strlcpy(tempname, view->new_zone_file, sizeof(tempname)); + if (strlen(tempname) < sizeof(tempname) - 1) { + strlcat(tempname, "~", sizeof(tempname)); + isc_file_rename(view->new_zone_file, tempname); + } + + cleanup: + if (result != ISC_R_SUCCESS) { + (void) nzd_close(&txn, false); + } else { + result = nzd_close(&txn, commit); + } + + if (text != NULL) { + isc_buffer_free(&text); + } + + if (nzf_config != NULL) { + cfg_obj_destroy(ns_g_addparser, &nzf_config); + } + + return (result); +} + +#endif /* HAVE_LMDB */ + +static isc_result_t +newzone_parse(ns_server_t *server, char *command, dns_view_t **viewp, + cfg_obj_t **zoneconfp, const cfg_obj_t **zoneobjp, + isc_buffer_t **text) +{ + isc_result_t result; + isc_buffer_t argbuf; + cfg_obj_t *zoneconf = NULL; + const cfg_obj_t *zlist = NULL; + const cfg_obj_t *zoneobj = NULL; + const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *obj = NULL; + const char *viewname = NULL; + dns_rdataclass_t rdclass; + dns_view_t *view = NULL; + const char *bn; + + REQUIRE(viewp != NULL && *viewp == NULL); + REQUIRE(zoneobjp != NULL && *zoneobjp == NULL); + REQUIRE(zoneconfp != NULL && *zoneconfp == NULL); + + /* Try to parse the argument string */ + isc_buffer_init(&argbuf, command, (unsigned int) strlen(command)); + isc_buffer_add(&argbuf, strlen(command)); + + if (strncasecmp(command, "add", 3) == 0) + bn = "addzone"; + else if (strncasecmp(command, "mod", 3) == 0) + bn = "modzone"; + else + INSIST(0); + + /* + * Convert the "addzone" or "modzone" to just "zone", for + * the benefit of the parser + */ + isc_buffer_forward(&argbuf, 3); + + cfg_parser_reset(ns_g_addparser); + CHECK(cfg_parse_buffer3(ns_g_addparser, &argbuf, bn, 0, + &cfg_type_addzoneconf, &zoneconf)); + CHECK(cfg_map_get(zoneconf, "zone", &zlist)); + if (!cfg_obj_islist(zlist)) + CHECK(ISC_R_FAILURE); + + /* For now we only support adding one zone at a time */ + zoneobj = cfg_listelt_value(cfg_list_first(zlist)); + + /* Check the zone type for ones that are not supported by addzone. */ + zoptions = cfg_tuple_get(zoneobj, "options"); + + obj = NULL; + (void)cfg_map_get(zoptions, "type", &obj); + if (obj == NULL) { + (void) cfg_map_get(zoptions, "in-view", &obj); + if (obj != NULL) { + (void) putstr(text, + "'in-view' zones not supported by "); + (void) putstr(text, bn); + } else + (void) putstr(text, "zone type not specified"); + CHECK(ISC_R_FAILURE); + } + + if (strcasecmp(cfg_obj_asstring(obj), "hint") == 0 || + strcasecmp(cfg_obj_asstring(obj), "forward") == 0 || + strcasecmp(cfg_obj_asstring(obj), "redirect") == 0 || + strcasecmp(cfg_obj_asstring(obj), "delegation-only") == 0) + { + (void) putstr(text, "'"); + (void) putstr(text, cfg_obj_asstring(obj)); + (void) putstr(text, "' zones not supported by "); + (void) putstr(text, bn); + CHECK(ISC_R_FAILURE); + } + + /* Make sense of optional class argument */ + obj = cfg_tuple_get(zoneobj, "class"); + CHECK(ns_config_getclass(obj, dns_rdataclass_in, &rdclass)); + + /* Make sense of optional view argument */ + obj = cfg_tuple_get(zoneobj, "view"); + if (obj && cfg_obj_isstring(obj)) + viewname = cfg_obj_asstring(obj); + if (viewname == NULL || *viewname == '\0') + viewname = "_default"; + result = dns_viewlist_find(&server->viewlist, viewname, rdclass, + &view); + if (result == ISC_R_NOTFOUND) { + (void) putstr(text, "no matching view found for '"); + (void) putstr(text, viewname); + (void) putstr(text, "'"); + goto cleanup; + } else if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + *viewp = view; + *zoneobjp = zoneobj; + *zoneconfp = zoneconf; + + return (ISC_R_SUCCESS); + + cleanup: + if (zoneconf != NULL) + cfg_obj_destroy(ns_g_addparser, &zoneconf); + if (view != NULL) + dns_view_detach(&view); + + return (result); +} + +static isc_result_t +delete_zoneconf(dns_view_t *view, cfg_parser_t *pctx, + const cfg_obj_t *config, const dns_name_t *zname, + nzfwriter_t nzfwriter) +{ + isc_result_t result = ISC_R_NOTFOUND; + const cfg_listelt_t *elt = NULL; + const cfg_obj_t *zl = NULL; + cfg_list_t *list; + dns_fixedname_t myfixed; + dns_name_t *myname; + + REQUIRE(view != NULL); + REQUIRE(pctx != NULL); + REQUIRE(config != NULL); + REQUIRE(zname != NULL); + + LOCK(&view->new_zone_lock); + + cfg_map_get(config, "zone", &zl); + + if (!cfg_obj_islist(zl)) + CHECK(ISC_R_FAILURE); + + DE_CONST(&zl->value.list, list); + + myname = dns_fixedname_initname(&myfixed); + + for (elt = ISC_LIST_HEAD(*list); + elt != NULL; + elt = ISC_LIST_NEXT(elt, link)) + { + const cfg_obj_t *zconf = cfg_listelt_value(elt); + const char *zn; + cfg_listelt_t *e; + + zn = cfg_obj_asstring(cfg_tuple_get(zconf, "name")); + result = dns_name_fromstring(myname, zn, 0, NULL); + if (result != ISC_R_SUCCESS || + !dns_name_equal(zname, myname)) + continue; + + DE_CONST(elt, e); + ISC_LIST_UNLINK(*list, e, link); + cfg_obj_destroy(pctx, &e->obj); + isc_mem_put(pctx->mctx, e, sizeof(*e)); + result = ISC_R_SUCCESS; + break; + } + + /* + * Write config to NZF file if appropriate + */ + if (nzfwriter != NULL && view->new_zone_file != NULL) + result = nzfwriter(config, view); + + cleanup: + UNLOCK(&view->new_zone_lock); + return (result); +} + +static isc_result_t +do_addzone(ns_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, + dns_name_t *name, cfg_obj_t *zoneconf, const cfg_obj_t *zoneobj, + isc_buffer_t **text) +{ + isc_result_t result, tresult; + dns_zone_t *zone = NULL; +#ifndef HAVE_LMDB + FILE *fp = NULL; + bool cleanup_config = false; +#else /* HAVE_LMDB */ + MDB_txn *txn = NULL; + MDB_dbi dbi; + + UNUSED(zoneconf); +#endif /* HAVE_LMDB */ + + /* Zone shouldn't already exist */ + result = dns_zt_find(view->zonetable, name, 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; + +#ifndef HAVE_LMDB + /* + * Make sure we can open the configuration save file + */ + result = isc_stdio_open(view->new_zone_file, "a", &fp); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "unable to create '")); + TCHECK(putstr(text, view->new_zone_file)); + TCHECK(putstr(text, "': ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + + (void)isc_stdio_close(fp); + fp = NULL; +#else /* HAVE_LMDB */ + /* Make sure we can open the NZD database */ + result = nzd_writable(view); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "unable to open NZD database for '")); + TCHECK(putstr(text, view->new_zone_db)); + TCHECK(putstr(text, "'")); + result = ISC_R_FAILURE; + goto cleanup; + } +#endif /* HAVE_LMDB */ + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* Mark view unfrozen and configure zone */ + dns_view_thaw(view); + result = configure_zone(cfg->config, zoneobj, cfg->vconfig, + server->mctx, view, &server->viewlist, + cfg->actx, true, false, false); + dns_view_freeze(view); + + isc_task_endexclusive(server->task); + + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "configure_zone failed: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + + /* Is it there yet? */ + result = dns_zt_find(view->zonetable, name, 0, NULL, &zone); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, + NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "added new zone was not found: %s", + isc_result_totext(result)); + goto cleanup; + } + +#ifndef HAVE_LMDB + /* + * If there wasn't a previous newzone config, just save the one + * we've created. If there was a previous one, merge the new + * zone into it. + */ + if (cfg->nzf_config == NULL) { + cfg_obj_attach(zoneconf, &cfg->nzf_config); + } else { + cfg_obj_t *z; + DE_CONST(zoneobj, z); + CHECK(cfg_parser_mapadd(cfg->add_parser, + cfg->nzf_config, z, "zone")); + } + cleanup_config = true; +#endif /* HAVE_LMDB */ + + /* + * 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; + + TCHECK(putstr(text, "dns_zone_loadnew failed: ")); + TCHECK(putstr(text, isc_result_totext(result))); + + 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, true); + +#ifdef HAVE_LMDB + /* Save the new zone configuration into the NZD */ + CHECK(nzd_open(view, 0, &txn, &dbi)); + CHECK(nzd_save(&txn, dbi, zone, zoneobj)); +#else + /* Append the zone configuration to the NZF */ + result = nzf_append(view, zoneobj); +#endif /* HAVE_LMDB */ + + cleanup: + +#ifndef HAVE_LMDB + if (fp != NULL) + (void)isc_stdio_close(fp); + if (result != ISC_R_SUCCESS && cleanup_config) { + tresult = delete_zoneconf(view, cfg->add_parser, + cfg->nzf_config, name, + NULL); + RUNTIME_CHECK(tresult == ISC_R_SUCCESS); + } +#else /* HAVE_LMDB */ + if (txn != NULL) + (void) nzd_close(&txn, false); +#endif /* HAVE_LMDB */ + + if (zone != NULL) + dns_zone_detach(&zone); + + return (result); +} + +static isc_result_t +do_modzone(ns_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, + dns_name_t *name, const char *zname, const cfg_obj_t *zoneobj, + isc_buffer_t **text) +{ + isc_result_t result, tresult; + dns_zone_t *zone = NULL; + bool added; + bool exclusive = false; +#ifndef HAVE_LMDB + FILE *fp = NULL; + cfg_obj_t *z; +#else /* HAVE_LMDB */ + MDB_txn *txn = NULL; + MDB_dbi dbi; +#endif /* HAVE_LMDB */ + + /* Zone must already exist */ + result = dns_zt_find(view->zonetable, name, 0, NULL, &zone); + if (result != ISC_R_SUCCESS) + goto cleanup; + + added = dns_zone_getadded(zone); + dns_zone_detach(&zone); + +#ifndef HAVE_LMDB + cfg = (ns_cfgctx_t *) view->new_zone_config; + if (cfg == NULL) { + TCHECK(putstr(text, "new zone config is not set")); + CHECK(ISC_R_FAILURE); + } +#endif + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + exclusive = true; + +#ifndef HAVE_LMDB + /* Make sure we can open the configuration save file */ + result = isc_stdio_open(view->new_zone_file, "a", &fp); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "unable to open '")); + TCHECK(putstr(text, view->new_zone_file)); + TCHECK(putstr(text, "': ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + (void)isc_stdio_close(fp); + fp = NULL; +#else /* HAVE_LMDB */ + /* Make sure we can open the NZD database */ + result = nzd_writable(view); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "unable to open NZD database for '")); + TCHECK(putstr(text, view->new_zone_db)); + TCHECK(putstr(text, "'")); + result = ISC_R_FAILURE; + goto cleanup; + } +#endif /* HAVE_LMDB */ + + /* Reconfigure the zone */ + dns_view_thaw(view); + result = configure_zone(cfg->config, zoneobj, cfg->vconfig, + server->mctx, view, &server->viewlist, + cfg->actx, true, false, true); + dns_view_freeze(view); + + exclusive = false; + isc_task_endexclusive(server->task); + + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "configure_zone failed: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + + /* Is it there yet? */ + CHECK(dns_zt_find(view->zonetable, name, 0, NULL, &zone)); + +#ifndef HAVE_LMDB + /* Remove old zone from configuration (and NZF file if applicable) */ + if (added) { + result = delete_zoneconf(view, cfg->add_parser, + cfg->nzf_config, + dns_zone_getorigin(zone), + nzf_writeconf); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "former zone configuration " + "not deleted: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + } +#endif /* HAVE_LMDB */ + + if (!added) { + if (cfg->vconfig == NULL) { + result = delete_zoneconf(view, cfg->conf_parser, + cfg->config, + dns_zone_getorigin(zone), + NULL); + } else { + const cfg_obj_t *voptions = + cfg_tuple_get(cfg->vconfig, "options"); + result = delete_zoneconf(view, cfg->conf_parser, + voptions, + dns_zone_getorigin(zone), + NULL); + } + + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "former zone configuration " + "not deleted: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } + } + + /* Load the zone from the master file if it needs reloading. */ + result = dns_zone_loadnew(zone); + + /* + * Dynamic zones need no reloading, so we can pass this result. + */ + if (result == DNS_R_DYNAMIC) + result = ISC_R_SUCCESS; + + if (result != ISC_R_SUCCESS) { + dns_db_t *dbp = NULL; + + TCHECK(putstr(text, "failed to load zone '")); + TCHECK(putstr(text, zname)); + TCHECK(putstr(text, "': ")); + TCHECK(putstr(text, isc_result_totext(result))); + TCHECK(putstr(text, "\nThe zone is no longer being served. ")); + TCHECK(putstr(text, "Use 'rndc addzone' to correct\n")); + TCHECK(putstr(text, "the problem and restore service.")); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "modzone failed; removing zone."); + + /* 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; + } + +#ifndef HAVE_LMDB + /* Store the new zone configuration; also in NZF if applicable */ + DE_CONST(zoneobj, z); + CHECK(cfg_parser_mapadd(cfg->add_parser, cfg->nzf_config, z, "zone")); +#endif /* HAVE_LMDB */ + + if (added) { +#ifdef HAVE_LMDB + CHECK(nzd_open(view, 0, &txn, &dbi)); + CHECK(nzd_save(&txn, dbi, zone, zoneobj)); +#else + result = nzf_append(view, zoneobj); + if (result != ISC_R_SUCCESS) { + TCHECK(putstr(text, "\nNew zone config not saved: ")); + TCHECK(putstr(text, isc_result_totext(result))); + goto cleanup; + } +#endif /* HAVE_LMDB */ + + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zname)); + TCHECK(putstr(text, "' reconfigured.")); + + } else { + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zname)); + TCHECK(putstr(text, "' must also be reconfigured in\n")); + TCHECK(putstr(text, "named.conf to make changes permanent.")); + } + + cleanup: + if (exclusive) + isc_task_endexclusive(server->task); + +#ifndef HAVE_LMDB + if (fp != NULL) + (void)isc_stdio_close(fp); +#else /* HAVE_LMDB */ + if (txn != NULL) + (void) nzd_close(&txn, false); +#endif /* HAVE_LMDB */ + + if (zone != NULL) + dns_zone_detach(&zone); + + return (result); +} + +/* + * Act on an "addzone" or "modzone" command from the command channel. + */ +isc_result_t +ns_server_changezone(ns_server_t *server, char *command, isc_buffer_t **text) { + isc_result_t result; + bool addzone; + ns_cfgctx_t *cfg = NULL; + cfg_obj_t *zoneconf = NULL; + const cfg_obj_t *zoneobj = NULL; + const char *zonename; + dns_view_t *view = NULL; + isc_buffer_t buf; + dns_fixedname_t fname; + dns_name_t *dnsname; + + if (strncasecmp(command, "add", 3) == 0) + addzone = true; + else { + INSIST(strncasecmp(command, "mod", 3) == 0); + addzone = false; + } + + CHECK(newzone_parse(server, command, &view, &zoneconf, + &zoneobj, text)); + + /* Are we accepting new zones in this view? */ +#ifdef HAVE_LMDB + if (view->new_zone_db == NULL) +#else + if (view->new_zone_file == NULL) +#endif /* HAVE_LMDB */ + { + (void) putstr(text, "Not allowing new zones in view '"); + (void) putstr(text, view->name); + (void) putstr(text, "'"); + result = ISC_R_NOPERM; + goto cleanup; + } + + cfg = (ns_cfgctx_t *) view->new_zone_config; + if (cfg == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + zonename = cfg_obj_asstring(cfg_tuple_get(zoneobj, "name")); + isc_buffer_constinit(&buf, zonename, strlen(zonename)); + isc_buffer_add(&buf, strlen(zonename)); + + dnsname = dns_fixedname_initname(&fname); + CHECK(dns_name_fromtext(dnsname, &buf, dns_rootname, 0, NULL)); + + if (addzone) + CHECK(do_addzone(server, cfg, view, dnsname, zoneconf, + zoneobj, text)); + else + CHECK(do_modzone(server, cfg, view, dnsname, zonename, + zoneobj, text)); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "%s zone %s in view %s via %s", + addzone ? "added" : "updated", + zonename, view->name, + addzone ? NS_COMMAND_ADDZONE : NS_COMMAND_MODZONE); + + /* Changing a zone counts as reconfiguration */ + CHECK(isc_time_now(&ns_g_configtime)); + + cleanup: + if (isc_buffer_usedlength(*text) > 0) + (void) putnull(text); + if (zoneconf != NULL) + cfg_obj_destroy(ns_g_addparser, &zoneconf); + if (view != NULL) + dns_view_detach(&view); + + return (result); +} + +static bool +inuse(const char* file, bool first, isc_buffer_t **text) { + if (file != NULL && isc_file_exists(file)) { + if (first) + (void) putstr(text, + "The following files were in use " + "and may now be removed:\n"); + else + (void) putstr(text, "\n"); + (void) putstr(text, file); + (void) putnull(text); + return (false); + } + return (first); +} + +typedef struct { + dns_zone_t *zone; + bool cleanup; +} ns_dzctx_t; + +/* + * Carry out a zone deletion scheduled by ns_server_delzone(). + */ +static void +rmzone(isc_task_t *task, isc_event_t *event) { + ns_dzctx_t *dz = (ns_dzctx_t *)event->ev_arg; + dns_zone_t *zone, *raw = NULL, *mayberaw; + char zonename[DNS_NAME_FORMATSIZE]; + dns_view_t *view; + ns_cfgctx_t *cfg; + dns_db_t *dbp = NULL; + bool added; + isc_result_t result; +#ifdef HAVE_LMDB + MDB_txn *txn = NULL; + MDB_dbi dbi; +#endif + + REQUIRE(dz != NULL); + + isc_event_free(&event); + + /* Dig out configuration for this zone */ + zone = dz->zone; + view = dns_zone_getview(zone); + cfg = (ns_cfgctx_t *) view->new_zone_config; + dns_name_format(dns_zone_getorigin(zone), zonename, sizeof(zonename)); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "deleting zone %s in view %s via delzone", + zonename, view->name); + + /* Remove the zone from configuration (and NZF file if applicable) */ + added = dns_zone_getadded(zone); + + if (added && cfg != NULL) { +#ifdef HAVE_LMDB + /* Make sure we can open the NZD database */ + result = nzd_open(view, 0, &txn, &dbi); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, + "unable to open NZD database for '%s'", + view->new_zone_db); + } else { + result = nzd_save(&txn, dbi, zone, NULL); + } + + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, "unable to " + "delete zone configuration: %s", + isc_result_totext(result)); + } +#else + result = delete_zoneconf(view, cfg->add_parser, + cfg->nzf_config, + dns_zone_getorigin(zone), + nzf_writeconf); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, "unable to " + "delete zone configuration: %s", + isc_result_totext(result)); + } +#endif /* HAVE_LMDB */ + } + + if (!added && cfg != NULL) { + if (cfg->vconfig != NULL) { + const cfg_obj_t *voptions = + cfg_tuple_get(cfg->vconfig, "options"); + result = delete_zoneconf(view, cfg->conf_parser, + voptions, + dns_zone_getorigin(zone), + NULL); + } else { + result = delete_zoneconf(view, cfg->conf_parser, + cfg->config, + dns_zone_getorigin(zone), + NULL); + } + if (result != ISC_R_SUCCESS){ + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_ERROR, "unable to " + "delete zone configuration: %s", + isc_result_totext(result)); + } + } + + /* Unload zone database */ + if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) { + dns_db_detach(&dbp); + dns_zone_unload(zone); + } + + /* Clean up stub/slave zone files if requested to do so */ + dns_zone_getraw(zone, &raw); + mayberaw = (raw != NULL) ? raw : zone; + + if (added && dz->cleanup) { + const char *file; + + file = dns_zone_getfile(mayberaw); + result = isc_file_remove(file); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "file %s not removed: %s", + file, isc_result_totext(result)); + } + + file = dns_zone_getjournal(mayberaw); + result = isc_file_remove(file); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "file %s not removed: %s", + file, isc_result_totext(result)); + } + + if (zone != mayberaw) { + file = dns_zone_getfile(zone); + result = isc_file_remove(file); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, + "file %s not removed: %s", + file, isc_result_totext(result)); + } + + file = dns_zone_getjournal(zone); + result = isc_file_remove(file); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, + ISC_LOG_WARNING, + "file %s not removed: %s", + file, isc_result_totext(result)); + } + } + } + +#ifdef HAVE_LMDB + if (txn != NULL) + (void) nzd_close(&txn, false); +#endif + if (raw != NULL) + dns_zone_detach(&raw); + dns_zone_detach(&zone); + isc_mem_put(ns_g_mctx, dz, sizeof(*dz)); + isc_task_detach(&task); +} + +/* + * Act on a "delzone" command from the command channel. + */ +isc_result_t +ns_server_delzone(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result, tresult; + dns_zone_t *zone = NULL; + dns_zone_t *raw = NULL; + dns_zone_t *mayberaw; + dns_view_t *view = NULL; + char zonename[DNS_NAME_FORMATSIZE]; + bool cleanup = false; + const char *ptr; + bool added; + ns_dzctx_t *dz = NULL; + isc_event_t *dzevent = NULL; + isc_task_t *task = NULL; + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + /* Find out what we are to do. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + if (strcmp(ptr, "-clean") == 0 || strcmp(ptr, "-clear") == 0) { + cleanup = true; + ptr = next_token(lex, text); + } + + CHECK(zone_from_args(server, lex, ptr, &zone, zonename, + text, false)); + if (zone == NULL) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + INSIST(zonename != NULL); + + /* Is this a policy zone? */ + if (dns_zone_get_rpz_num(zone) != DNS_RPZ_INVALID_NUM) { + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zonename)); + TCHECK(putstr(text, + "' cannot be deleted: response-policy zone.")); + result = ISC_R_FAILURE; + goto cleanup; + } + + view = dns_zone_getview(zone); + CHECK(dns_zt_unmount(view->zonetable, zone)); + + /* Send cleanup event */ + dz = isc_mem_get(ns_g_mctx, sizeof(*dz)); + if (dz == NULL) + CHECK(ISC_R_NOMEMORY); + + dz->cleanup = cleanup; + dz->zone = NULL; + dns_zone_attach(zone, &dz->zone); + dzevent = isc_event_allocate(ns_g_mctx, server, NS_EVENT_DELZONE, + rmzone, dz, sizeof(isc_event_t)); + if (dzevent == NULL) + CHECK(ISC_R_NOMEMORY); + + dns_zone_gettask(zone, &task); + isc_task_send(task, &dzevent); + dz = NULL; + + /* Inform user about cleaning up stub/slave zone files */ + dns_zone_getraw(zone, &raw); + mayberaw = (raw != NULL) ? raw : zone; + + added = dns_zone_getadded(zone); + if (!added) { + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zonename)); + TCHECK(putstr(text, + "' is no longer active and will be deleted.\n")); + TCHECK(putstr(text, "To keep it from returning ")); + TCHECK(putstr(text, "when the server is restarted, it\n")); + TCHECK(putstr(text, "must also be removed from named.conf.")); + } else if (cleanup) { + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zonename)); + TCHECK(putstr(text, "' and associated files will be deleted.")); + } else if (dns_zone_gettype(mayberaw) == dns_zone_slave || + dns_zone_gettype(mayberaw) == dns_zone_stub) + { + bool first; + const char *file; + + TCHECK(putstr(text, "zone '")); + TCHECK(putstr(text, zonename)); + TCHECK(putstr(text, "' will be deleted.")); + + file = dns_zone_getfile(mayberaw); + first = inuse(file, true, text); + + file = dns_zone_getjournal(mayberaw); + first = inuse(file, first, text); + + if (zone != mayberaw) { + file = dns_zone_getfile(zone); + first = inuse(file, first, text); + + file = dns_zone_getjournal(zone); + (void) inuse(file, first, text); + } + } + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "zone %s scheduled for removal via delzone", zonename); + + /* Removing a zone counts as reconfiguration */ + CHECK(isc_time_now(&ns_g_configtime)); + + result = ISC_R_SUCCESS; + + cleanup: + if (isc_buffer_usedlength(*text) > 0) + (void) putnull(text); + if (raw != NULL) + dns_zone_detach(&raw); + if (zone != NULL) + dns_zone_detach(&zone); + if (dz != NULL) { + dns_zone_detach(&dz->zone); + isc_mem_put(ns_g_mctx, dz, sizeof(*dz)); + } + + return (result); +} + +static const cfg_obj_t * +find_name_in_list_from_map(const cfg_obj_t *config, + const char *map_key_for_list, + const char *name) +{ + const cfg_obj_t *list = NULL; + const cfg_listelt_t *element; + const cfg_obj_t *obj = NULL; + dns_fixedname_t fixed1, fixed2; + dns_name_t *name1 = NULL, *name2 = NULL; + isc_result_t result; + + if (strcmp(map_key_for_list, "zone") == 0) { + name1 = dns_fixedname_initname(&fixed1); + name2 = dns_fixedname_initname(&fixed2); + result = dns_name_fromstring(name1, name, 0, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + cfg_map_get(config, map_key_for_list, &list); + for (element = cfg_list_first(list); + element != NULL; + element = cfg_list_next(element)) + { + const char *vname; + + obj = cfg_listelt_value(element); + INSIST(obj != NULL); + vname = cfg_obj_asstring(cfg_tuple_get(obj, "name")); + if (vname == NULL) { + obj = NULL; + continue; + } + + if (name1 != NULL) { + result = dns_name_fromstring(name2, vname, 0, NULL); + if (result == ISC_R_SUCCESS && + dns_name_equal(name1, name2)) + break; + } else if (strcasecmp(vname, name) == 0) + break; + + obj = NULL; + } + + return (obj); +} + +static void +emitzone(void *arg, const char *buf, int len) { + isc_buffer_t **tpp = arg; + putmem(tpp, buf, len); +} + +/* + * Act on a "showzone" command from the command channel. + */ +isc_result_t +ns_server_showzone(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result; + const cfg_obj_t *vconfig = NULL, *zconfig = NULL; + char zonename[DNS_NAME_FORMATSIZE]; + const cfg_obj_t *map; + dns_view_t *view = NULL; + dns_zone_t *zone = NULL; + ns_cfgctx_t *cfg = NULL; + bool exclusive = false; +#ifdef HAVE_LMDB + cfg_obj_t *nzconfig = NULL; +#endif /* HAVE_LMDB */ + + /* Parse parameters */ + CHECK(zone_from_args(server, lex, NULL, &zone, zonename, + text, true)); + if (zone == NULL) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + view = dns_zone_getview(zone); + dns_zone_detach(&zone); + + cfg = (ns_cfgctx_t *) view->new_zone_config; + if (cfg == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + exclusive = true; + + /* Find the view statement */ + vconfig = find_name_in_list_from_map(cfg->config, "view", view->name); + + /* Find the zone statement */ + if (vconfig != NULL) + map = cfg_tuple_get(vconfig, "options"); + else + map = cfg->config; + + zconfig = find_name_in_list_from_map(map, "zone", zonename); + +#ifndef HAVE_LMDB + if (zconfig == NULL && cfg->nzf_config != NULL) + zconfig = find_name_in_list_from_map(cfg->nzf_config, + "zone", zonename); +#else /* HAVE_LMDB */ + if (zconfig == NULL) { + const cfg_obj_t *zlist = NULL; + CHECK(get_newzone_config(view, zonename, &nzconfig)); + CHECK(cfg_map_get(nzconfig, "zone", &zlist)); + if (!cfg_obj_islist(zlist)) + CHECK(ISC_R_FAILURE); + + zconfig = cfg_listelt_value(cfg_list_first(zlist)); + } +#endif /* HAVE_LMDB */ + + if (zconfig == NULL) + CHECK(ISC_R_NOTFOUND); + + putstr(text, "zone "); + cfg_printx(zconfig, CFG_PRINTER_ONELINE, emitzone, text); + putstr(text, ";"); + + result = ISC_R_SUCCESS; + + cleanup: +#ifdef HAVE_LMDB + if (nzconfig != NULL) + cfg_obj_destroy(ns_g_addparser, &nzconfig); +#endif /* HAVE_LMDB */ + if (isc_buffer_usedlength(*text) > 0) + (void) putnull(text); + if (exclusive) + isc_task_endexclusive(server->task); + + return (result); +} + +static void +newzone_cfgctx_destroy(void **cfgp) { + ns_cfgctx_t *cfg; + + REQUIRE(cfgp != NULL && *cfgp != NULL); + + cfg = *cfgp; + + if (cfg->conf_parser != NULL) { + if (cfg->config != NULL) + cfg_obj_destroy(cfg->conf_parser, &cfg->config); + if (cfg->vconfig != NULL) + cfg_obj_destroy(cfg->conf_parser, &cfg->vconfig); + cfg_parser_destroy(&cfg->conf_parser); + } + if (cfg->add_parser != NULL) { + if (cfg->nzf_config != NULL) + cfg_obj_destroy(cfg->add_parser, &cfg->nzf_config); + cfg_parser_destroy(&cfg->add_parser); + } + + if (cfg->actx != NULL) + cfg_aclconfctx_detach(&cfg->actx); + + isc_mem_putanddetach(&cfg->mctx, cfg, sizeof(*cfg)); + *cfgp = NULL; +} + +static isc_result_t +generate_salt(unsigned char *salt, size_t saltlen) { + int i, n; + union { + unsigned char rnd[256]; + uint32_t rnd32[64]; + } rnd; + unsigned char text[512 + 1]; + isc_region_t r; + isc_buffer_t buf; + isc_result_t result; + + if (saltlen > 256U) + return (ISC_R_RANGE); + + n = (int) (saltlen + sizeof(uint32_t) - 1) / sizeof(uint32_t); + for (i = 0; i < n; i++) + isc_random_get(&rnd.rnd32[i]); + + memmove(salt, rnd.rnd, saltlen); + + r.base = rnd.rnd; + r.length = (unsigned int) saltlen; + + isc_buffer_init(&buf, text, sizeof(text)); + result = isc_hex_totext(&r, 2, "", &buf); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + text[saltlen * 2] = 0; + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "generated salt: %s", text); + + return (ISC_R_SUCCESS); +} + +isc_result_t +ns_server_signing(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { + isc_result_t result = ISC_R_SUCCESS; + dns_zone_t *zone = NULL; + dns_name_t *origin; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_dbversion_t *version = NULL; + dns_rdatatype_t privatetype; + dns_rdataset_t privset; + bool first = true; + bool list = false, clear = false; + bool chain = false; + bool setserial = false; + uint32_t serial = 0; + char keystr[DNS_SECALG_FORMATSIZE + 7]; /* <5-digit keyid>/<alg> */ + unsigned short hash = 0, flags = 0, iter = 0, saltlen = 0; + unsigned char salt[255]; + const char *ptr; + size_t n; + + dns_rdataset_init(&privset); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + /* Find out what we are to do. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + if (strcasecmp(ptr, "-list") == 0) + list = true; + else if ((strcasecmp(ptr, "-clear") == 0) || + (strcasecmp(ptr, "-clean") == 0)) + { + clear = true; + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + strlcpy(keystr, ptr, sizeof(keystr)); + } else if (strcasecmp(ptr, "-nsec3param") == 0) { + char hashbuf[64], flagbuf[64], iterbuf[64]; + char nbuf[256]; + + chain = true; + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + if (strcasecmp(ptr, "none") == 0) + hash = 0; + else { + strlcpy(hashbuf, ptr, sizeof(hashbuf)); + + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + strlcpy(flagbuf, ptr, sizeof(flagbuf)); + + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + strlcpy(iterbuf, ptr, sizeof(iterbuf)); + + n = snprintf(nbuf, sizeof(nbuf), "%s %s %s", + hashbuf, flagbuf, iterbuf); + if (n == sizeof(nbuf)) + return (ISC_R_NOSPACE); + n = sscanf(nbuf, "%hu %hu %hu", &hash, &flags, &iter); + if (n != 3U) + return (ISC_R_BADNUMBER); + + if (hash > 0xffU || flags > 0xffU) + return (ISC_R_RANGE); + + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } else if (strcasecmp(ptr, "auto") == 0) { + /* Auto-generate a random salt. + * XXXMUKS: This currently uses the + * minimum recommended length by RFC + * 5155 (64 bits). It should be made + * configurable. + */ + saltlen = 8; + CHECK(generate_salt(salt, saltlen)); + } else if (strcmp(ptr, "-") != 0) { + isc_buffer_t buf; + + isc_buffer_init(&buf, salt, sizeof(salt)); + CHECK(isc_hex_decodestring(ptr, &buf)); + saltlen = isc_buffer_usedlength(&buf); + } + } + } else if (strcasecmp(ptr, "-serial") == 0) { + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + CHECK(isc_parse_uint32(&serial, ptr, 10)); + setserial = true; + } else + CHECK(DNS_R_SYNTAX); + + CHECK(zone_from_args(server, lex, NULL, &zone, NULL, + text, false)); + if (zone == NULL) + CHECK(ISC_R_UNEXPECTEDEND); + + if (clear) { + CHECK(dns_zone_keydone(zone, keystr)); + (void) putstr(text, "request queued"); + (void) putnull(text); + } else if (chain) { + CHECK(dns_zone_setnsec3param(zone, (uint8_t)hash, + (uint8_t)flags, iter, + (uint8_t)saltlen, salt, + true)); + (void) putstr(text, "nsec3param request queued"); + (void) putnull(text); + } else if (setserial) { + CHECK(dns_zone_setserial(zone, serial)); + (void) putstr(text, "serial request queued"); + (void) putnull(text); + } else if (list) { + privatetype = dns_zone_getprivatetype(zone); + origin = dns_zone_getorigin(zone); + CHECK(dns_zone_getdb(zone, &db)); + CHECK(dns_db_findnode(db, origin, false, &node)); + dns_db_currentversion(db, &version); + + result = dns_db_findrdataset(db, node, version, privatetype, + dns_rdatatype_none, 0, + &privset, NULL); + if (result == ISC_R_NOTFOUND) { + (void) putstr(text, "No signing records found"); + (void) putnull(text); + result = ISC_R_SUCCESS; + goto cleanup; + } + + for (result = dns_rdataset_first(&privset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&privset)) + { + dns_rdata_t priv = DNS_RDATA_INIT; + char output[BUFSIZ]; + isc_buffer_t buf; + + dns_rdataset_current(&privset, &priv); + + isc_buffer_init(&buf, output, sizeof(output)); + CHECK(dns_private_totext(&priv, &buf)); + if (!first) + CHECK(putstr(text, "\n")); + CHECK(putstr(text, output)); + first = false; + } + if (!first) + CHECK(putnull(text)); + + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + } + + cleanup: + if (dns_rdataset_isassociated(&privset)) + dns_rdataset_disassociate(&privset); + if (node != NULL) + dns_db_detachnode(db, &node); + if (version != NULL) + dns_db_closeversion(db, &version, false); + if (db != NULL) + dns_db_detach(&db); + if (zone != NULL) + dns_zone_detach(&zone); + + return (result); +} + +static isc_result_t +putmem(isc_buffer_t **b, const char *str, size_t len) { + isc_result_t result; + + result = isc_buffer_reserve(b, (unsigned int)len); + if (result != ISC_R_SUCCESS) + return (ISC_R_NOSPACE); + + isc_buffer_putmem(*b, (const unsigned char *)str, (unsigned int)len); + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +putstr(isc_buffer_t **b, const char *str) { + return (putmem(b, str, strlen(str))); +} + +static isc_result_t +putuint8(isc_buffer_t **b, uint8_t val) { + isc_result_t result; + + result = isc_buffer_reserve(b, 1); + if (result != ISC_R_SUCCESS) + return (ISC_R_NOSPACE); + + isc_buffer_putuint8(*b, val); + return (ISC_R_SUCCESS); +} + +static inline isc_result_t +putnull(isc_buffer_t **b) { + return (putuint8(b, 0)); +} + +isc_result_t +ns_server_zonestatus(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) +{ + isc_result_t result = ISC_R_SUCCESS; + dns_zone_t *zone = NULL, *raw = NULL, *mayberaw = NULL; + const char *type, *file; + char zonename[DNS_NAME_FORMATSIZE]; + uint32_t serial, signed_serial, nodes; + char serbuf[16], sserbuf[16], nodebuf[16]; + char resignbuf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + 2]; + char lbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char xbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char rbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char kbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + char rtbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + isc_time_t loadtime, expiretime, refreshtime; + isc_time_t refreshkeytime, resigntime; + dns_zonetype_t zonetype; + bool dynamic = false, frozen = false; + bool hasraw = false; + bool secure, maintain, allow; + dns_db_t *db = NULL, *rawdb = NULL; + char **incfiles = NULL; + int nfiles = 0; + + isc_time_settoepoch(&loadtime); + isc_time_settoepoch(&refreshtime); + isc_time_settoepoch(&expiretime); + isc_time_settoepoch(&refreshkeytime); + isc_time_settoepoch(&resigntime); + + CHECK(zone_from_args(server, lex, NULL, &zone, zonename, + text, true)); + if (zone == NULL) { + result = ISC_R_UNEXPECTEDEND; + goto cleanup; + } + + /* Inline signing? */ + CHECK(dns_zone_getdb(zone, &db)); + dns_zone_getraw(zone, &raw); + hasraw = (raw != NULL); + if (hasraw) { + mayberaw = raw; + zonetype = dns_zone_gettype(raw); + CHECK(dns_zone_getdb(raw, &rawdb)); + } else { + mayberaw = zone; + zonetype = dns_zone_gettype(zone); + } + + switch (zonetype) { + case dns_zone_master: + type = "master"; + break; + case dns_zone_slave: + type = "slave"; + break; + case dns_zone_stub: + type = "stub"; + break; + case dns_zone_staticstub: + type = "staticstub"; + break; + case dns_zone_redirect: + type = "redirect"; + break; + case dns_zone_key: + type = "key"; + break; + case dns_zone_dlz: + type = "dlz"; + break; + default: + type = "unknown"; + } + + /* Serial number */ + serial = dns_zone_getserial(mayberaw); + snprintf(serbuf, sizeof(serbuf), "%u", serial); + if (hasraw) { + signed_serial = dns_zone_getserial(zone); + snprintf(sserbuf, sizeof(sserbuf), "%u", signed_serial); + } + + /* Database node count */ + nodes = dns_db_nodecount(hasraw ? rawdb : db); + snprintf(nodebuf, sizeof(nodebuf), "%u", nodes); + + /* Security */ + secure = dns_db_issecure(db); + allow = (dns_zone_getkeyopts(zone) & DNS_ZONEKEY_ALLOW); + maintain = (dns_zone_getkeyopts(zone) & DNS_ZONEKEY_MAINTAIN); + + /* Master files */ + file = dns_zone_getfile(mayberaw); + nfiles = dns_zone_getincludes(mayberaw, &incfiles); + + /* Load time */ + dns_zone_getloadtime(zone, &loadtime); + isc_time_formathttptimestamp(&loadtime, lbuf, sizeof(lbuf)); + + /* Refresh/expire times */ + if (zonetype == dns_zone_slave || + zonetype == dns_zone_stub || + zonetype == dns_zone_redirect) + { + dns_zone_getexpiretime(mayberaw, &expiretime); + isc_time_formathttptimestamp(&expiretime, xbuf, sizeof(xbuf)); + dns_zone_getrefreshtime(mayberaw, &refreshtime); + isc_time_formathttptimestamp(&refreshtime, rbuf, sizeof(rbuf)); + } + + /* Key refresh time */ + if (zonetype == dns_zone_master || + (zonetype == dns_zone_slave && hasraw)) + { + dns_zone_getrefreshkeytime(zone, &refreshkeytime); + isc_time_formathttptimestamp(&refreshkeytime, kbuf, + sizeof(kbuf)); + } + + /* Dynamic? */ + if (zonetype == dns_zone_master) { + dynamic = dns_zone_isdynamic(mayberaw, true); + frozen = dynamic && !dns_zone_isdynamic(mayberaw, false); + } + + /* Next resign event */ + if (secure && (zonetype == dns_zone_master || + (zonetype == dns_zone_slave && hasraw)) && + ((dns_zone_getkeyopts(zone) & DNS_ZONEKEY_NORESIGN) == 0)) + { + dns_name_t *name; + dns_fixedname_t fixed; + dns_rdataset_t next; + + dns_rdataset_init(&next); + name = dns_fixedname_initname(&fixed); + + result = dns_db_getsigningtime(db, &next, name); + if (result == ISC_R_SUCCESS) { + isc_stdtime_t timenow; + char namebuf[DNS_NAME_FORMATSIZE]; + char typebuf[DNS_RDATATYPE_FORMATSIZE]; + + isc_stdtime_get(&timenow); + dns_name_format(name, namebuf, sizeof(namebuf)); + dns_rdatatype_format(next.covers, + typebuf, sizeof(typebuf)); + snprintf(resignbuf, sizeof(resignbuf), + "%s/%s", namebuf, typebuf); + isc_time_set(&resigntime, next.resign - + dns_zone_getsigresigninginterval(zone), 0); + isc_time_formathttptimestamp(&resigntime, rtbuf, + sizeof(rtbuf)); + dns_rdataset_disassociate(&next); + } + } + + /* Create text */ + CHECK(putstr(text, "name: ")); + CHECK(putstr(text, zonename)); + + CHECK(putstr(text, "\ntype: ")); + CHECK(putstr(text, type)); + + if (file != NULL) { + int i; + CHECK(putstr(text, "\nfiles: ")); + CHECK(putstr(text, file)); + for (i = 0; i < nfiles; i++) { + CHECK(putstr(text, ", ")); + if (incfiles[i] != NULL) { + CHECK(putstr(text, incfiles[i])); + } + } + } + + CHECK(putstr(text, "\nserial: ")); + CHECK(putstr(text, serbuf)); + if (hasraw) { + CHECK(putstr(text, "\nsigned serial: ")); + CHECK(putstr(text, sserbuf)); + } + + CHECK(putstr(text, "\nnodes: ")); + CHECK(putstr(text, nodebuf)); + + if (! isc_time_isepoch(&loadtime)) { + CHECK(putstr(text, "\nlast loaded: ")); + CHECK(putstr(text, lbuf)); + } + + if (! isc_time_isepoch(&refreshtime)) { + CHECK(putstr(text, "\nnext refresh: ")); + CHECK(putstr(text, rbuf)); + } + + if (! isc_time_isepoch(&expiretime)) { + CHECK(putstr(text, "\nexpires: ")); + CHECK(putstr(text, xbuf)); + } + + if (secure) { + CHECK(putstr(text, "\nsecure: yes")); + if (hasraw) { + CHECK(putstr(text, "\ninline signing: yes")); + } else { + CHECK(putstr(text, "\ninline signing: no")); + } + } else { + CHECK(putstr(text, "\nsecure: no")); + } + + if (maintain) { + CHECK(putstr(text, "\nkey maintenance: automatic")); + if (! isc_time_isepoch(&refreshkeytime)) { + CHECK(putstr(text, "\nnext key event: ")); + CHECK(putstr(text, kbuf)); + } + } else if (allow) { + CHECK(putstr(text, "\nkey maintenance: on command")); + } else if (secure || hasraw) { + CHECK(putstr(text, "\nkey maintenance: none")); + } + + if (!isc_time_isepoch(&resigntime)) { + CHECK(putstr(text, "\nnext resign node: ")); + CHECK(putstr(text, resignbuf)); + CHECK(putstr(text, "\nnext resign time: ")); + CHECK(putstr(text, rtbuf)); + } + + if (dynamic) { + CHECK(putstr(text, "\ndynamic: yes")); + if (frozen) { + CHECK(putstr(text, "\nfrozen: yes")); + } else { + CHECK(putstr(text, "\nfrozen: no")); + } + } else { + CHECK(putstr(text, "\ndynamic: no")); + } + + CHECK(putstr(text, "\nreconfigurable via modzone: ")); + CHECK(putstr(text, dns_zone_getadded(zone) ? "yes" : "no")); + + cleanup: + /* Indicate truncated output if possible. */ + if (result == ISC_R_NOSPACE) { + (void) putstr(text, "\n..."); + } + if ((result == ISC_R_SUCCESS || result == ISC_R_NOSPACE)) { + (void) putnull(text); + } + + if (db != NULL) { + dns_db_detach(&db); + } + if (rawdb != NULL) { + dns_db_detach(&rawdb); + } + if (incfiles != NULL && mayberaw != NULL) { + int i; + isc_mem_t *mctx = dns_zone_getmctx(mayberaw); + + for (i = 0; i < nfiles; i++) { + if (incfiles[i] != NULL) { + isc_mem_free(mctx, incfiles[i]); + } + } + isc_mem_free(mctx, incfiles); + } + if (raw != NULL) { + dns_zone_detach(&raw); + } + if (zone != NULL) { + dns_zone_detach(&zone); + } + return (result); +} + +static inline bool +argcheck(char *cmd, const char *full) { + size_t l; + + if (cmd == NULL || cmd[0] != '-') + return (false); + + cmd++; + l = strlen(cmd); + if (l > strlen(full) || strncasecmp(cmd, full, l) != 0) + return (false); + + return (true); +} + +isc_result_t +ns_server_nta(ns_server_t *server, isc_lex_t *lex, bool readonly, + isc_buffer_t **text) +{ + dns_view_t *view; + dns_ntatable_t *ntatable = NULL; + isc_result_t result = ISC_R_SUCCESS; + char *ptr, *nametext = NULL, *viewname; + char namebuf[DNS_NAME_FORMATSIZE]; + isc_stdtime_t now, when; + isc_time_t t; + char tbuf[64]; + const char *msg = NULL; + bool dump = false, force = false; + dns_fixedname_t fn; + dns_name_t *ntaname; + dns_ttl_t ntattl; + bool ttlset = false, excl = false; + dns_rdataclass_t rdclass = dns_rdataclass_in; + + UNUSED(force); + + ntaname = dns_fixedname_initname(&fn); + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + for (;;) { + /* Check for options */ + ptr = next_token(lex, text); + if (ptr == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + if (argcheck(ptr, "dump")) { + dump = true; + } else if (argcheck(ptr, "remove")) { + ntattl = 0; + ttlset = true; + } else if (argcheck(ptr, "force")) { + force = true; + continue; + } else if (argcheck(ptr, "lifetime")) { + isc_textregion_t tr; + + ptr = next_token(lex, text); + if (ptr == NULL) { + msg = "No lifetime specified"; + CHECK(ISC_R_UNEXPECTEDEND); + } + + tr.base = ptr; + tr.length = strlen(ptr); + result = dns_ttl_fromtext(&tr, &ntattl); + if (result != ISC_R_SUCCESS) { + msg = "could not parse NTA lifetime"; + CHECK(result); + } + + if (ntattl > 604800) { + msg = "NTA lifetime cannot exceed one week"; + CHECK(ISC_R_RANGE); + } + + ttlset = true; + continue; + } else if (argcheck(ptr, "class")) { + isc_textregion_t tr; + + ptr = next_token(lex, text); + if (ptr == NULL) { + msg = "No class specified"; + CHECK(ISC_R_UNEXPECTEDEND); + } + + tr.base = ptr; + tr.length = strlen(ptr); + CHECK(dns_rdataclass_fromtext(&rdclass, &tr)); + continue; + } else { + nametext = ptr; + } + + break; + } + + /* + * If -dump was specified, list NTA's and return + */ + if (dump) { + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + continue; + } + CHECK(dns_ntatable_totext(ntatable, text)); + } + CHECK(putnull(text)); + + goto cleanup; + } + + if (readonly) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_CONTROL, ISC_LOG_INFO, + "rejecting restricted control channel " + "NTA command"); + CHECK(ISC_R_FAILURE); + } + + /* Get the NTA name. */ + if (nametext == NULL) { + nametext = next_token(lex, text); + } + if (nametext == NULL) { + return (ISC_R_UNEXPECTEDEND); + } + + /* Copy nametext as it'll be overwritten by next_token() */ + strlcpy(namebuf, nametext, DNS_NAME_FORMATSIZE); + + if (strcmp(namebuf, ".") == 0) { + ntaname = dns_rootname; + } else { + isc_buffer_t b; + isc_buffer_init(&b, namebuf, strlen(namebuf)); + isc_buffer_add(&b, strlen(namebuf)); + CHECK(dns_name_fromtext(ntaname, &b, dns_rootname, 0, NULL)); + } + + /* Look for the view name. */ + viewname = next_token(lex, text); + + isc_stdtime_get(&now); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + excl = true; + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + static bool first = true; + + if (viewname != NULL && strcmp(view->name, viewname) != 0) { + continue; + } + + if (view->rdclass != rdclass && rdclass != dns_rdataclass_any) { + continue; + } + + if (view->nta_lifetime == 0) { + continue; + } + + if (!ttlset) { + ntattl = view->nta_lifetime; + } + + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + + result = dns_view_getntatable(view, &ntatable); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + continue; + } + + result = dns_view_flushnode(view, ntaname, true); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "flush tree '%s' in cache view '%s': %s", + namebuf, view->name, + isc_result_totext(result)); + + if (ntattl != 0) { + CHECK(dns_ntatable_add(ntatable, ntaname, + force, now, ntattl)); + + when = now + ntattl; + isc_time_set(&t, when, 0); + isc_time_formattimestamp(&t, tbuf, sizeof(tbuf)); + + if (!first) { + CHECK(putstr(text, "\n")); + } + first = false; + + CHECK(putstr(text, "Negative trust anchor added: ")); + CHECK(putstr(text, namebuf)); + CHECK(putstr(text, "/")); + CHECK(putstr(text, view->name)); + CHECK(putstr(text, ", expires ")); + CHECK(putstr(text, tbuf)); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "added NTA '%s' (%d sec) in view '%s'", + namebuf, ntattl, view->name); + } else { + CHECK(dns_ntatable_delete(ntatable, ntaname)); + + if (!first) { + CHECK(putstr(text, "\n")); + } + first = false; + + CHECK(putstr(text, "Negative trust anchor removed: ")); + CHECK(putstr(text, namebuf)); + CHECK(putstr(text, "/")); + CHECK(putstr(text, view->name)); + + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_INFO, + "removed NTA '%s' in view %s", + namebuf, view->name); + } + + result = dns_view_saventa(view); + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "error writing NTA file " + "for view '%s': %s", + view->name, isc_result_totext(result)); + } + } + + CHECK(putnull(text)); + + cleanup: + if (msg != NULL) { + (void) putstr(text, msg); + (void) putnull(text); + } + if (excl) { + isc_task_endexclusive(server->task); + } + if (ntatable != NULL) { + dns_ntatable_detach(&ntatable); + } + return (result); +} + +isc_result_t +ns_server_saventa(ns_server_t *server) { + dns_view_t *view; + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + isc_result_t result = dns_view_saventa(view); + + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "error writing NTA file " + "for view '%s': %s", + view->name, isc_result_totext(result)); + } + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +ns_server_loadnta(ns_server_t *server) { + dns_view_t *view; + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + isc_result_t result = dns_view_loadnta(view); + + if ((result != ISC_R_SUCCESS) && + (result != ISC_R_FILENOTFOUND) && + (result != ISC_R_NOTFOUND)) + { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "error loading NTA file " + "for view '%s': %s", + view->name, isc_result_totext(result)); + } + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +mkey_refresh(dns_view_t *view, isc_buffer_t **text) { + isc_result_t result; + char msg[DNS_NAME_FORMATSIZE + 500] = ""; + + snprintf(msg, sizeof(msg), + "refreshing managed keys for '%s'", view->name); + CHECK(putstr(text, msg)); + CHECK(dns_zone_synckeyzone(view->managed_keys)); + + cleanup: + return (result); +} + +static isc_result_t +mkey_dumpzone(dns_view_t *view, isc_buffer_t **text) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbversion_t *ver = NULL; + dns_rriterator_t rrit; + isc_stdtime_t now; + dns_name_t *prevname = NULL; + + isc_stdtime_get(&now); + + CHECK(dns_zone_getdb(view->managed_keys, &db)); + dns_db_currentversion(db, &ver); + dns_rriterator_init(&rrit, db, ver, 0); + for (result = dns_rriterator_first(&rrit); + result == ISC_R_SUCCESS; + result = dns_rriterator_nextrrset(&rrit)) + { + char buf[DNS_NAME_FORMATSIZE + 500]; + dns_name_t *name = NULL; + dns_rdataset_t *kdset = NULL; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_keydata_t kd; + uint32_t ttl; + + dns_rriterator_current(&rrit, &name, &ttl, &kdset, NULL); + if (kdset == NULL || kdset->type != dns_rdatatype_keydata || + !dns_rdataset_isassociated(kdset)) + continue; + + if (name != prevname) { + char nbuf[DNS_NAME_FORMATSIZE]; + dns_name_format(name, nbuf, sizeof(nbuf)); + snprintf(buf, sizeof(buf), "\n\n name: %s", nbuf); + CHECK(putstr(text, buf)); + } + + + for (result = dns_rdataset_first(kdset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(kdset)) + { + char alg[DNS_SECALG_FORMATSIZE]; + char tbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + dns_keytag_t keyid; + isc_region_t r; + isc_time_t t; + bool revoked; + + dns_rdata_reset(&rdata); + dns_rdataset_current(kdset, &rdata); + result = dns_rdata_tostruct(&rdata, &kd, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_rdata_toregion(&rdata, &r); + isc_region_consume(&r, 12); + keyid = dst_region_computeid(&r, kd.algorithm); + + snprintf(buf, sizeof(buf), "\n keyid: %u", keyid); + CHECK(putstr(text, buf)); + + dns_secalg_format(kd.algorithm, alg, sizeof(alg)); + snprintf(buf, sizeof(buf), "\n\talgorithm: %s", alg); + CHECK(putstr(text, buf)); + + revoked = (kd.flags & DNS_KEYFLAG_REVOKE); + snprintf(buf, sizeof(buf), "\n\tflags:%s%s%s", + revoked ? " REVOKE" : "", + ((kd.flags & DNS_KEYFLAG_KSK) != 0) + ? " SEP" : "", + (kd.flags == 0) ? " (none)" : ""); + CHECK(putstr(text, buf)); + + isc_time_set(&t, kd.refresh, 0); + isc_time_formathttptimestamp(&t, tbuf, sizeof(tbuf)); + snprintf(buf, sizeof(buf), + "\n\tnext refresh: %s", tbuf); + CHECK(putstr(text, buf)); + + if (kd.removehd != 0) { + isc_time_set(&t, kd.removehd, 0); + isc_time_formathttptimestamp(&t, tbuf, + sizeof(tbuf)); + snprintf(buf, sizeof(buf), + "\n\tremove at: %s", tbuf); + CHECK(putstr(text, buf)); + } + + isc_time_set(&t, kd.addhd, 0); + isc_time_formathttptimestamp(&t, tbuf, sizeof(tbuf)); + if (kd.addhd == 0) + snprintf(buf, sizeof(buf), "\n\tno trust"); + else if (revoked) + snprintf(buf, sizeof(buf), + "\n\ttrust revoked"); + else if (kd.addhd <= now) + snprintf(buf, sizeof(buf), + "\n\ttrusted since: %s", tbuf); + else if (kd.addhd > now) + snprintf(buf, sizeof(buf), + "\n\ttrust pending: %s", tbuf); + CHECK(putstr(text, buf)); + } + } + + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + + cleanup: + if (ver != NULL) { + dns_rriterator_destroy(&rrit); + dns_db_closeversion(db, &ver, false); + } + if (db != NULL) + dns_db_detach(&db); + + return (result); +} + +static isc_result_t +mkey_status(dns_view_t *view, isc_buffer_t **text) { + isc_result_t result; + char msg[ISC_FORMATHTTPTIMESTAMP_SIZE]; + isc_time_t t; + + CHECK(putstr(text, "view: ")); + CHECK(putstr(text, view->name)); + + CHECK(putstr(text, "\nnext scheduled event: ")); + + dns_zone_getrefreshkeytime(view->managed_keys, &t); + if (isc_time_isepoch(&t)) { + CHECK(putstr(text, "never")); + } else { + isc_time_formathttptimestamp(&t, msg, sizeof(msg)); + CHECK(putstr(text, msg)); + } + + CHECK(mkey_dumpzone(view, text)); + + cleanup: + return (result); +} + +isc_result_t +ns_server_mkeys(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { + char *cmd, *classtxt, *viewtxt = NULL; + isc_result_t result = ISC_R_SUCCESS; + dns_view_t *view = NULL; + dns_rdataclass_t rdclass; + char msg[DNS_NAME_FORMATSIZE + 500] = ""; + enum { NONE, STATUS, REFRESH, SYNC } opt = NONE; + bool found = false; + bool first = true; + + /* Skip rndc command name */ + cmd = next_token(lex, text); + if (cmd == NULL) + return (ISC_R_UNEXPECTEDEND); + + /* Get managed-keys subcommand */ + cmd = next_token(lex, text); + if (cmd == NULL) + return (ISC_R_UNEXPECTEDEND); + + if (strcasecmp(cmd, "status") == 0) + opt = STATUS; + else if (strcasecmp(cmd, "refresh") == 0) + opt = REFRESH; + else if (strcasecmp(cmd, "sync") == 0) + opt = SYNC; + else { + snprintf(msg, sizeof(msg), "unknown command '%s'", cmd); + (void) putstr(text, msg); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* Look for the optional class name. */ + classtxt = next_token(lex, text); + if (classtxt != NULL) { + /* Look for the optional view name. */ + viewtxt = next_token(lex, text); + } + + if (classtxt == NULL) { + rdclass = dns_rdataclass_in; + } else { + isc_textregion_t r; + r.base = classtxt; + r.length = strlen(classtxt); + result = dns_rdataclass_fromtext(&rdclass, &r); + if (result != ISC_R_SUCCESS) { + if (viewtxt == NULL) { + rdclass = dns_rdataclass_in; + viewtxt = classtxt; + result = ISC_R_SUCCESS; + } else { + snprintf(msg, sizeof(msg), + "unknown class '%s'", classtxt); + (void) putstr(text, msg); + goto cleanup; + } + } + } + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + if (viewtxt != NULL && + (rdclass != view->rdclass || + strcmp(view->name, viewtxt) != 0)) + continue; + + if (view->managed_keys == NULL) { + if (viewtxt != NULL) { + snprintf(msg, sizeof(msg), + "view '%s': no managed keys", viewtxt); + CHECK(putstr(text, msg)); + goto cleanup; + } else + continue; + } + + found = true; + + switch (opt) { + case REFRESH: + CHECK(mkey_refresh(view, text)); + break; + case STATUS: + if (!first) + CHECK(putstr(text, "\n\n")); + CHECK(mkey_status(view, text)); + first = false; + break; + case SYNC: + CHECK(dns_zone_flush(view->managed_keys)); + break; + default: + INSIST(0); + } + + if (viewtxt != NULL) + break; + } + + if (!found) + CHECK(putstr(text, "no views with managed keys")); + + cleanup: + if (isc_buffer_usedlength(*text) > 0) + (void) putnull(text); + + return (result); +} + +isc_result_t +ns_server_dnstap(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) { +#ifdef HAVE_DNSTAP + char *ptr; + isc_result_t result; + bool reopen = false; + int backups = 0; + + if (server->dtenv == NULL) + return (ISC_R_NOTFOUND); + + /* Check the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + /* "dnstap-reopen" was used in 9.11.0b1 */ + if (strcasecmp(ptr, "dnstap-reopen") == 0) { + reopen = true; + } else { + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + } + + if (reopen || strcasecmp(ptr, "-reopen") == 0) { + backups = -1; + } else if ((strcasecmp(ptr, "-roll") == 0)) { + unsigned int n; + ptr = next_token(lex, text); + if (ptr != NULL) { + unsigned int u; + n = sscanf(ptr, "%u", &u); + if (n != 1U || u > INT_MAX) + return (ISC_R_BADNUMBER); + backups = u; + } + } else + return (DNS_R_SYNTAX); + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = dns_dt_reopen(server->dtenv, backups); + isc_task_endexclusive(server->task); + return (result); +#else + UNUSED(server); + UNUSED(lex); + UNUSED(text); + return (ISC_R_NOTIMPLEMENTED); +#endif +} |