/* $NetBSD: zone.c,v 1.16.2.2 2024/02/25 15:46:54 martin Exp $ */ /* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * 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 https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ /*! \file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "zone_p.h" #define ZONE_MAGIC ISC_MAGIC('Z', 'O', 'N', 'E') #define DNS_ZONE_VALID(zone) ISC_MAGIC_VALID(zone, ZONE_MAGIC) #define NOTIFY_MAGIC ISC_MAGIC('N', 't', 'f', 'y') #define DNS_NOTIFY_VALID(notify) ISC_MAGIC_VALID(notify, NOTIFY_MAGIC) #define CHECKDS_MAGIC ISC_MAGIC('C', 'h', 'D', 'S') #define DNS_CHECKDS_VALID(checkds) ISC_MAGIC_VALID(checkds, CHECKDS_MAGIC) #define STUB_MAGIC ISC_MAGIC('S', 't', 'u', 'b') #define DNS_STUB_VALID(stub) ISC_MAGIC_VALID(stub, STUB_MAGIC) #define ZONEMGR_MAGIC ISC_MAGIC('Z', 'm', 'g', 'r') #define DNS_ZONEMGR_VALID(stub) ISC_MAGIC_VALID(stub, ZONEMGR_MAGIC) #define LOAD_MAGIC ISC_MAGIC('L', 'o', 'a', 'd') #define DNS_LOAD_VALID(load) ISC_MAGIC_VALID(load, LOAD_MAGIC) #define FORWARD_MAGIC ISC_MAGIC('F', 'o', 'r', 'w') #define DNS_FORWARD_VALID(load) ISC_MAGIC_VALID(load, FORWARD_MAGIC) #define IO_MAGIC ISC_MAGIC('Z', 'm', 'I', 'O') #define DNS_IO_VALID(load) ISC_MAGIC_VALID(load, IO_MAGIC) #define KEYMGMT_MAGIC ISC_MAGIC('M', 'g', 'm', 't') #define DNS_KEYMGMT_VALID(load) ISC_MAGIC_VALID(load, KEYMGMT_MAGIC) #define KEYFILEIO_MAGIC ISC_MAGIC('K', 'y', 'I', 'O') #define DNS_KEYFILEIO_VALID(kfio) ISC_MAGIC_VALID(kfio, KEYFILEIO_MAGIC) /*% * Ensure 'a' is at least 'min' but not more than 'max'. */ #define RANGE(a, min, max) (((a) < (min)) ? (min) : ((a) < (max) ? (a) : (max))) #define NSEC3REMOVE(x) (((x) & DNS_NSEC3FLAG_REMOVE) != 0) /*% * Key flags */ #define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0) #define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0) #define ID(x) dst_key_id(x) #define ALG(x) dst_key_alg(x) /*% * KASP flags */ #define KASP_LOCK(k) \ if ((k) != NULL) { \ LOCK((&((k)->lock))); \ } #define KASP_UNLOCK(k) \ if ((k) != NULL) { \ UNLOCK((&((k)->lock))); \ } /* * Default values. */ #define DNS_DEFAULT_IDLEIN 3600 /*%< 1 hour */ #define DNS_DEFAULT_IDLEOUT 3600 /*%< 1 hour */ #define MAX_XFER_TIME (2 * 3600) /*%< Documented default is 2 hours */ #define RESIGN_DELAY 3600 /*%< 1 hour */ #ifndef DNS_MAX_EXPIRE #define DNS_MAX_EXPIRE 14515200 /*%< 24 weeks */ #endif /* ifndef DNS_MAX_EXPIRE */ #ifndef DNS_DUMP_DELAY #define DNS_DUMP_DELAY 900 /*%< 15 minutes */ #endif /* ifndef DNS_DUMP_DELAY */ typedef struct dns_notify dns_notify_t; typedef struct dns_checkds dns_checkds_t; typedef struct dns_stub dns_stub_t; typedef struct dns_load dns_load_t; typedef struct dns_forward dns_forward_t; typedef ISC_LIST(dns_forward_t) dns_forwardlist_t; typedef struct dns_io dns_io_t; typedef ISC_LIST(dns_io_t) dns_iolist_t; typedef struct dns_keymgmt dns_keymgmt_t; typedef struct dns_signing dns_signing_t; typedef ISC_LIST(dns_signing_t) dns_signinglist_t; typedef struct dns_nsec3chain dns_nsec3chain_t; typedef ISC_LIST(dns_nsec3chain_t) dns_nsec3chainlist_t; typedef struct dns_keyfetch dns_keyfetch_t; typedef struct dns_asyncload dns_asyncload_t; typedef struct dns_include dns_include_t; #define DNS_ZONE_CHECKLOCK #ifdef DNS_ZONE_CHECKLOCK #define LOCK_ZONE(z) \ do { \ LOCK(&(z)->lock); \ INSIST(!(z)->locked); \ (z)->locked = true; \ } while (0) #define UNLOCK_ZONE(z) \ do { \ (z)->locked = false; \ UNLOCK(&(z)->lock); \ } while (0) #define LOCKED_ZONE(z) ((z)->locked) #define TRYLOCK_ZONE(result, z) \ do { \ result = isc_mutex_trylock(&(z)->lock); \ if (result == ISC_R_SUCCESS) { \ INSIST(!(z)->locked); \ (z)->locked = true; \ } \ } while (0) #else /* ifdef DNS_ZONE_CHECKLOCK */ #define LOCK_ZONE(z) LOCK(&(z)->lock) #define UNLOCK_ZONE(z) UNLOCK(&(z)->lock) #define LOCKED_ZONE(z) true #define TRYLOCK_ZONE(result, z) \ do { \ result = isc_mutex_trylock(&(z)->lock); \ } while (0) #endif /* ifdef DNS_ZONE_CHECKLOCK */ #define ZONEDB_INITLOCK(l) isc_rwlock_init((l), 0, 0) #define ZONEDB_DESTROYLOCK(l) isc_rwlock_destroy(l) #define ZONEDB_LOCK(l, t) RWLOCK((l), (t)) #define ZONEDB_UNLOCK(l, t) RWUNLOCK((l), (t)) #ifdef ENABLE_AFL extern bool dns_fuzzing_resolver; #endif /* ifdef ENABLE_AFL */ /*% * Hold key file IO locks. */ typedef struct dns_keyfileio { unsigned int magic; struct dns_keyfileio *next; uint32_t hashval; dns_fixedname_t fname; dns_name_t *name; isc_refcount_t references; isc_mutex_t lock; } dns_keyfileio_t; struct dns_keymgmt { unsigned int magic; isc_rwlock_t lock; isc_mem_t *mctx; dns_keyfileio_t **table; atomic_uint_fast32_t count; uint32_t bits; }; struct dns_zone { /* Unlocked */ unsigned int magic; isc_mutex_t lock; #ifdef DNS_ZONE_CHECKLOCK bool locked; #endif /* ifdef DNS_ZONE_CHECKLOCK */ isc_mem_t *mctx; isc_refcount_t erefs; isc_rwlock_t dblock; dns_db_t *db; /* Locked by dblock */ /* Locked */ dns_zonemgr_t *zmgr; ISC_LINK(dns_zone_t) link; /* Used by zmgr. */ isc_timer_t *timer; isc_refcount_t irefs; dns_name_t origin; char *masterfile; const FILE *stream; /* loading from a stream? */ ISC_LIST(dns_include_t) includes; /* Include files */ ISC_LIST(dns_include_t) newincludes; /* Loading */ unsigned int nincludes; dns_masterformat_t masterformat; const dns_master_style_t *masterstyle; char *journal; int32_t journalsize; dns_rdataclass_t rdclass; dns_zonetype_t type; #ifdef __NetBSD__ atomic_uint_fast32_t flags; atomic_uint_fast32_t options; #else atomic_uint_fast64_t flags; atomic_uint_fast64_t options; #endif unsigned int db_argc; char **db_argv; isc_time_t expiretime; isc_time_t refreshtime; isc_time_t dumptime; isc_time_t loadtime; isc_time_t notifytime; isc_time_t resigntime; isc_time_t keywarntime; isc_time_t signingtime; isc_time_t nsec3chaintime; isc_time_t refreshkeytime; uint32_t refreshkeyinterval; uint32_t refreshkeycount; uint32_t refresh; uint32_t retry; uint32_t expire; uint32_t minimum; isc_stdtime_t key_expiry; isc_stdtime_t log_key_expired_timer; char *keydirectory; dns_keyfileio_t *kfio; uint32_t maxrefresh; uint32_t minrefresh; uint32_t maxretry; uint32_t minretry; uint32_t maxrecords; isc_sockaddr_t *primaries; dns_name_t **primarykeynames; dns_name_t **primarytlsnames; bool *primariesok; unsigned int primariescnt; unsigned int curprimary; isc_sockaddr_t primaryaddr; isc_sockaddr_t *parentals; dns_name_t **parentalkeynames; dns_name_t **parentaltlsnames; dns_dnsseckeylist_t checkds_ok; unsigned int parentalscnt; isc_sockaddr_t parentaladdr; dns_notifytype_t notifytype; isc_sockaddr_t *notify; dns_name_t **notifykeynames; dns_name_t **notifytlsnames; unsigned int notifycnt; isc_sockaddr_t notifyfrom; isc_task_t *task; isc_task_t *loadtask; isc_sockaddr_t notifysrc4; isc_sockaddr_t notifysrc6; isc_sockaddr_t parentalsrc4; isc_sockaddr_t parentalsrc6; isc_sockaddr_t xfrsource4; isc_sockaddr_t xfrsource6; isc_sockaddr_t altxfrsource4; isc_sockaddr_t altxfrsource6; isc_sockaddr_t sourceaddr; dns_xfrin_ctx_t *xfr; /* task locked */ dns_tsigkey_t *tsigkey; /* key used for xfr */ dns_transport_t *transport; /* transport used for xfr */ /* Access Control Lists */ dns_acl_t *update_acl; dns_acl_t *forward_acl; dns_acl_t *notify_acl; dns_acl_t *query_acl; dns_acl_t *queryon_acl; dns_acl_t *xfr_acl; bool update_disabled; bool zero_no_soa_ttl; dns_severity_t check_names; ISC_LIST(dns_notify_t) notifies; ISC_LIST(dns_checkds_t) checkds_requests; dns_request_t *request; dns_loadctx_t *lctx; dns_io_t *readio; dns_dumpctx_t *dctx; dns_io_t *writeio; uint32_t maxxfrin; uint32_t maxxfrout; uint32_t idlein; uint32_t idleout; isc_event_t ctlevent; dns_ssutable_t *ssutable; uint32_t sigvalidityinterval; uint32_t keyvalidityinterval; uint32_t sigresigninginterval; dns_view_t *view; dns_view_t *prev_view; dns_kasp_t *kasp; dns_checkmxfunc_t checkmx; dns_checksrvfunc_t checksrv; dns_checknsfunc_t checkns; /*% * Zones in certain states such as "waiting for zone transfer" * or "zone transfer in progress" are kept on per-state linked lists * in the zone manager using the 'statelink' field. The 'statelist' * field points at the list the zone is currently on. It the zone * is not on any such list, statelist is NULL. */ ISC_LINK(dns_zone_t) statelink; dns_zonelist_t *statelist; /*% * Statistics counters about zone management. */ isc_stats_t *stats; /*% * Optional per-zone statistics counters. Counted outside of this * module. */ dns_zonestat_level_t statlevel; bool requeststats_on; isc_stats_t *requeststats; dns_stats_t *rcvquerystats; dns_stats_t *dnssecsignstats; uint32_t notifydelay; dns_isselffunc_t isself; void *isselfarg; char *strnamerd; char *strname; char *strrdclass; char *strviewname; /*% * Serial number for deferred journal compaction. */ uint32_t compact_serial; /*% * Keys that are signing the zone for the first time. */ dns_signinglist_t signing; dns_nsec3chainlist_t nsec3chain; /*% * List of outstanding NSEC3PARAM change requests. */ isc_eventlist_t setnsec3param_queue; /*% * Signing / re-signing quantum stopping parameters. */ uint32_t signatures; uint32_t nodes; dns_rdatatype_t privatetype; /*% * Autosigning/key-maintenance options */ #ifdef __NetBSD__ atomic_uint_fast32_t keyopts; #else atomic_uint_fast64_t keyopts; #endif /*% * True if added by "rndc addzone" */ bool added; /*% * True if added by automatically by named. */ bool automatic; /*% * response policy data to be relayed to the database */ dns_rpz_zones_t *rpzs; dns_rpz_num_t rpz_num; /*% * catalog zone data */ dns_catz_zones_t *catzs; /*% * parent catalog zone */ dns_catz_zone_t *parentcatz; /*% * Serial number update method. */ dns_updatemethod_t updatemethod; /*% * whether ixfr is requested */ bool requestixfr; uint32_t ixfr_ratio; /*% * whether EDNS EXPIRE is requested */ bool requestexpire; /*% * Outstanding forwarded UPDATE requests. */ dns_forwardlist_t forwards; dns_zone_t *raw; dns_zone_t *secure; bool sourceserialset; uint32_t sourceserial; /*% * soa and maximum zone ttl */ dns_ttl_t soattl; dns_ttl_t maxttl; /* * Inline zone signing state. */ dns_diff_t rss_diff; isc_eventlist_t rss_events; isc_eventlist_t rss_post; dns_dbversion_t *rss_newver; dns_dbversion_t *rss_oldver; dns_db_t *rss_db; dns_zone_t *rss_raw; isc_event_t *rss_event; dns_update_state_t *rss_state; isc_stats_t *gluecachestats; }; #define zonediff_init(z, d) \ do { \ dns__zonediff_t *_z = (z); \ (_z)->diff = (d); \ (_z)->offline = false; \ } while (0) #define DNS_ZONE_FLAG(z, f) ((atomic_load_relaxed(&(z)->flags) & (f)) != 0) #define DNS_ZONE_SETFLAG(z, f) atomic_fetch_or(&(z)->flags, (f)) #define DNS_ZONE_CLRFLAG(z, f) atomic_fetch_and(&(z)->flags, ~(f)) typedef enum { DNS_ZONEFLG_REFRESH = 0x00000001U, /*%< refresh check in progress */ DNS_ZONEFLG_NEEDDUMP = 0x00000002U, /*%< zone need consolidation */ DNS_ZONEFLG_USEVC = 0x00000004U, /*%< use tcp for refresh query */ DNS_ZONEFLG_DUMPING = 0x00000008U, /*%< a dump is in progress */ DNS_ZONEFLG_HASINCLUDE = 0x00000010U, /*%< $INCLUDE in zone file */ DNS_ZONEFLG_LOADED = 0x00000020U, /*%< database has loaded */ DNS_ZONEFLG_EXITING = 0x00000040U, /*%< zone is being destroyed */ DNS_ZONEFLG_EXPIRED = 0x00000080U, /*%< zone has expired */ DNS_ZONEFLG_NEEDREFRESH = 0x00000100U, /*%< refresh check needed */ DNS_ZONEFLG_UPTODATE = 0x00000200U, /*%< zone contents are * up-to-date */ DNS_ZONEFLG_NEEDNOTIFY = 0x00000400U, /*%< need to send out notify * messages */ DNS_ZONEFLG_FIXJOURNAL = 0x00000800U, /*%< journal file had * recoverable error, * needs rewriting */ DNS_ZONEFLG_NOPRIMARIES = 0x00001000U, /*%< an attempt to refresh a * zone with no primaries * occurred */ DNS_ZONEFLG_LOADING = 0x00002000U, /*%< load from disk in progress*/ DNS_ZONEFLG_HAVETIMERS = 0x00004000U, /*%< timer values have been set * from SOA (if not set, we * are still using * default timer values) */ DNS_ZONEFLG_FORCEXFER = 0x00008000U, /*%< Force a zone xfer */ DNS_ZONEFLG_NOREFRESH = 0x00010000U, DNS_ZONEFLG_DIALNOTIFY = 0x00020000U, DNS_ZONEFLG_DIALREFRESH = 0x00040000U, DNS_ZONEFLG_SHUTDOWN = 0x00080000U, DNS_ZONEFLG_NOIXFR = 0x00100000U, /*%< IXFR failed, force AXFR */ DNS_ZONEFLG_FLUSH = 0x00200000U, DNS_ZONEFLG_NOEDNS = 0x00400000U, DNS_ZONEFLG_USEALTXFRSRC = 0x00800000U, DNS_ZONEFLG_SOABEFOREAXFR = 0x01000000U, DNS_ZONEFLG_NEEDCOMPACT = 0x02000000U, DNS_ZONEFLG_REFRESHING = 0x04000000U, /*%< Refreshing keydata */ DNS_ZONEFLG_THAW = 0x08000000U, DNS_ZONEFLG_LOADPENDING = 0x10000000U, /*%< Loading scheduled */ DNS_ZONEFLG_NODELAY = 0x20000000U, DNS_ZONEFLG_SENDSECURE = 0x40000000U, DNS_ZONEFLG_NEEDSTARTUPNOTIFY = 0x80000000U, /*%< need to send out * notify due to the zone * just being loaded for * the first time. */ #ifndef __NetBSD__ /* * DO NOT add any new zone flags here until all platforms * support 64-bit enum values. Currently they fail on * Windows. */ DNS_ZONEFLG___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */ #endif } dns_zoneflg_t; #define DNS_ZONE_OPTION(z, o) ((atomic_load_relaxed(&(z)->options) & (o)) != 0) #define DNS_ZONE_SETOPTION(z, o) atomic_fetch_or(&(z)->options, (o)) #define DNS_ZONE_CLROPTION(z, o) atomic_fetch_and(&(z)->options, ~(o)) #define DNS_ZONEKEY_OPTION(z, o) \ ((atomic_load_relaxed(&(z)->keyopts) & (o)) != 0) #define DNS_ZONEKEY_SETOPTION(z, o) atomic_fetch_or(&(z)->keyopts, (o)) #define DNS_ZONEKEY_CLROPTION(z, o) atomic_fetch_and(&(z)->keyopts, ~(o)) /* Flags for zone_load() */ typedef enum { DNS_ZONELOADFLAG_NOSTAT = 0x00000001U, /* Do not stat() master files */ DNS_ZONELOADFLAG_THAW = 0x00000002U, /* Thaw the zone on successful * load. */ } dns_zoneloadflag_t; #define UNREACH_CACHE_SIZE 10U #define UNREACH_HOLD_TIME 600 /* 10 minutes */ #define CHECK(op) \ do { \ result = (op); \ if (result != ISC_R_SUCCESS) \ goto failure; \ } while (0) struct dns_unreachable { isc_sockaddr_t remote; isc_sockaddr_t local; atomic_uint_fast32_t expire; atomic_uint_fast32_t last; uint32_t count; }; struct dns_zonemgr { unsigned int magic; isc_mem_t *mctx; isc_refcount_t refs; isc_taskmgr_t *taskmgr; isc_timermgr_t *timermgr; isc_nm_t *netmgr; isc_taskpool_t *zonetasks; isc_taskpool_t *loadtasks; isc_task_t *task; isc_pool_t *mctxpool; isc_ratelimiter_t *checkdsrl; isc_ratelimiter_t *notifyrl; isc_ratelimiter_t *refreshrl; isc_ratelimiter_t *startupnotifyrl; isc_ratelimiter_t *startuprefreshrl; isc_rwlock_t rwlock; isc_mutex_t iolock; isc_rwlock_t urlock; /* Locked by rwlock. */ dns_zonelist_t zones; dns_zonelist_t waiting_for_xfrin; dns_zonelist_t xfrin_in_progress; /* Configuration data. */ uint32_t transfersin; uint32_t transfersperns; unsigned int checkdsrate; unsigned int notifyrate; unsigned int startupnotifyrate; unsigned int serialqueryrate; unsigned int startupserialqueryrate; /* Locked by iolock */ uint32_t iolimit; uint32_t ioactive; dns_iolist_t high; dns_iolist_t low; /* Locked by urlock. */ /* LRU cache */ struct dns_unreachable unreachable[UNREACH_CACHE_SIZE]; dns_keymgmt_t *keymgmt; isc_tlsctx_cache_t *tlsctx_cache; isc_rwlock_t tlsctx_cache_rwlock; }; /*% * Hold notify state. */ struct dns_notify { unsigned int magic; unsigned int flags; isc_mem_t *mctx; dns_zone_t *zone; dns_adbfind_t *find; dns_request_t *request; dns_name_t ns; isc_sockaddr_t dst; dns_tsigkey_t *key; dns_transport_t *transport; ISC_LINK(dns_notify_t) link; isc_event_t *event; }; #define DNS_NOTIFY_NOSOA 0x0001U #define DNS_NOTIFY_STARTUP 0x0002U /*% * Hold checkds state. */ struct dns_checkds { unsigned int magic; unsigned int flags; isc_mem_t *mctx; dns_zone_t *zone; dns_request_t *request; isc_sockaddr_t dst; dns_tsigkey_t *key; dns_transport_t *transport; ISC_LINK(dns_checkds_t) link; isc_event_t *event; }; /*% * dns_stub holds state while performing a 'stub' transfer. * 'db' is the zone's 'db' or a new one if this is the initial * transfer. */ struct dns_stub { unsigned int magic; isc_mem_t *mctx; dns_zone_t *zone; dns_db_t *db; dns_dbversion_t *version; atomic_uint_fast32_t pending_requests; }; /*% * Hold load state. */ struct dns_load { unsigned int magic; isc_mem_t *mctx; dns_zone_t *zone; dns_db_t *db; isc_time_t loadtime; dns_rdatacallbacks_t callbacks; }; /*% * Hold forward state. */ struct dns_forward { unsigned int magic; isc_mem_t *mctx; dns_zone_t *zone; isc_buffer_t *msgbuf; dns_request_t *request; uint32_t which; isc_sockaddr_t addr; dns_updatecallback_t callback; void *callback_arg; unsigned int options; ISC_LINK(dns_forward_t) link; }; /*% * Hold IO request state. */ struct dns_io { unsigned int magic; dns_zonemgr_t *zmgr; bool high; isc_task_t *task; ISC_LINK(dns_io_t) link; isc_event_t *event; }; /*% * Hold state for when we are signing a zone with a new * DNSKEY as result of an update. */ struct dns_signing { unsigned int magic; dns_db_t *db; dns_dbiterator_t *dbiterator; dns_secalg_t algorithm; uint16_t keyid; bool deleteit; bool done; ISC_LINK(dns_signing_t) link; }; struct dns_nsec3chain { unsigned int magic; dns_db_t *db; dns_dbiterator_t *dbiterator; dns_rdata_nsec3param_t nsec3param; unsigned char salt[255]; bool done; bool seen_nsec; bool delete_nsec; bool save_delete_nsec; ISC_LINK(dns_nsec3chain_t) link; }; /*%< * 'dbiterator' contains a iterator for the database. If we are creating * a NSEC3 chain only the non-NSEC3 nodes will be iterated. If we are * removing a NSEC3 chain then both NSEC3 and non-NSEC3 nodes will be * iterated. * * 'nsec3param' contains the parameters of the NSEC3 chain being created * or removed. * * 'salt' is buffer space and is referenced via 'nsec3param.salt'. * * 'seen_nsec' will be set to true if, while iterating the zone to create a * NSEC3 chain, a NSEC record is seen. * * 'delete_nsec' will be set to true if, at the completion of the creation * of a NSEC3 chain, 'seen_nsec' is true. If 'delete_nsec' is true then we * are in the process of deleting the NSEC chain. * * 'save_delete_nsec' is used to store the initial state of 'delete_nsec' * so it can be recovered in the event of a error. */ struct dns_keyfetch { isc_mem_t *mctx; dns_fixedname_t name; dns_rdataset_t keydataset; dns_rdataset_t dnskeyset; dns_rdataset_t dnskeysigset; dns_zone_t *zone; dns_db_t *db; dns_fetch_t *fetch; }; /*% * Hold state for an asynchronous load */ struct dns_asyncload { dns_zone_t *zone; unsigned int flags; dns_zt_zoneloaded_t loaded; void *loaded_arg; }; /*% * Reference to an include file encountered during loading */ struct dns_include { char *name; isc_time_t filetime; ISC_LINK(dns_include_t) link; }; /* * These can be overridden by the -T mkeytimers option on the command * line, so that we can test with shorter periods than specified in * RFC 5011. */ #define HOUR 3600 #define DAY (24 * HOUR) #define MONTH (30 * DAY) unsigned int dns_zone_mkey_hour = HOUR; unsigned int dns_zone_mkey_day = DAY; unsigned int dns_zone_mkey_month = MONTH; #define SEND_BUFFER_SIZE 2048 static void zone_settimer(dns_zone_t *, isc_time_t *); static void cancel_refresh(dns_zone_t *); static void zone_debuglog(dns_zone_t *zone, const char *, int debuglevel, const char *msg, ...) ISC_FORMAT_PRINTF(4, 5); static void notify_log(dns_zone_t *zone, int level, const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); static void dnssec_log(dns_zone_t *zone, int level, const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); static void queue_xfrin(dns_zone_t *zone); static isc_result_t update_one_rr(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata); static void zone_unload(dns_zone_t *zone); static void zone_expire(dns_zone_t *zone); static void zone_refresh(dns_zone_t *zone); static void zone_iattach(dns_zone_t *source, dns_zone_t **target); static void zone_idetach(dns_zone_t **zonep); static isc_result_t zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump); static void zone_attachdb(dns_zone_t *zone, dns_db_t *db); static void zone_detachdb(dns_zone_t *zone); static void zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs); static void zone_catz_disable(dns_zone_t *zone); static isc_result_t default_journal(dns_zone_t *zone); static void zone_xfrdone(dns_zone_t *zone, isc_result_t result); static isc_result_t zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, isc_result_t result); static void zone_needdump(dns_zone_t *zone, unsigned int delay); static void zone_shutdown(isc_task_t *, isc_event_t *); static void zone_loaddone(void *arg, isc_result_t result); static isc_result_t zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime); static void zone_namerd_tostr(dns_zone_t *zone, char *buf, size_t length); static void zone_name_tostr(dns_zone_t *zone, char *buf, size_t length); static void zone_rdclass_tostr(dns_zone_t *zone, char *buf, size_t length); static void zone_viewname_tostr(dns_zone_t *zone, char *buf, size_t length); static isc_result_t zone_send_secureserial(dns_zone_t *zone, uint32_t serial); static void refresh_callback(isc_task_t *, isc_event_t *); static void stub_callback(isc_task_t *, isc_event_t *); static void queue_soa_query(dns_zone_t *zone); static void soa_query(isc_task_t *, isc_event_t *); static void ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub); static int message_count(dns_message_t *msg, dns_section_t section, dns_rdatatype_t type); static void checkds_cancel(dns_zone_t *zone); static void checkds_send(dns_zone_t *zone); static isc_result_t checkds_createmessage(dns_zone_t *zone, dns_message_t **messagep); static void checkds_done(isc_task_t *task, isc_event_t *event); static void checkds_send_toaddr(isc_task_t *task, isc_event_t *event); static void notify_cancel(dns_zone_t *zone); static void notify_find_address(dns_notify_t *notify); static void notify_send(dns_notify_t *notify); static isc_result_t notify_createmessage(dns_zone_t *zone, unsigned int flags, dns_message_t **messagep); static void notify_done(isc_task_t *task, isc_event_t *event); static void notify_send_toaddr(isc_task_t *task, isc_event_t *event); static isc_result_t zone_dump(dns_zone_t *, bool); static void got_transfer_quota(isc_task_t *task, isc_event_t *event); static isc_result_t zmgr_start_xfrin_ifquota(dns_zonemgr_t *zmgr, dns_zone_t *zone); static void zmgr_resume_xfrs(dns_zonemgr_t *zmgr, bool multi); static void zonemgr_free(dns_zonemgr_t *zmgr); static isc_result_t zonemgr_getio(dns_zonemgr_t *zmgr, bool high, isc_task_t *task, isc_taskaction_t action, void *arg, dns_io_t **iop); static void zonemgr_putio(dns_io_t **iop); static void zonemgr_cancelio(dns_io_t *io); static void rss_post(dns_zone_t *, isc_event_t *); static isc_result_t zone_get_from_db(dns_zone_t *zone, dns_db_t *db, unsigned int *nscount, unsigned int *soacount, uint32_t *soattl, uint32_t *serial, uint32_t *refresh, uint32_t *retry, uint32_t *expire, uint32_t *minimum, unsigned int *errors); static void zone_freedbargs(dns_zone_t *zone); static void forward_callback(isc_task_t *task, isc_event_t *event); static void zone_saveunique(dns_zone_t *zone, const char *path, const char *templat); static void zone_maintenance(dns_zone_t *zone); static void zone_notify(dns_zone_t *zone, isc_time_t *now); static void dump_done(void *arg, isc_result_t result); static isc_result_t zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, bool deleteit); static isc_result_t delete_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, dns_name_t *name, dns_diff_t *diff); static void zone_rekey(dns_zone_t *zone); static isc_result_t zone_send_securedb(dns_zone_t *zone, dns_db_t *db); static dns_ttl_t zone_nsecttl(dns_zone_t *zone); static void setrl(isc_ratelimiter_t *rl, unsigned int *rate, unsigned int value); static void zone_journal_compact(dns_zone_t *zone, dns_db_t *db, uint32_t serial); static isc_result_t zone_journal_rollforward(dns_zone_t *zone, dns_db_t *db, bool *needdump, bool *fixjournal); #define ENTER zone_debuglog(zone, me, 1, "enter") static void zmgr_tlsctx_attach(dns_zonemgr_t *zmgr, isc_tlsctx_cache_t **ptlsctx_cache); /*%< * Attach to TLS client context cache used for zone transfers via * encrypted transports (e.g. XoT). * * The obtained reference needs to be detached by a call to * 'isc_tlsctx_cache_detach()' when not needed anymore. * * Requires: *\li 'zmgr' is a valid zone manager. *\li 'ptlsctx_cache' is not 'NULL' and points to 'NULL'. */ static const unsigned int dbargc_default = 1; static const char *dbargv_default[] = { "rbt" }; #define DNS_ZONE_JITTER_ADD(a, b, c) \ do { \ isc_interval_t _i; \ uint32_t _j; \ _j = (b)-isc_random_uniform((b) / 4); \ isc_interval_set(&_i, _j, 0); \ if (isc_time_add((a), &_i, (c)) != ISC_R_SUCCESS) { \ dns_zone_log(zone, ISC_LOG_WARNING, \ "epoch approaching: upgrade required: " \ "now + %s failed", \ #b); \ isc_interval_set(&_i, _j / 2, 0); \ (void)isc_time_add((a), &_i, (c)); \ } \ } while (0) #define DNS_ZONE_TIME_ADD(a, b, c) \ do { \ isc_interval_t _i; \ isc_interval_set(&_i, (b), 0); \ if (isc_time_add((a), &_i, (c)) != ISC_R_SUCCESS) { \ dns_zone_log(zone, ISC_LOG_WARNING, \ "epoch approaching: upgrade required: " \ "now + %s failed", \ #b); \ isc_interval_set(&_i, (b) / 2, 0); \ (void)isc_time_add((a), &_i, (c)); \ } \ } while (0) typedef struct nsec3param nsec3param_t; struct nsec3param { dns_rdata_nsec3param_t rdata; unsigned char data[DNS_NSEC3PARAM_BUFFERSIZE + 1]; unsigned int length; bool nsec; bool replace; bool resalt; bool lookup; ISC_LINK(nsec3param_t) link; }; typedef ISC_LIST(nsec3param_t) nsec3paramlist_t; struct np3event { isc_event_t event; nsec3param_t params; }; struct ssevent { isc_event_t event; uint32_t serial; }; struct stub_cb_args { dns_stub_t *stub; dns_tsigkey_t *tsig_key; uint16_t udpsize; int timeout; bool reqnsid; }; struct stub_glue_request { dns_request_t *request; dns_name_t name; struct stub_cb_args *args; bool ipv4; }; /*% * Increment resolver-related statistics counters. Zone must be locked. */ static void inc_stats(dns_zone_t *zone, isc_statscounter_t counter) { if (zone->stats != NULL) { isc_stats_increment(zone->stats, counter); } } /*** *** Public functions. ***/ isc_result_t dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { isc_result_t result; isc_time_t now; dns_zone_t *zone = NULL; dns_zone_t z = { .masterformat = dns_masterformat_none, .journalsize = -1, .rdclass = dns_rdataclass_none, .type = dns_zone_none, .refresh = DNS_ZONE_DEFAULTREFRESH, .retry = DNS_ZONE_DEFAULTRETRY, .maxrefresh = DNS_ZONE_MAXREFRESH, .minrefresh = DNS_ZONE_MINREFRESH, .maxretry = DNS_ZONE_MAXRETRY, .minretry = DNS_ZONE_MINRETRY, .notifytype = dns_notifytype_yes, .zero_no_soa_ttl = true, .check_names = dns_severity_ignore, .idlein = DNS_DEFAULT_IDLEIN, .idleout = DNS_DEFAULT_IDLEOUT, .maxxfrin = MAX_XFER_TIME, .maxxfrout = MAX_XFER_TIME, .sigvalidityinterval = 30 * 24 * 3600, .sigresigninginterval = 7 * 24 * 3600, .statlevel = dns_zonestat_none, .notifydelay = 5, .signatures = 10, .nodes = 100, .privatetype = (dns_rdatatype_t)0xffffU, .rpz_num = DNS_RPZ_INVALID_NUM, .requestixfr = true, .ixfr_ratio = 100, .requestexpire = true, .updatemethod = dns_updatemethod_increment, .magic = ZONE_MAGIC }; REQUIRE(zonep != NULL && *zonep == NULL); REQUIRE(mctx != NULL); TIME_NOW(&now); zone = isc_mem_get(mctx, sizeof(*zone)); *zone = z; zone->mctx = NULL; isc_mem_attach(mctx, &zone->mctx); isc_mutex_init(&zone->lock); ZONEDB_INITLOCK(&zone->dblock); /* XXX MPA check that all elements are initialised */ #ifdef DNS_ZONE_CHECKLOCK zone->locked = false; #endif /* ifdef DNS_ZONE_CHECKLOCK */ zone->notifytime = now; ISC_LINK_INIT(zone, link); isc_refcount_init(&zone->erefs, 1); isc_refcount_init(&zone->irefs, 0); dns_name_init(&zone->origin, NULL); ISC_LIST_INIT(zone->includes); ISC_LIST_INIT(zone->newincludes); atomic_init(&zone->flags, 0); atomic_init(&zone->options, 0); atomic_init(&zone->keyopts, 0); isc_time_settoepoch(&zone->expiretime); isc_time_settoepoch(&zone->refreshtime); isc_time_settoepoch(&zone->dumptime); isc_time_settoepoch(&zone->loadtime); isc_time_settoepoch(&zone->resigntime); isc_time_settoepoch(&zone->keywarntime); isc_time_settoepoch(&zone->signingtime); isc_time_settoepoch(&zone->nsec3chaintime); isc_time_settoepoch(&zone->refreshkeytime); ISC_LIST_INIT(zone->notifies); ISC_LIST_INIT(zone->checkds_requests); isc_sockaddr_any(&zone->notifysrc4); isc_sockaddr_any6(&zone->notifysrc6); isc_sockaddr_any(&zone->parentalsrc4); isc_sockaddr_any6(&zone->parentalsrc6); isc_sockaddr_any(&zone->xfrsource4); isc_sockaddr_any6(&zone->xfrsource6); isc_sockaddr_any(&zone->altxfrsource4); isc_sockaddr_any6(&zone->altxfrsource6); ISC_LINK_INIT(zone, statelink); ISC_LIST_INIT(zone->signing); ISC_LIST_INIT(zone->nsec3chain); ISC_LIST_INIT(zone->setnsec3param_queue); ISC_LIST_INIT(zone->forwards); ISC_LIST_INIT(zone->rss_events); ISC_LIST_INIT(zone->rss_post); result = isc_stats_create(mctx, &zone->gluecachestats, dns_gluecachestatscounter_max); if (result != ISC_R_SUCCESS) { goto free_refs; } /* Must be after magic is set. */ dns_zone_setdbtype(zone, dbargc_default, dbargv_default); ISC_EVENT_INIT(&zone->ctlevent, sizeof(zone->ctlevent), 0, NULL, DNS_EVENT_ZONECONTROL, zone_shutdown, zone, zone, NULL, NULL); *zonep = zone; return (ISC_R_SUCCESS); free_refs: isc_refcount_decrement0(&zone->erefs); isc_refcount_destroy(&zone->erefs); isc_refcount_destroy(&zone->irefs); ZONEDB_DESTROYLOCK(&zone->dblock); isc_mutex_destroy(&zone->lock); isc_mem_putanddetach(&zone->mctx, zone, sizeof(*zone)); return (result); } static void clear_keylist(dns_dnsseckeylist_t *list, isc_mem_t *mctx) { dns_dnsseckey_t *key; while (!ISC_LIST_EMPTY(*list)) { key = ISC_LIST_HEAD(*list); ISC_LIST_UNLINK(*list, key, link); dns_dnsseckey_destroy(mctx, &key); } } /* * Free a zone. Because we require that there be no more * outstanding events or references, no locking is necessary. */ static void zone_free(dns_zone_t *zone) { dns_signing_t *signing; dns_nsec3chain_t *nsec3chain; isc_event_t *event; dns_include_t *include; REQUIRE(DNS_ZONE_VALID(zone)); isc_refcount_destroy(&zone->erefs); isc_refcount_destroy(&zone->irefs); REQUIRE(!LOCKED_ZONE(zone)); REQUIRE(zone->timer == NULL); REQUIRE(zone->zmgr == NULL); /* * Managed objects. Order is important. */ if (zone->request != NULL) { dns_request_destroy(&zone->request); /* XXXMPA */ } INSIST(zone->readio == NULL); INSIST(zone->statelist == NULL); INSIST(zone->writeio == NULL); INSIST(zone->view == NULL); INSIST(zone->prev_view == NULL); if (zone->task != NULL) { isc_task_detach(&zone->task); } if (zone->loadtask != NULL) { isc_task_detach(&zone->loadtask); } /* Unmanaged objects */ while (!ISC_LIST_EMPTY(zone->setnsec3param_queue)) { event = ISC_LIST_HEAD(zone->setnsec3param_queue); ISC_LIST_UNLINK(zone->setnsec3param_queue, event, ev_link); isc_event_free(&event); } while (!ISC_LIST_EMPTY(zone->rss_post)) { event = ISC_LIST_HEAD(zone->rss_post); ISC_LIST_UNLINK(zone->rss_post, event, ev_link); isc_event_free(&event); } for (signing = ISC_LIST_HEAD(zone->signing); signing != NULL; signing = ISC_LIST_HEAD(zone->signing)) { ISC_LIST_UNLINK(zone->signing, signing, link); dns_db_detach(&signing->db); dns_dbiterator_destroy(&signing->dbiterator); isc_mem_put(zone->mctx, signing, sizeof *signing); } for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); nsec3chain != NULL; nsec3chain = ISC_LIST_HEAD(zone->nsec3chain)) { ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link); dns_db_detach(&nsec3chain->db); dns_dbiterator_destroy(&nsec3chain->dbiterator); isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); } for (include = ISC_LIST_HEAD(zone->includes); include != NULL; include = ISC_LIST_HEAD(zone->includes)) { ISC_LIST_UNLINK(zone->includes, include, link); isc_mem_free(zone->mctx, include->name); isc_mem_put(zone->mctx, include, sizeof *include); } for (include = ISC_LIST_HEAD(zone->newincludes); include != NULL; include = ISC_LIST_HEAD(zone->newincludes)) { ISC_LIST_UNLINK(zone->newincludes, include, link); isc_mem_free(zone->mctx, include->name); isc_mem_put(zone->mctx, include, sizeof *include); } if (zone->masterfile != NULL) { isc_mem_free(zone->mctx, zone->masterfile); } zone->masterfile = NULL; if (zone->keydirectory != NULL) { isc_mem_free(zone->mctx, zone->keydirectory); } zone->keydirectory = NULL; if (zone->kasp != NULL) { dns_kasp_detach(&zone->kasp); } if (!ISC_LIST_EMPTY(zone->checkds_ok)) { clear_keylist(&zone->checkds_ok, zone->mctx); } zone->journalsize = -1; if (zone->journal != NULL) { isc_mem_free(zone->mctx, zone->journal); } zone->journal = NULL; if (zone->stats != NULL) { isc_stats_detach(&zone->stats); } if (zone->requeststats != NULL) { isc_stats_detach(&zone->requeststats); } if (zone->rcvquerystats != NULL) { dns_stats_detach(&zone->rcvquerystats); } if (zone->dnssecsignstats != NULL) { dns_stats_detach(&zone->dnssecsignstats); } if (zone->db != NULL) { zone_detachdb(zone); } if (zone->rpzs != NULL) { REQUIRE(zone->rpz_num < zone->rpzs->p.num_zones); dns_rpz_detach_rpzs(&zone->rpzs); zone->rpz_num = DNS_RPZ_INVALID_NUM; } if (zone->catzs != NULL) { dns_catz_detach_catzs(&zone->catzs); } zone_freedbargs(zone); dns_zone_setparentals(zone, NULL, NULL, NULL, 0); dns_zone_setprimaries(zone, NULL, NULL, NULL, 0); dns_zone_setalsonotify(zone, NULL, NULL, NULL, 0); zone->check_names = dns_severity_ignore; if (zone->update_acl != NULL) { dns_acl_detach(&zone->update_acl); } if (zone->forward_acl != NULL) { dns_acl_detach(&zone->forward_acl); } if (zone->notify_acl != NULL) { dns_acl_detach(&zone->notify_acl); } if (zone->query_acl != NULL) { dns_acl_detach(&zone->query_acl); } if (zone->queryon_acl != NULL) { dns_acl_detach(&zone->queryon_acl); } if (zone->xfr_acl != NULL) { dns_acl_detach(&zone->xfr_acl); } if (dns_name_dynamic(&zone->origin)) { dns_name_free(&zone->origin, zone->mctx); } if (zone->strnamerd != NULL) { isc_mem_free(zone->mctx, zone->strnamerd); } if (zone->strname != NULL) { isc_mem_free(zone->mctx, zone->strname); } if (zone->strrdclass != NULL) { isc_mem_free(zone->mctx, zone->strrdclass); } if (zone->strviewname != NULL) { isc_mem_free(zone->mctx, zone->strviewname); } if (zone->ssutable != NULL) { dns_ssutable_detach(&zone->ssutable); } if (zone->gluecachestats != NULL) { isc_stats_detach(&zone->gluecachestats); } /* last stuff */ ZONEDB_DESTROYLOCK(&zone->dblock); isc_mutex_destroy(&zone->lock); zone->magic = 0; isc_mem_putanddetach(&zone->mctx, zone, sizeof(*zone)); } /* * Returns true iff this the signed side of an inline-signing zone. * Caller should hold zone lock. */ static bool inline_secure(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); if (zone->raw != NULL) { return (true); } return (false); } /* * Returns true iff this the unsigned side of an inline-signing zone * Caller should hold zone lock. */ static bool inline_raw(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); if (zone->secure != NULL) { return (true); } return (false); } /* * Single shot. */ void dns_zone_setclass(dns_zone_t *zone, dns_rdataclass_t rdclass) { char namebuf[1024]; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(rdclass != dns_rdataclass_none); /* * Test and set. */ LOCK_ZONE(zone); INSIST(zone != zone->raw); REQUIRE(zone->rdclass == dns_rdataclass_none || zone->rdclass == rdclass); zone->rdclass = rdclass; if (zone->strnamerd != NULL) { isc_mem_free(zone->mctx, zone->strnamerd); } if (zone->strrdclass != NULL) { isc_mem_free(zone->mctx, zone->strrdclass); } zone_namerd_tostr(zone, namebuf, sizeof namebuf); zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf); zone_rdclass_tostr(zone, namebuf, sizeof namebuf); zone->strrdclass = isc_mem_strdup(zone->mctx, namebuf); if (inline_secure(zone)) { dns_zone_setclass(zone->raw, rdclass); } UNLOCK_ZONE(zone); } dns_rdataclass_t dns_zone_getclass(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->rdclass); } void dns_zone_setnotifytype(dns_zone_t *zone, dns_notifytype_t notifytype) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->notifytype = notifytype; UNLOCK_ZONE(zone); } isc_result_t dns_zone_getserial(dns_zone_t *zone, uint32_t *serialp) { isc_result_t result; unsigned int soacount; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(serialp != NULL); LOCK_ZONE(zone); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, serialp, NULL, NULL, NULL, NULL, NULL); if (result == ISC_R_SUCCESS && soacount == 0) { result = ISC_R_FAILURE; } } else { result = DNS_R_NOTLOADED; } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); UNLOCK_ZONE(zone); return (result); } /* * Single shot. */ void dns_zone_settype(dns_zone_t *zone, dns_zonetype_t type) { char namebuf[1024]; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(type != dns_zone_none); /* * Test and set. */ LOCK_ZONE(zone); REQUIRE(zone->type == dns_zone_none || zone->type == type); zone->type = type; if (zone->strnamerd != NULL) { isc_mem_free(zone->mctx, zone->strnamerd); } zone_namerd_tostr(zone, namebuf, sizeof namebuf); zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf); UNLOCK_ZONE(zone); } static void zone_freedbargs(dns_zone_t *zone) { unsigned int i; /* Free the old database argument list. */ if (zone->db_argv != NULL) { for (i = 0; i < zone->db_argc; i++) { isc_mem_free(zone->mctx, zone->db_argv[i]); } isc_mem_put(zone->mctx, zone->db_argv, zone->db_argc * sizeof(*zone->db_argv)); } zone->db_argc = 0; zone->db_argv = NULL; } isc_result_t dns_zone_getdbtype(dns_zone_t *zone, char ***argv, isc_mem_t *mctx) { size_t size = 0; unsigned int i; isc_result_t result = ISC_R_SUCCESS; void *mem; char **tmp, *tmp2, *base; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(argv != NULL && *argv == NULL); LOCK_ZONE(zone); size = (zone->db_argc + 1) * sizeof(char *); for (i = 0; i < zone->db_argc; i++) { size += strlen(zone->db_argv[i]) + 1; } mem = isc_mem_allocate(mctx, size); { tmp = mem; tmp2 = mem; base = mem; tmp2 += (zone->db_argc + 1) * sizeof(char *); for (i = 0; i < zone->db_argc; i++) { *tmp++ = tmp2; strlcpy(tmp2, zone->db_argv[i], size - (tmp2 - base)); tmp2 += strlen(tmp2) + 1; } *tmp = NULL; } UNLOCK_ZONE(zone); *argv = mem; return (result); } void dns_zone_setdbtype(dns_zone_t *zone, unsigned int dbargc, const char *const *dbargv) { char **argv = NULL; unsigned int i; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(dbargc >= 1); REQUIRE(dbargv != NULL); LOCK_ZONE(zone); /* Set up a new database argument list. */ argv = isc_mem_get(zone->mctx, dbargc * sizeof(*argv)); for (i = 0; i < dbargc; i++) { argv[i] = NULL; } for (i = 0; i < dbargc; i++) { argv[i] = isc_mem_strdup(zone->mctx, dbargv[i]); } /* Free the old list. */ zone_freedbargs(zone); zone->db_argc = dbargc; zone->db_argv = argv; UNLOCK_ZONE(zone); } static void dns_zone_setview_helper(dns_zone_t *zone, dns_view_t *view) { char namebuf[1024]; if (zone->prev_view == NULL && zone->view != NULL) { dns_view_weakattach(zone->view, &zone->prev_view); } INSIST(zone != zone->raw); if (zone->view != NULL) { dns_view_sfd_del(zone->view, &zone->origin); dns_view_weakdetach(&zone->view); } dns_view_weakattach(view, &zone->view); dns_view_sfd_add(view, &zone->origin); if (zone->strviewname != NULL) { isc_mem_free(zone->mctx, zone->strviewname); } if (zone->strnamerd != NULL) { isc_mem_free(zone->mctx, zone->strnamerd); } zone_namerd_tostr(zone, namebuf, sizeof namebuf); zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf); zone_viewname_tostr(zone, namebuf, sizeof namebuf); zone->strviewname = isc_mem_strdup(zone->mctx, namebuf); if (inline_secure(zone)) { dns_zone_setview(zone->raw, view); } } void dns_zone_setview(dns_zone_t *zone, dns_view_t *view) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); dns_zone_setview_helper(zone, view); UNLOCK_ZONE(zone); } dns_view_t * dns_zone_getview(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->view); } void dns_zone_setviewcommit(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->prev_view != NULL) { dns_view_weakdetach(&zone->prev_view); } if (inline_secure(zone)) { dns_zone_setviewcommit(zone->raw); } UNLOCK_ZONE(zone); } void dns_zone_setviewrevert(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->prev_view != NULL) { dns_zone_setview_helper(zone, zone->prev_view); dns_view_weakdetach(&zone->prev_view); } if (zone->catzs != NULL) { zone_catz_enable(zone, zone->catzs); } if (inline_secure(zone)) { dns_zone_setviewrevert(zone->raw); } UNLOCK_ZONE(zone); } isc_result_t dns_zone_setorigin(dns_zone_t *zone, const dns_name_t *origin) { isc_result_t result = ISC_R_SUCCESS; char namebuf[1024]; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(origin != NULL); LOCK_ZONE(zone); INSIST(zone != zone->raw); if (dns_name_dynamic(&zone->origin)) { dns_name_free(&zone->origin, zone->mctx); dns_name_init(&zone->origin, NULL); } dns_name_dup(origin, zone->mctx, &zone->origin); if (zone->strnamerd != NULL) { isc_mem_free(zone->mctx, zone->strnamerd); } if (zone->strname != NULL) { isc_mem_free(zone->mctx, zone->strname); } zone_namerd_tostr(zone, namebuf, sizeof namebuf); zone->strnamerd = isc_mem_strdup(zone->mctx, namebuf); zone_name_tostr(zone, namebuf, sizeof namebuf); zone->strname = isc_mem_strdup(zone->mctx, namebuf); if (inline_secure(zone)) { result = dns_zone_setorigin(zone->raw, origin); } UNLOCK_ZONE(zone); return (result); } static isc_result_t dns_zone_setstring(dns_zone_t *zone, char **field, const char *value) { char *copy; if (value != NULL) { copy = isc_mem_strdup(zone->mctx, value); } else { copy = NULL; } if (*field != NULL) { isc_mem_free(zone->mctx, *field); } *field = copy; return (ISC_R_SUCCESS); } isc_result_t dns_zone_setfile(dns_zone_t *zone, const char *file, dns_masterformat_t format, const dns_master_style_t *style) { isc_result_t result = ISC_R_SUCCESS; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(zone->stream == NULL); LOCK_ZONE(zone); result = dns_zone_setstring(zone, &zone->masterfile, file); if (result == ISC_R_SUCCESS) { zone->masterformat = format; if (format == dns_masterformat_text) { zone->masterstyle = style; } result = default_journal(zone); } UNLOCK_ZONE(zone); return (result); } const char * dns_zone_getfile(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->masterfile); } isc_result_t dns_zone_setstream(dns_zone_t *zone, const FILE *stream, dns_masterformat_t format, const dns_master_style_t *style) { isc_result_t result = ISC_R_SUCCESS; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(stream != NULL); REQUIRE(zone->masterfile == NULL); LOCK_ZONE(zone); zone->stream = stream; zone->masterformat = format; if (format == dns_masterformat_text) { zone->masterstyle = style; } result = default_journal(zone); UNLOCK_ZONE(zone); return (result); } dns_ttl_t dns_zone_getmaxttl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->maxttl); } void dns_zone_setmaxttl(dns_zone_t *zone, dns_ttl_t maxttl) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (maxttl != 0) { DNS_ZONE_SETOPTION(zone, DNS_ZONEOPT_CHECKTTL); } else { DNS_ZONE_CLROPTION(zone, DNS_ZONEOPT_CHECKTTL); } zone->maxttl = maxttl; UNLOCK_ZONE(zone); return; } static isc_result_t default_journal(dns_zone_t *zone) { isc_result_t result; char *journal; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(LOCKED_ZONE(zone)); if (zone->masterfile != NULL) { /* Calculate string length including '\0'. */ int len = strlen(zone->masterfile) + sizeof(".jnl"); journal = isc_mem_allocate(zone->mctx, len); strlcpy(journal, zone->masterfile, len); strlcat(journal, ".jnl", len); } else { journal = NULL; } result = dns_zone_setstring(zone, &zone->journal, journal); if (journal != NULL) { isc_mem_free(zone->mctx, journal); } return (result); } isc_result_t dns_zone_setjournal(dns_zone_t *zone, const char *myjournal) { isc_result_t result = ISC_R_SUCCESS; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); result = dns_zone_setstring(zone, &zone->journal, myjournal); UNLOCK_ZONE(zone); return (result); } char * dns_zone_getjournal(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->journal); } /* * Return true iff the zone is "dynamic", in the sense that the zone's * master file (if any) is written by the server, rather than being * updated manually and read by the server. * * This is true for secondary zones, mirror zones, stub zones, key zones, * and zones that allow dynamic updates either by having an update * policy ("ssutable") or an "allow-update" ACL with a value other than * exactly "{ none; }". */ bool dns_zone_isdynamic(dns_zone_t *zone, bool ignore_freeze) { REQUIRE(DNS_ZONE_VALID(zone)); if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_stub || zone->type == dns_zone_key || (zone->type == dns_zone_redirect && zone->primaries != NULL)) { return (true); } /* Inline zones are always dynamic. */ if (zone->type == dns_zone_primary && zone->raw != NULL) { return (true); } /* If !ignore_freeze, we need check whether updates are disabled. */ if (zone->type == dns_zone_primary && (!zone->update_disabled || ignore_freeze) && ((zone->ssutable != NULL) || (zone->update_acl != NULL && !dns_acl_isnone(zone->update_acl)))) { return (true); } return (false); } /* * Set the response policy index and information for a zone. */ isc_result_t dns_zone_rpz_enable(dns_zone_t *zone, dns_rpz_zones_t *rpzs, dns_rpz_num_t rpz_num) { /* * Only RBTDB zones can be used for response policy zones, * because only they have the code to create the summary data. * Only zones that are loaded instead of mmap()ed create the * summary data and so can be policy zones. */ if (strcmp(zone->db_argv[0], "rbt") != 0 && strcmp(zone->db_argv[0], "rbt64") != 0) { return (ISC_R_NOTIMPLEMENTED); } /* * This must happen only once or be redundant. */ LOCK_ZONE(zone); if (zone->rpzs != NULL) { REQUIRE(zone->rpzs == rpzs && zone->rpz_num == rpz_num); } else { REQUIRE(zone->rpz_num == DNS_RPZ_INVALID_NUM); dns_rpz_attach_rpzs(rpzs, &zone->rpzs); zone->rpz_num = rpz_num; } rpzs->defined |= DNS_RPZ_ZBIT(rpz_num); UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } dns_rpz_num_t dns_zone_get_rpz_num(dns_zone_t *zone) { return (zone->rpz_num); } /* * If a zone is a response policy zone, mark its new database. */ void dns_zone_rpz_enable_db(dns_zone_t *zone, dns_db_t *db) { isc_result_t result; if (zone->rpz_num == DNS_RPZ_INVALID_NUM) { return; } REQUIRE(zone->rpzs != NULL); result = dns_db_updatenotify_register(db, dns_rpz_dbupdate_callback, zone->rpzs->zones[zone->rpz_num]); REQUIRE(result == ISC_R_SUCCESS); } static void dns_zone_rpz_disable_db(dns_zone_t *zone, dns_db_t *db) { if (zone->rpz_num == DNS_RPZ_INVALID_NUM) { return; } REQUIRE(zone->rpzs != NULL); (void)dns_db_updatenotify_unregister(db, dns_rpz_dbupdate_callback, zone->rpzs->zones[zone->rpz_num]); } /* * If a zone is a catalog zone, attach it to update notification in database. */ void dns_zone_catz_enable_db(dns_zone_t *zone, dns_db_t *db) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(db != NULL); if (zone->catzs != NULL) { dns_catz_dbupdate_register(db, zone->catzs); } } static void dns_zone_catz_disable_db(dns_zone_t *zone, dns_db_t *db) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(db != NULL); if (zone->catzs != NULL) { dns_catz_dbupdate_unregister(db, zone->catzs); } } static void zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(catzs != NULL); INSIST(zone->catzs == NULL || zone->catzs == catzs); dns_catz_catzs_set_view(catzs, zone->view); if (zone->catzs == NULL) { dns_catz_attach_catzs(catzs, &zone->catzs); } } void dns_zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone_catz_enable(zone, catzs); UNLOCK_ZONE(zone); } static void zone_catz_disable(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); if (zone->catzs != NULL) { if (zone->db != NULL) { dns_zone_catz_disable_db(zone, zone->db); } dns_catz_detach_catzs(&zone->catzs); } } void dns_zone_catz_disable(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone_catz_disable(zone); UNLOCK_ZONE(zone); } bool dns_zone_catz_is_enabled(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->catzs != NULL); } /* * Set catalog zone ownership of the zone */ void dns_zone_set_parentcatz(dns_zone_t *zone, dns_catz_zone_t *catz) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(catz != NULL); LOCK_ZONE(zone); INSIST(zone->parentcatz == NULL || zone->parentcatz == catz); zone->parentcatz = catz; UNLOCK_ZONE(zone); } dns_catz_zone_t * dns_zone_get_parentcatz(const dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->parentcatz); } static bool zone_touched(dns_zone_t *zone) { isc_result_t result; isc_time_t modtime; dns_include_t *include; REQUIRE(DNS_ZONE_VALID(zone)); result = isc_file_getmodtime(zone->masterfile, &modtime); if (result != ISC_R_SUCCESS || isc_time_compare(&modtime, &zone->loadtime) > 0) { return (true); } for (include = ISC_LIST_HEAD(zone->includes); include != NULL; include = ISC_LIST_NEXT(include, link)) { result = isc_file_getmodtime(include->name, &modtime); if (result != ISC_R_SUCCESS || isc_time_compare(&modtime, &include->filetime) > 0) { return (true); } } return (false); } /* * Note: when dealing with inline-signed zones, external callers will always * call zone_load() for the secure zone; zone_load() calls itself recursively * in order to load the raw zone. */ static isc_result_t zone_load(dns_zone_t *zone, unsigned int flags, bool locked) { isc_result_t result; isc_time_t now; isc_time_t loadtime; dns_db_t *db = NULL; bool rbt, hasraw, is_dynamic; REQUIRE(DNS_ZONE_VALID(zone)); if (!locked) { LOCK_ZONE(zone); } INSIST(zone != zone->raw); hasraw = inline_secure(zone); if (hasraw) { /* * We are trying to load an inline-signed zone. First call * self recursively to try loading the raw version of the zone. * Assuming the raw zone file is readable, there are two * possibilities: * * a) the raw zone was not yet loaded and thus it will be * loaded now, synchronously; if this succeeds, a * subsequent attempt to load the signed zone file will * take place and thus zone_postload() will be called * twice: first for the raw zone and then for the secure * zone; the latter call will take care of syncing the raw * version with the secure version, * * b) the raw zone was already loaded and we are trying to * reload it, which will happen asynchronously; this means * zone_postload() will only be called for the raw zone * because "result" returned by the zone_load() call below * will not be ISC_R_SUCCESS but rather DNS_R_CONTINUE; * zone_postload() called for the raw zone will take care * of syncing the raw version with the secure version. */ result = zone_load(zone->raw, flags, false); if (result != ISC_R_SUCCESS) { if (!locked) { UNLOCK_ZONE(zone); } return (result); } LOCK_ZONE(zone->raw); } TIME_NOW(&now); INSIST(zone->type != dns_zone_none); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADING)) { if ((flags & DNS_ZONELOADFLAG_THAW) != 0) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_THAW); } result = DNS_R_CONTINUE; goto cleanup; } INSIST(zone->db_argc >= 1); rbt = strcmp(zone->db_argv[0], "rbt") == 0 || strcmp(zone->db_argv[0], "rbt64") == 0; if (zone->db != NULL && zone->masterfile == NULL && rbt) { /* * The zone has no master file configured. */ result = ISC_R_SUCCESS; goto cleanup; } is_dynamic = dns_zone_isdynamic(zone, false); if (zone->db != NULL && is_dynamic) { /* * This is a secondary, stub, or dynamically updated zone * being reloaded. Do nothing - the database we already * have is guaranteed to be up-to-date. */ if (zone->type == dns_zone_primary && !hasraw) { result = DNS_R_DYNAMIC; } else { result = ISC_R_SUCCESS; } goto cleanup; } /* * Store the current time before the zone is loaded, so that if the * file changes between the time of the load and the time that * zone->loadtime is set, then the file will still be reloaded * the next time dns_zone_load is called. */ TIME_NOW(&loadtime); /* * Don't do the load if the file that stores the zone is older * than the last time the zone was loaded. If the zone has not * been loaded yet, zone->loadtime will be the epoch. */ if (zone->masterfile != NULL) { isc_time_t filetime; /* * The file is already loaded. If we are just doing a * "rndc reconfig", we are done. */ if (!isc_time_isepoch(&zone->loadtime) && (flags & DNS_ZONELOADFLAG_NOSTAT) != 0) { result = ISC_R_SUCCESS; goto cleanup; } if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && !zone_touched(zone)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "skipping load: master file " "older than last load"); result = DNS_R_UPTODATE; goto cleanup; } /* * If the file modification time is in the past * set loadtime to that value. */ result = isc_file_getmodtime(zone->masterfile, &filetime); if (result == ISC_R_SUCCESS && isc_time_compare(&loadtime, &filetime) > 0) { loadtime = filetime; } } /* * Built in zones (with the exception of empty zones) don't need * to be reloaded. */ if (zone->type == dns_zone_primary && strcmp(zone->db_argv[0], "_builtin") == 0 && (zone->db_argc < 2 || strcmp(zone->db_argv[1], "empty") != 0) && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) { result = ISC_R_SUCCESS; goto cleanup; } /* * Zones associated with a DLZ don't need to be loaded either, * but we need to associate the database with the zone object. */ if (strcmp(zone->db_argv[0], "dlz") == 0) { dns_dlzdb_t *dlzdb; dns_dlzfindzone_t findzone; for (dlzdb = ISC_LIST_HEAD(zone->view->dlz_unsearched); dlzdb != NULL; dlzdb = ISC_LIST_NEXT(dlzdb, link)) { INSIST(DNS_DLZ_VALID(dlzdb)); if (strcmp(zone->db_argv[1], dlzdb->dlzname) == 0) { break; } } if (dlzdb == NULL) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "DLZ %s does not exist or is set " "to 'search yes;'", zone->db_argv[1]); result = ISC_R_NOTFOUND; goto cleanup; } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); /* ask SDLZ driver if the zone is supported */ findzone = dlzdb->implementation->methods->findzone; result = (*findzone)(dlzdb->implementation->driverarg, dlzdb->dbdata, dlzdb->mctx, zone->view->rdclass, &zone->origin, NULL, NULL, &db); if (result != ISC_R_NOTFOUND) { if (zone->db != NULL) { zone_detachdb(zone); } zone_attachdb(zone, db); dns_db_detach(&db); result = ISC_R_SUCCESS; } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); if (result == ISC_R_SUCCESS) { if (dlzdb->configure_callback == NULL) { goto cleanup; } result = (*dlzdb->configure_callback)(zone->view, dlzdb, zone); if (result != ISC_R_SUCCESS) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "DLZ configuration callback: %s", isc_result_totext(result)); } } goto cleanup; } if ((zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_stub || (zone->type == dns_zone_redirect && zone->primaries != NULL)) && rbt) { if (zone->stream == NULL && (zone->masterfile == NULL || !isc_file_exists(zone->masterfile))) { if (zone->masterfile != NULL) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "no master file"); } zone->refreshtime = now; if (zone->task != NULL) { zone_settimer(zone, &now); } result = ISC_R_SUCCESS; goto cleanup; } } dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "starting load"); result = dns_db_create(zone->mctx, zone->db_argv[0], &zone->origin, (zone->type == dns_zone_stub) ? dns_dbtype_stub : dns_dbtype_zone, zone->rdclass, zone->db_argc - 1, zone->db_argv + 1, &db); if (result != ISC_R_SUCCESS) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "loading zone: creating database: %s", isc_result_totext(result)); goto cleanup; } dns_db_settask(db, zone->task); if (zone->type == dns_zone_primary || zone->type == dns_zone_secondary || zone->type == dns_zone_mirror) { result = dns_db_setgluecachestats(db, zone->gluecachestats); if (result == ISC_R_NOTIMPLEMENTED) { result = ISC_R_SUCCESS; } if (result != ISC_R_SUCCESS) { goto cleanup; } } if (!dns_db_ispersistent(db)) { if (zone->masterfile != NULL || zone->stream != NULL) { result = zone_startload(db, zone, loadtime); } else { result = DNS_R_NOMASTERFILE; if (zone->type == dns_zone_primary || (zone->type == dns_zone_redirect && zone->primaries == NULL)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "loading zone: " "no master file configured"); goto cleanup; } dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, "loading zone: " "no master file configured: continuing"); } } if (result == DNS_R_CONTINUE) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADING); if ((flags & DNS_ZONELOADFLAG_THAW) != 0) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_THAW); } goto cleanup; } result = zone_postload(zone, db, loadtime, result); cleanup: if (hasraw) { UNLOCK_ZONE(zone->raw); } if (!locked) { UNLOCK_ZONE(zone); } if (db != NULL) { dns_db_detach(&db); } return (result); } isc_result_t dns_zone_load(dns_zone_t *zone, bool newonly) { return (zone_load(zone, newonly ? DNS_ZONELOADFLAG_NOSTAT : 0, false)); } static void zone_asyncload(isc_task_t *task, isc_event_t *event) { dns_asyncload_t *asl = event->ev_arg; dns_zone_t *zone = asl->zone; isc_result_t result; UNUSED(task); REQUIRE(DNS_ZONE_VALID(zone)); isc_event_free(&event); LOCK_ZONE(zone); result = zone_load(zone, asl->flags, true); if (result != DNS_R_CONTINUE) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADPENDING); } UNLOCK_ZONE(zone); /* Inform the zone table we've finished loading */ if (asl->loaded != NULL) { (asl->loaded)(asl->loaded_arg, zone, task); } /* Reduce the quantum */ isc_task_setquantum(zone->loadtask, 1); isc_mem_put(zone->mctx, asl, sizeof(*asl)); dns_zone_idetach(&zone); } isc_result_t dns_zone_asyncload(dns_zone_t *zone, bool newonly, dns_zt_zoneloaded_t done, void *arg) { isc_event_t *e; dns_asyncload_t *asl = NULL; REQUIRE(DNS_ZONE_VALID(zone)); if (zone->zmgr == NULL) { return (ISC_R_FAILURE); } /* If we already have a load pending, stop now */ LOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING)) { UNLOCK_ZONE(zone); return (ISC_R_ALREADYRUNNING); } asl = isc_mem_get(zone->mctx, sizeof(*asl)); asl->zone = NULL; asl->flags = newonly ? DNS_ZONELOADFLAG_NOSTAT : 0; asl->loaded = done; asl->loaded_arg = arg; e = isc_event_allocate(zone->zmgr->mctx, zone->zmgr, DNS_EVENT_ZONELOAD, zone_asyncload, asl, sizeof(isc_event_t)); zone_iattach(zone, &asl->zone); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADPENDING); isc_task_send(zone->loadtask, &e); UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } bool dns__zone_loadpending(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING)); } isc_result_t dns_zone_loadandthaw(dns_zone_t *zone) { isc_result_t result; if (inline_raw(zone)) { result = zone_load(zone->secure, DNS_ZONELOADFLAG_THAW, false); } else { /* * When thawing a zone, we don't know what changes * have been made. If we do DNSSEC maintenance on this * zone, schedule a full sign for this zone. */ if (zone->type == dns_zone_primary && DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) { DNS_ZONEKEY_SETOPTION(zone, DNS_ZONEKEY_FULLSIGN); } result = zone_load(zone, DNS_ZONELOADFLAG_THAW, false); } switch (result) { case DNS_R_CONTINUE: /* Deferred thaw. */ break; case DNS_R_UPTODATE: case ISC_R_SUCCESS: case DNS_R_SEENINCLUDE: zone->update_disabled = false; break; case DNS_R_NOMASTERFILE: zone->update_disabled = false; break; default: /* Error, remain in disabled state. */ break; } return (result); } static unsigned int get_primary_options(dns_zone_t *zone) { unsigned int options; options = DNS_MASTER_ZONE | DNS_MASTER_RESIGN; if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || (zone->type == dns_zone_redirect && zone->primaries == NULL)) { options |= DNS_MASTER_SECONDARY; } if (zone->type == dns_zone_key) { options |= DNS_MASTER_KEY; } if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNS)) { options |= DNS_MASTER_CHECKNS; } if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_FATALNS)) { options |= DNS_MASTER_FATALNS; } if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMES)) { options |= DNS_MASTER_CHECKNAMES; } if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMESFAIL)) { options |= DNS_MASTER_CHECKNAMESFAIL; } if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKMX)) { options |= DNS_MASTER_CHECKMX; } if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKMXFAIL)) { options |= DNS_MASTER_CHECKMXFAIL; } if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKWILDCARD)) { options |= DNS_MASTER_CHECKWILDCARD; } if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKTTL)) { options |= DNS_MASTER_CHECKTTL; } return (options); } static void zone_registerinclude(const char *filename, void *arg) { isc_result_t result; dns_zone_t *zone = (dns_zone_t *)arg; dns_include_t *inc = NULL; REQUIRE(DNS_ZONE_VALID(zone)); if (filename == NULL) { return; } /* * Suppress duplicates. */ for (inc = ISC_LIST_HEAD(zone->newincludes); inc != NULL; inc = ISC_LIST_NEXT(inc, link)) { if (strcmp(filename, inc->name) == 0) { return; } } inc = isc_mem_get(zone->mctx, sizeof(dns_include_t)); inc->name = isc_mem_strdup(zone->mctx, filename); ISC_LINK_INIT(inc, link); result = isc_file_getmodtime(filename, &inc->filetime); if (result != ISC_R_SUCCESS) { isc_time_settoepoch(&inc->filetime); } ISC_LIST_APPEND(zone->newincludes, inc, link); } static void zone_gotreadhandle(isc_task_t *task, isc_event_t *event) { dns_load_t *load = event->ev_arg; isc_result_t result = ISC_R_SUCCESS; unsigned int options; REQUIRE(DNS_LOAD_VALID(load)); if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) { result = ISC_R_CANCELED; } isc_event_free(&event); if (result == ISC_R_CANCELED) { goto fail; } options = get_primary_options(load->zone); result = dns_master_loadfileinc( load->zone->masterfile, dns_db_origin(load->db), dns_db_origin(load->db), load->zone->rdclass, options, 0, &load->callbacks, task, zone_loaddone, load, &load->zone->lctx, zone_registerinclude, load->zone, load->zone->mctx, load->zone->masterformat, load->zone->maxttl); if (result != ISC_R_SUCCESS && result != DNS_R_CONTINUE && result != DNS_R_SEENINCLUDE) { goto fail; } return; fail: zone_loaddone(load, result); } static void get_raw_serial(dns_zone_t *raw, dns_masterrawheader_t *rawdata) { isc_result_t result; unsigned int soacount; LOCK(&raw->lock); if (raw->db != NULL) { result = zone_get_from_db(raw, raw->db, NULL, &soacount, NULL, &rawdata->sourceserial, NULL, NULL, NULL, NULL, NULL); if (result == ISC_R_SUCCESS && soacount > 0U) { rawdata->flags |= DNS_MASTERRAW_SOURCESERIALSET; } } UNLOCK(&raw->lock); } static void zone_gotwritehandle(isc_task_t *task, isc_event_t *event) { const char me[] = "zone_gotwritehandle"; dns_zone_t *zone = event->ev_arg; isc_result_t result = ISC_R_SUCCESS; dns_dbversion_t *version = NULL; dns_masterrawheader_t rawdata; dns_db_t *db = NULL; REQUIRE(DNS_ZONE_VALID(zone)); INSIST(task == zone->task); ENTER; if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) { result = ISC_R_CANCELED; } isc_event_free(&event); if (result == ISC_R_CANCELED) { goto fail; } LOCK_ZONE(zone); INSIST(zone != zone->raw); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db != NULL) { const dns_master_style_t *output_style; dns_db_currentversion(db, &version); dns_master_initrawheader(&rawdata); if (inline_secure(zone)) { get_raw_serial(zone->raw, &rawdata); } if (zone->type == dns_zone_key) { output_style = &dns_master_style_keyzone; } else if (zone->masterstyle != NULL) { output_style = zone->masterstyle; } else { output_style = &dns_master_style_default; } result = dns_master_dumpasync( zone->mctx, db, version, output_style, zone->masterfile, zone->task, dump_done, zone, &zone->dctx, zone->masterformat, &rawdata); dns_db_closeversion(db, &version, false); } else { result = ISC_R_CANCELED; } if (db != NULL) { dns_db_detach(&db); } UNLOCK_ZONE(zone); if (result != DNS_R_CONTINUE) { goto fail; } return; fail: dump_done(zone, result); } /* * Save the raw serial number for inline-signing zones. * (XXX: Other information from the header will be used * for other purposes in the future, but for now this is * all we're interested in.) */ static void zone_setrawdata(dns_zone_t *zone, dns_masterrawheader_t *header) { if ((header->flags & DNS_MASTERRAW_SOURCESERIALSET) == 0) { return; } zone->sourceserial = header->sourceserial; zone->sourceserialset = true; } void dns_zone_setrawdata(dns_zone_t *zone, dns_masterrawheader_t *header) { if (zone == NULL) { return; } LOCK_ZONE(zone); zone_setrawdata(zone, header); UNLOCK_ZONE(zone); } static isc_result_t zone_startload(dns_db_t *db, dns_zone_t *zone, isc_time_t loadtime) { const char me[] = "zone_startload"; dns_load_t *load; isc_result_t result; isc_result_t tresult; unsigned int options; ENTER; dns_zone_rpz_enable_db(zone, db); dns_zone_catz_enable_db(zone, db); options = get_primary_options(zone); if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MANYERRORS)) { options |= DNS_MASTER_MANYERRORS; } if (zone->zmgr != NULL && zone->db != NULL && zone->loadtask != NULL) { load = isc_mem_get(zone->mctx, sizeof(*load)); load->mctx = NULL; load->zone = NULL; load->db = NULL; load->loadtime = loadtime; load->magic = LOAD_MAGIC; isc_mem_attach(zone->mctx, &load->mctx); zone_iattach(zone, &load->zone); dns_db_attach(db, &load->db); dns_rdatacallbacks_init(&load->callbacks); load->callbacks.rawdata = zone_setrawdata; zone_iattach(zone, &load->callbacks.zone); result = dns_db_beginload(db, &load->callbacks); if (result != ISC_R_SUCCESS) { goto cleanup; } result = zonemgr_getio(zone->zmgr, true, zone->loadtask, zone_gotreadhandle, load, &zone->readio); if (result != ISC_R_SUCCESS) { /* * We can't report multiple errors so ignore * the result of dns_db_endload(). */ (void)dns_db_endload(load->db, &load->callbacks); goto cleanup; } else { result = DNS_R_CONTINUE; } } else { dns_rdatacallbacks_t callbacks; dns_rdatacallbacks_init(&callbacks); callbacks.rawdata = zone_setrawdata; zone_iattach(zone, &callbacks.zone); result = dns_db_beginload(db, &callbacks); if (result != ISC_R_SUCCESS) { zone_idetach(&callbacks.zone); return (result); } if (zone->stream != NULL) { FILE *stream = NULL; DE_CONST(zone->stream, stream); result = dns_master_loadstream( stream, &zone->origin, &zone->origin, zone->rdclass, options, &callbacks, zone->mctx); } else { result = dns_master_loadfile( zone->masterfile, &zone->origin, &zone->origin, zone->rdclass, options, 0, &callbacks, zone_registerinclude, zone, zone->mctx, zone->masterformat, zone->maxttl); } tresult = dns_db_endload(db, &callbacks); if (result == ISC_R_SUCCESS) { result = tresult; } zone_idetach(&callbacks.zone); } return (result); cleanup: load->magic = 0; dns_db_detach(&load->db); zone_idetach(&load->zone); zone_idetach(&load->callbacks.zone); isc_mem_detach(&load->mctx); isc_mem_put(zone->mctx, load, sizeof(*load)); return (result); } static bool zone_check_mx(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, dns_name_t *owner) { isc_result_t result; char ownerbuf[DNS_NAME_FORMATSIZE]; char namebuf[DNS_NAME_FORMATSIZE]; char altbuf[DNS_NAME_FORMATSIZE]; dns_fixedname_t fixed; dns_name_t *foundname; int level; /* * "." means the services does not exist. */ if (dns_name_equal(name, dns_rootname)) { return (true); } /* * Outside of zone. */ if (!dns_name_issubdomain(name, &zone->origin)) { if (zone->checkmx != NULL) { return ((zone->checkmx)(zone, name, owner)); } return (true); } if (zone->type == dns_zone_primary) { level = ISC_LOG_ERROR; } else { level = ISC_LOG_WARNING; } foundname = dns_fixedname_initname(&fixed); result = dns_db_find(db, name, NULL, dns_rdatatype_a, 0, 0, NULL, foundname, NULL, NULL); if (result == ISC_R_SUCCESS) { return (true); } if (result == DNS_R_NXRRSET) { result = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, 0, 0, NULL, foundname, NULL, NULL); if (result == ISC_R_SUCCESS) { return (true); } } dns_name_format(owner, ownerbuf, sizeof ownerbuf); dns_name_format(name, namebuf, sizeof namebuf); if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN || result == DNS_R_EMPTYNAME) { if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKMXFAIL)) { level = ISC_LOG_WARNING; } dns_zone_log(zone, level, "%s/MX '%s' has no address records (A or AAAA)", ownerbuf, namebuf); return ((level == ISC_LOG_WARNING) ? true : false); } if (result == DNS_R_CNAME) { if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNMXCNAME) || DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) { level = ISC_LOG_WARNING; } if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) { dns_zone_log(zone, level, "%s/MX '%s' is a CNAME (illegal)", ownerbuf, namebuf); } return ((level == ISC_LOG_WARNING) ? true : false); } if (result == DNS_R_DNAME) { if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNMXCNAME) || DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) { level = ISC_LOG_WARNING; } if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNOREMXCNAME)) { dns_name_format(foundname, altbuf, sizeof altbuf); dns_zone_log(zone, level, "%s/MX '%s' is below a DNAME" " '%s' (illegal)", ownerbuf, namebuf, altbuf); } return ((level == ISC_LOG_WARNING) ? true : false); } if (zone->checkmx != NULL && result == DNS_R_DELEGATION) { return ((zone->checkmx)(zone, name, owner)); } return (true); } static bool zone_check_srv(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, dns_name_t *owner) { isc_result_t result; char ownerbuf[DNS_NAME_FORMATSIZE]; char namebuf[DNS_NAME_FORMATSIZE]; char altbuf[DNS_NAME_FORMATSIZE]; dns_fixedname_t fixed; dns_name_t *foundname; int level; /* * "." means the services does not exist. */ if (dns_name_equal(name, dns_rootname)) { return (true); } /* * Outside of zone. */ if (!dns_name_issubdomain(name, &zone->origin)) { if (zone->checksrv != NULL) { return ((zone->checksrv)(zone, name, owner)); } return (true); } if (zone->type == dns_zone_primary) { level = ISC_LOG_ERROR; } else { level = ISC_LOG_WARNING; } foundname = dns_fixedname_initname(&fixed); result = dns_db_find(db, name, NULL, dns_rdatatype_a, 0, 0, NULL, foundname, NULL, NULL); if (result == ISC_R_SUCCESS) { return (true); } if (result == DNS_R_NXRRSET) { result = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, 0, 0, NULL, foundname, NULL, NULL); if (result == ISC_R_SUCCESS) { return (true); } } dns_name_format(owner, ownerbuf, sizeof ownerbuf); dns_name_format(name, namebuf, sizeof namebuf); if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN || result == DNS_R_EMPTYNAME) { dns_zone_log(zone, level, "%s/SRV '%s' has no address records (A or AAAA)", ownerbuf, namebuf); /* XXX950 make fatal for 9.5.0. */ return (true); } if (result == DNS_R_CNAME) { if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNSRVCNAME) || DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) { level = ISC_LOG_WARNING; } if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) { dns_zone_log(zone, level, "%s/SRV '%s' is a CNAME (illegal)", ownerbuf, namebuf); } return ((level == ISC_LOG_WARNING) ? true : false); } if (result == DNS_R_DNAME) { if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_WARNSRVCNAME) || DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) { level = ISC_LOG_WARNING; } if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IGNORESRVCNAME)) { dns_name_format(foundname, altbuf, sizeof altbuf); dns_zone_log(zone, level, "%s/SRV '%s' is below a " "DNAME '%s' (illegal)", ownerbuf, namebuf, altbuf); } return ((level == ISC_LOG_WARNING) ? true : false); } if (zone->checksrv != NULL && result == DNS_R_DELEGATION) { return ((zone->checksrv)(zone, name, owner)); } return (true); } static bool zone_check_glue(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, dns_name_t *owner) { bool answer = true; isc_result_t result, tresult; char ownerbuf[DNS_NAME_FORMATSIZE]; char namebuf[DNS_NAME_FORMATSIZE]; char altbuf[DNS_NAME_FORMATSIZE]; dns_fixedname_t fixed; dns_name_t *foundname; dns_rdataset_t a; dns_rdataset_t aaaa; int level; /* * Outside of zone. */ if (!dns_name_issubdomain(name, &zone->origin)) { if (zone->checkns != NULL) { return ((zone->checkns)(zone, name, owner, NULL, NULL)); } return (true); } if (zone->type == dns_zone_primary) { level = ISC_LOG_ERROR; } else { level = ISC_LOG_WARNING; } foundname = dns_fixedname_initname(&fixed); dns_rdataset_init(&a); dns_rdataset_init(&aaaa); /* * Perform a regular lookup to catch DNAME records then look * for glue. */ result = dns_db_find(db, name, NULL, dns_rdatatype_a, 0, 0, NULL, foundname, &a, NULL); switch (result) { case ISC_R_SUCCESS: case DNS_R_DNAME: case DNS_R_CNAME: break; default: if (dns_rdataset_isassociated(&a)) { dns_rdataset_disassociate(&a); } result = dns_db_find(db, name, NULL, dns_rdatatype_a, DNS_DBFIND_GLUEOK, 0, NULL, foundname, &a, NULL); } if (result == ISC_R_SUCCESS) { dns_rdataset_disassociate(&a); return (true); } else if (result == DNS_R_DELEGATION) { dns_rdataset_disassociate(&a); } if (result == DNS_R_NXRRSET || result == DNS_R_DELEGATION || result == DNS_R_GLUE) { tresult = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, DNS_DBFIND_GLUEOK, 0, NULL, foundname, &aaaa, NULL); if (tresult == ISC_R_SUCCESS) { if (dns_rdataset_isassociated(&a)) { dns_rdataset_disassociate(&a); } dns_rdataset_disassociate(&aaaa); return (true); } if (tresult == DNS_R_DELEGATION || tresult == DNS_R_DNAME) { dns_rdataset_disassociate(&aaaa); } if (result == DNS_R_GLUE || tresult == DNS_R_GLUE) { /* * Check glue against child zone. */ if (zone->checkns != NULL) { answer = (zone->checkns)(zone, name, owner, &a, &aaaa); } if (dns_rdataset_isassociated(&a)) { dns_rdataset_disassociate(&a); } if (dns_rdataset_isassociated(&aaaa)) { dns_rdataset_disassociate(&aaaa); } return (answer); } } dns_name_format(owner, ownerbuf, sizeof ownerbuf); dns_name_format(name, namebuf, sizeof namebuf); if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN || result == DNS_R_EMPTYNAME || result == DNS_R_DELEGATION) { const char *what; bool required = false; if (dns_name_issubdomain(name, owner)) { what = "REQUIRED GLUE "; required = true; } else if (result == DNS_R_DELEGATION) { what = "SIBLING GLUE "; } else { what = ""; } if (result != DNS_R_DELEGATION || required || DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKSIBLING)) { dns_zone_log(zone, level, "%s/NS '%s' has no %s" "address records (A or AAAA)", ownerbuf, namebuf, what); /* * Log missing address record. */ if (result == DNS_R_DELEGATION && zone->checkns != NULL) { (void)(zone->checkns)(zone, name, owner, &a, &aaaa); } /* XXX950 make fatal for 9.5.0. */ /* answer = false; */ } } else if (result == DNS_R_CNAME) { dns_zone_log(zone, level, "%s/NS '%s' is a CNAME (illegal)", ownerbuf, namebuf); /* XXX950 make fatal for 9.5.0. */ /* answer = false; */ } else if (result == DNS_R_DNAME) { dns_name_format(foundname, altbuf, sizeof altbuf); dns_zone_log(zone, level, "%s/NS '%s' is below a DNAME '%s' (illegal)", ownerbuf, namebuf, altbuf); /* XXX950 make fatal for 9.5.0. */ /* answer = false; */ } if (dns_rdataset_isassociated(&a)) { dns_rdataset_disassociate(&a); } if (dns_rdataset_isassociated(&aaaa)) { dns_rdataset_disassociate(&aaaa); } return (answer); } static bool zone_rrset_check_dup(dns_zone_t *zone, dns_name_t *owner, dns_rdataset_t *rdataset) { dns_rdataset_t tmprdataset; isc_result_t result; bool answer = true; bool format = true; int level = ISC_LOG_WARNING; char ownerbuf[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; unsigned int count1 = 0; if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKDUPRRFAIL)) { level = ISC_LOG_ERROR; } dns_rdataset_init(&tmprdataset); for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdata_t rdata1 = DNS_RDATA_INIT; unsigned int count2 = 0; count1++; dns_rdataset_current(rdataset, &rdata1); dns_rdataset_clone(rdataset, &tmprdataset); for (result = dns_rdataset_first(&tmprdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&tmprdataset)) { dns_rdata_t rdata2 = DNS_RDATA_INIT; count2++; if (count1 >= count2) { continue; } dns_rdataset_current(&tmprdataset, &rdata2); if (dns_rdata_casecompare(&rdata1, &rdata2) == 0) { if (format) { dns_name_format(owner, ownerbuf, sizeof ownerbuf); dns_rdatatype_format(rdata1.type, typebuf, sizeof(typebuf)); format = false; } dns_zone_log(zone, level, "%s/%s has " "semantically identical records", ownerbuf, typebuf); if (level == ISC_LOG_ERROR) { answer = false; } break; } } dns_rdataset_disassociate(&tmprdataset); if (!format) { break; } } return (answer); } static bool zone_check_dup(dns_zone_t *zone, dns_db_t *db) { dns_dbiterator_t *dbiterator = NULL; dns_dbnode_t *node = NULL; dns_fixedname_t fixed; dns_name_t *name; dns_rdataset_t rdataset; dns_rdatasetiter_t *rdsit = NULL; bool ok = true; isc_result_t result; name = dns_fixedname_initname(&fixed); dns_rdataset_init(&rdataset); result = dns_db_createiterator(db, 0, &dbiterator); if (result != ISC_R_SUCCESS) { return (true); } for (result = dns_dbiterator_first(dbiterator); result == ISC_R_SUCCESS; result = dns_dbiterator_next(dbiterator)) { result = dns_dbiterator_current(dbiterator, &node, name); if (result != ISC_R_SUCCESS) { continue; } result = dns_db_allrdatasets(db, node, NULL, 0, 0, &rdsit); if (result != ISC_R_SUCCESS) { continue; } for (result = dns_rdatasetiter_first(rdsit); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(rdsit)) { dns_rdatasetiter_current(rdsit, &rdataset); if (!zone_rrset_check_dup(zone, name, &rdataset)) { ok = false; } dns_rdataset_disassociate(&rdataset); } dns_rdatasetiter_destroy(&rdsit); dns_db_detachnode(db, &node); } if (node != NULL) { dns_db_detachnode(db, &node); } dns_dbiterator_destroy(&dbiterator); return (ok); } static bool isspf(const dns_rdata_t *rdata) { char buf[1024]; const unsigned char *data = rdata->data; unsigned int rdl = rdata->length, i = 0, tl, len; while (rdl > 0U) { len = tl = *data; ++data; --rdl; INSIST(tl <= rdl); if (len > sizeof(buf) - i - 1) { len = sizeof(buf) - i - 1; } memmove(buf + i, data, len); i += len; data += tl; rdl -= tl; } if (i < 6U) { return (false); } buf[i] = 0; if (strncmp(buf, "v=spf1", 6) == 0 && (buf[6] == 0 || buf[6] == ' ')) { return (true); } return (false); } static bool integrity_checks(dns_zone_t *zone, dns_db_t *db) { dns_dbiterator_t *dbiterator = NULL; dns_dbnode_t *node = NULL; dns_rdataset_t rdataset; dns_fixedname_t fixed; dns_fixedname_t fixedbottom; dns_rdata_mx_t mx; dns_rdata_ns_t ns; dns_rdata_in_srv_t srv; dns_rdata_t rdata; dns_name_t *name; dns_name_t *bottom; isc_result_t result; bool ok = true, have_spf, have_txt; name = dns_fixedname_initname(&fixed); bottom = dns_fixedname_initname(&fixedbottom); dns_rdataset_init(&rdataset); dns_rdata_init(&rdata); result = dns_db_createiterator(db, 0, &dbiterator); if (result != ISC_R_SUCCESS) { return (true); } result = dns_dbiterator_first(dbiterator); while (result == ISC_R_SUCCESS) { result = dns_dbiterator_current(dbiterator, &node, name); if (result != ISC_R_SUCCESS) { goto cleanup; } /* * Is this name visible in the zone? */ if (!dns_name_issubdomain(name, &zone->origin) || (dns_name_countlabels(bottom) > 0 && dns_name_issubdomain(name, bottom))) { goto next; } dns_dbiterator_pause(dbiterator); /* * Don't check the NS records at the origin. */ if (dns_name_equal(name, &zone->origin)) { goto checkfordname; } result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_ns, 0, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { goto checkfordname; } /* * Remember bottom of zone due to NS. */ dns_name_copy(name, bottom); result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &ns, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (!zone_check_glue(zone, db, &ns.name, name)) { ok = false; } dns_rdata_reset(&rdata); result = dns_rdataset_next(&rdataset); } dns_rdataset_disassociate(&rdataset); goto next; checkfordname: result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_dname, 0, 0, &rdataset, NULL); if (result == ISC_R_SUCCESS) { /* * Remember bottom of zone due to DNAME. */ dns_name_copy(name, bottom); dns_rdataset_disassociate(&rdataset); } result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_mx, 0, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { goto checksrv; } result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &mx, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (!zone_check_mx(zone, db, &mx.mx, name)) { ok = false; } dns_rdata_reset(&rdata); result = dns_rdataset_next(&rdataset); } dns_rdataset_disassociate(&rdataset); checksrv: if (zone->rdclass != dns_rdataclass_in) { goto next; } result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_srv, 0, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { goto checkspf; } result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &srv, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (!zone_check_srv(zone, db, &srv.target, name)) { ok = false; } dns_rdata_reset(&rdata); result = dns_rdataset_next(&rdataset); } dns_rdataset_disassociate(&rdataset); checkspf: /* * Check if there is a type SPF record without an * SPF-formatted type TXT record also being present. */ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKSPF)) { goto next; } if (zone->rdclass != dns_rdataclass_in) { goto next; } have_spf = have_txt = false; result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_spf, 0, 0, &rdataset, NULL); if (result == ISC_R_SUCCESS) { dns_rdataset_disassociate(&rdataset); have_spf = true; } result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_txt, 0, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { goto notxt; } result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { dns_rdataset_current(&rdataset, &rdata); have_txt = isspf(&rdata); dns_rdata_reset(&rdata); if (have_txt) { break; } result = dns_rdataset_next(&rdataset); } dns_rdataset_disassociate(&rdataset); notxt: if (have_spf && !have_txt) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(name, namebuf, sizeof(namebuf)); dns_zone_log(zone, ISC_LOG_WARNING, "'%s' found type " "SPF record but no SPF TXT record found, " "add matching type TXT record", namebuf); } next: dns_db_detachnode(db, &node); result = dns_dbiterator_next(dbiterator); } cleanup: if (node != NULL) { dns_db_detachnode(db, &node); } dns_dbiterator_destroy(&dbiterator); return (ok); } /* * OpenSSL verification of RSA keys with exponent 3 is known to be * broken prior OpenSSL 0.9.8c/0.9.7k. Look for such keys and warn * if they are in use. */ static void zone_check_dnskeys(dns_zone_t *zone, dns_db_t *db) { dns_dbnode_t *node = NULL; dns_dbversion_t *version = NULL; dns_rdata_dnskey_t dnskey; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t rdataset; isc_result_t result; result = dns_db_findnode(db, &zone->origin, false, &node); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_db_currentversion(db, &version); dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, dns_rdatatype_none, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { goto cleanup; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &dnskey, NULL); INSIST(result == ISC_R_SUCCESS); /* * RFC 3110, section 4: Performance Considerations: * * A public exponent of 3 minimizes the effort needed to verify * a signature. Use of 3 as the public exponent is weak for * confidentiality uses since, if the same data can be collected * encrypted under three different keys with an exponent of 3 * then, using the Chinese Remainder Theorem [NETSEC], the * original plain text can be easily recovered. If a key is * known to be used only for authentication, as is the case with * DNSSEC, then an exponent of 3 is acceptable. However other * applications in the future may wish to leverage DNS * distributed keys for applications that do require * confidentiality. For keys which might have such other uses, * a more conservative choice would be 65537 (F4, the fourth * fermat number). */ if (dnskey.datalen > 1 && dnskey.data[0] == 1 && dnskey.data[1] == 3 && (dnskey.algorithm == DNS_KEYALG_RSAMD5 || dnskey.algorithm == DNS_KEYALG_RSASHA1 || dnskey.algorithm == DNS_KEYALG_NSEC3RSASHA1 || dnskey.algorithm == DNS_KEYALG_RSASHA256 || dnskey.algorithm == DNS_KEYALG_RSASHA512)) { char algorithm[DNS_SECALG_FORMATSIZE]; isc_region_t r; dns_rdata_toregion(&rdata, &r); dns_secalg_format(dnskey.algorithm, algorithm, sizeof(algorithm)); dnssec_log(zone, ISC_LOG_WARNING, "weak %s (%u) key found (exponent=3, id=%u)", algorithm, dnskey.algorithm, dst_region_computeid(&r)); } dns_rdata_reset(&rdata); } dns_rdataset_disassociate(&rdataset); cleanup: if (node != NULL) { dns_db_detachnode(db, &node); } if (version != NULL) { dns_db_closeversion(db, &version, false); } } static void resume_signingwithkey(dns_zone_t *zone) { dns_dbnode_t *node = NULL; dns_dbversion_t *version = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t rdataset; isc_result_t result; dns_db_t *db = NULL; ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { goto cleanup; } result = dns_db_findnode(db, &zone->origin, false, &node); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_db_currentversion(db, &version); dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, version, zone->privatetype, dns_rdatatype_none, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto cleanup; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdataset_current(&rdataset, &rdata); if (rdata.length != 5 || rdata.data[0] == 0 || rdata.data[4] != 0) { dns_rdata_reset(&rdata); continue; } result = zone_signwithkey(zone, rdata.data[0], (rdata.data[1] << 8) | rdata.data[2], rdata.data[3]); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_signwithkey failed: %s", isc_result_totext(result)); } dns_rdata_reset(&rdata); } dns_rdataset_disassociate(&rdataset); cleanup: if (db != NULL) { if (node != NULL) { dns_db_detachnode(db, &node); } if (version != NULL) { dns_db_closeversion(db, &version, false); } dns_db_detach(&db); } } /* * Initiate adding/removing NSEC3 records belonging to the chain defined by the * supplied NSEC3PARAM RDATA. * * Zone must be locked by caller. */ static isc_result_t zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param) { dns_nsec3chain_t *nsec3chain, *current; dns_dbversion_t *version = NULL; bool nseconly = false, nsec3ok = false; isc_result_t result; isc_time_t now; unsigned int options = 0; char saltbuf[255 * 2 + 1]; char flags[sizeof("INITIAL|REMOVE|CREATE|NONSEC|OPTOUT")]; dns_db_t *db = NULL; ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { result = ISC_R_SUCCESS; goto cleanup; } /* * If this zone is not NSEC3-capable, attempting to remove any NSEC3 * chain from it is pointless as it would not be possible for the * latter to exist in the first place. */ dns_db_currentversion(db, &version); result = dns_nsec_nseconly(db, version, NULL, &nseconly); nsec3ok = (result == ISC_R_SUCCESS && !nseconly); dns_db_closeversion(db, &version, false); if (!nsec3ok && (nsec3param->flags & DNS_NSEC3FLAG_REMOVE) == 0) { result = ISC_R_SUCCESS; goto cleanup; } /* * Allocate and initialize structure preserving state of * adding/removing records belonging to this NSEC3 chain between * separate zone_nsec3chain() calls. */ nsec3chain = isc_mem_get(zone->mctx, sizeof *nsec3chain); nsec3chain->magic = 0; nsec3chain->done = false; nsec3chain->db = NULL; nsec3chain->dbiterator = NULL; nsec3chain->nsec3param.common.rdclass = nsec3param->common.rdclass; nsec3chain->nsec3param.common.rdtype = nsec3param->common.rdtype; nsec3chain->nsec3param.hash = nsec3param->hash; nsec3chain->nsec3param.iterations = nsec3param->iterations; nsec3chain->nsec3param.flags = nsec3param->flags; nsec3chain->nsec3param.salt_length = nsec3param->salt_length; memmove(nsec3chain->salt, nsec3param->salt, nsec3param->salt_length); nsec3chain->nsec3param.salt = nsec3chain->salt; nsec3chain->seen_nsec = false; nsec3chain->delete_nsec = false; nsec3chain->save_delete_nsec = false; /* * Log NSEC3 parameters defined by supplied NSEC3PARAM RDATA. */ if (nsec3param->flags == 0) { strlcpy(flags, "NONE", sizeof(flags)); } else { flags[0] = '\0'; if ((nsec3param->flags & DNS_NSEC3FLAG_REMOVE) != 0) { strlcat(flags, "REMOVE", sizeof(flags)); } if ((nsec3param->flags & DNS_NSEC3FLAG_INITIAL) != 0) { if (flags[0] == '\0') { strlcpy(flags, "INITIAL", sizeof(flags)); } else { strlcat(flags, "|INITIAL", sizeof(flags)); } } if ((nsec3param->flags & DNS_NSEC3FLAG_CREATE) != 0) { if (flags[0] == '\0') { strlcpy(flags, "CREATE", sizeof(flags)); } else { strlcat(flags, "|CREATE", sizeof(flags)); } } if ((nsec3param->flags & DNS_NSEC3FLAG_NONSEC) != 0) { if (flags[0] == '\0') { strlcpy(flags, "NONSEC", sizeof(flags)); } else { strlcat(flags, "|NONSEC", sizeof(flags)); } } if ((nsec3param->flags & DNS_NSEC3FLAG_OPTOUT) != 0) { if (flags[0] == '\0') { strlcpy(flags, "OPTOUT", sizeof(flags)); } else { strlcat(flags, "|OPTOUT", sizeof(flags)); } } } result = dns_nsec3param_salttotext(nsec3param, saltbuf, sizeof(saltbuf)); RUNTIME_CHECK(result == ISC_R_SUCCESS); dnssec_log(zone, ISC_LOG_INFO, "zone_addnsec3chain(%u,%s,%u,%s)", nsec3param->hash, flags, nsec3param->iterations, saltbuf); /* * If the NSEC3 chain defined by the supplied NSEC3PARAM RDATA is * currently being processed, interrupt its processing to avoid * simultaneously adding and removing records for the same NSEC3 chain. */ for (current = ISC_LIST_HEAD(zone->nsec3chain); current != NULL; current = ISC_LIST_NEXT(current, link)) { if ((current->db == db) && (current->nsec3param.hash == nsec3param->hash) && (current->nsec3param.iterations == nsec3param->iterations) && (current->nsec3param.salt_length == nsec3param->salt_length) && memcmp(current->nsec3param.salt, nsec3param->salt, nsec3param->salt_length) == 0) { current->done = true; } } /* * Attach zone database to the structure initialized above and create * an iterator for it with appropriate options in order to avoid * creating NSEC3 records for NSEC3 records. */ dns_db_attach(db, &nsec3chain->db); if ((nsec3chain->nsec3param.flags & DNS_NSEC3FLAG_CREATE) != 0) { options = DNS_DB_NONSEC3; } result = dns_db_createiterator(nsec3chain->db, options, &nsec3chain->dbiterator); if (result == ISC_R_SUCCESS) { result = dns_dbiterator_first(nsec3chain->dbiterator); } if (result == ISC_R_SUCCESS) { /* * Database iterator initialization succeeded. We are now * ready to kick off adding/removing records belonging to this * NSEC3 chain. Append the structure initialized above to the * "nsec3chain" list for the zone and set the appropriate zone * timer so that zone_nsec3chain() is called as soon as * possible. */ dns_dbiterator_pause(nsec3chain->dbiterator); ISC_LIST_INITANDAPPEND(zone->nsec3chain, nsec3chain, link); nsec3chain = NULL; if (isc_time_isepoch(&zone->nsec3chaintime)) { TIME_NOW(&now); zone->nsec3chaintime = now; if (zone->task != NULL) { zone_settimer(zone, &now); } } } if (nsec3chain != NULL) { if (nsec3chain->db != NULL) { dns_db_detach(&nsec3chain->db); } if (nsec3chain->dbiterator != NULL) { dns_dbiterator_destroy(&nsec3chain->dbiterator); } isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); } cleanup: if (db != NULL) { dns_db_detach(&db); } return (result); } /* * Find private-type records at the zone apex which signal that an NSEC3 chain * should be added or removed. For each such record, extract NSEC3PARAM RDATA * and pass it to zone_addnsec3chain(). * * Zone must be locked by caller. */ static void resume_addnsec3chain(dns_zone_t *zone) { dns_dbnode_t *node = NULL; dns_dbversion_t *version = NULL; dns_rdataset_t rdataset; isc_result_t result; dns_rdata_nsec3param_t nsec3param; bool nseconly = false, nsec3ok = false; dns_db_t *db = NULL; INSIST(LOCKED_ZONE(zone)); if (zone->privatetype == 0) { return; } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { goto cleanup; } result = dns_db_findnode(db, &zone->origin, false, &node); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_db_currentversion(db, &version); /* * In order to create NSEC3 chains we need the DNSKEY RRset at zone * apex to exist and contain no keys using NSEC-only algorithms. */ result = dns_nsec_nseconly(db, version, NULL, &nseconly); nsec3ok = (result == ISC_R_SUCCESS && !nseconly); /* * Get the RRset containing all private-type records at the zone apex. */ dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, version, zone->privatetype, dns_rdatatype_none, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto cleanup; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_t private = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &private); /* * Try extracting NSEC3PARAM RDATA from this private-type * record. Failure means this private-type record does not * represent an NSEC3PARAM record, so skip it. */ if (!dns_nsec3param_fromprivate(&private, &rdata, buf, sizeof(buf))) { continue; } result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) || ((nsec3param.flags & DNS_NSEC3FLAG_CREATE) != 0 && nsec3ok)) { /* * Pass the NSEC3PARAM RDATA contained in this * private-type record to zone_addnsec3chain() so that * it can kick off adding or removing NSEC3 records. */ result = zone_addnsec3chain(zone, &nsec3param); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_addnsec3chain failed: %s", isc_result_totext(result)); } } } dns_rdataset_disassociate(&rdataset); cleanup: if (db != NULL) { if (node != NULL) { dns_db_detachnode(db, &node); } if (version != NULL) { dns_db_closeversion(db, &version, false); } dns_db_detach(&db); } } static void set_resigntime(dns_zone_t *zone) { dns_rdataset_t rdataset; dns_fixedname_t fixed; unsigned int resign; isc_result_t result; uint32_t nanosecs; dns_db_t *db = NULL; INSIST(LOCKED_ZONE(zone)); /* We only re-sign zones that can be dynamically updated */ if (!dns_zone_isdynamic(zone, false)) { return; } if (inline_raw(zone)) { return; } dns_rdataset_init(&rdataset); dns_fixedname_init(&fixed); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { isc_time_settoepoch(&zone->resigntime); return; } result = dns_db_getsigningtime(db, &rdataset, dns_fixedname_name(&fixed)); if (result != ISC_R_SUCCESS) { isc_time_settoepoch(&zone->resigntime); goto cleanup; } resign = rdataset.resign - dns_zone_getsigresigninginterval(zone); dns_rdataset_disassociate(&rdataset); nanosecs = isc_random_uniform(1000000000); isc_time_set(&zone->resigntime, resign, nanosecs); cleanup: dns_db_detach(&db); return; } static isc_result_t check_nsec3param(dns_zone_t *zone, dns_db_t *db) { bool ok = false; dns_dbnode_t *node = NULL; dns_dbversion_t *version = NULL; dns_rdata_nsec3param_t nsec3param; dns_rdataset_t rdataset; isc_result_t result; bool dynamic = (zone->type == dns_zone_primary) ? dns_zone_isdynamic(zone, false) : false; dns_rdataset_init(&rdataset); result = dns_db_findnode(db, &zone->origin, false, &node); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "nsec3param lookup failure: %s", isc_result_totext(result)); return (result); } dns_db_currentversion(db, &version); result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3param, dns_rdatatype_none, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { INSIST(!dns_rdataset_isassociated(&rdataset)); result = ISC_R_SUCCESS; goto cleanup; } if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); dns_zone_log(zone, ISC_LOG_ERROR, "nsec3param lookup failure: %s", isc_result_totext(result)); goto cleanup; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); /* * For dynamic zones we must support every algorithm so we * can regenerate all the NSEC3 chains. * For non-dynamic zones we only need to find a supported * algorithm. */ if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NSEC3TESTZONE) && nsec3param.hash == DNS_NSEC3_UNKNOWNALG && !dynamic) { dns_zone_log(zone, ISC_LOG_WARNING, "nsec3 test \"unknown\" hash algorithm " "found: %u", nsec3param.hash); ok = true; } else if (!dns_nsec3_supportedhash(nsec3param.hash)) { if (dynamic) { dns_zone_log(zone, ISC_LOG_ERROR, "unsupported nsec3 hash algorithm" " in dynamic zone: %u", nsec3param.hash); result = DNS_R_BADZONE; /* Stop second error message. */ ok = true; break; } else { dns_zone_log(zone, ISC_LOG_WARNING, "unsupported nsec3 hash " "algorithm: %u", nsec3param.hash); } } else { ok = true; } /* * Warn if the zone has excessive NSEC3 iterations. */ if (nsec3param.iterations > dns_nsec3_maxiterations()) { dnssec_log(zone, ISC_LOG_WARNING, "excessive NSEC3PARAM iterations %u > %u", nsec3param.iterations, dns_nsec3_maxiterations()); } } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } if (!ok) { result = DNS_R_BADZONE; dns_zone_log(zone, ISC_LOG_ERROR, "no supported nsec3 hash algorithm"); } cleanup: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } dns_db_closeversion(db, &version, false); dns_db_detachnode(db, &node); return (result); } /* * Set the timer for refreshing the key zone to the soonest future time * of the set (current timer, keydata->refresh, keydata->addhd, * keydata->removehd). */ static void set_refreshkeytimer(dns_zone_t *zone, dns_rdata_keydata_t *key, isc_stdtime_t now, bool force) { const char me[] = "set_refreshkeytimer"; isc_stdtime_t then; isc_time_t timenow, timethen; char timebuf[80]; ENTER; then = key->refresh; if (force) { then = now; } if (key->addhd > now && key->addhd < then) { then = key->addhd; } if (key->removehd > now && key->removehd < then) { then = key->removehd; } TIME_NOW(&timenow); if (then > now) { DNS_ZONE_TIME_ADD(&timenow, then - now, &timethen); } else { timethen = timenow; } if (isc_time_compare(&zone->refreshkeytime, &timenow) < 0 || isc_time_compare(&timethen, &zone->refreshkeytime) < 0) { zone->refreshkeytime = timethen; } isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); dns_zone_log(zone, ISC_LOG_DEBUG(1), "next key refresh: %s", timebuf); zone_settimer(zone, &timenow); } /* * If keynode references a key or a DS rdataset, and if the key * zone does not contain a KEYDATA record for the corresponding name, * then create an empty KEYDATA and push it into the zone as a placeholder, * then schedule a key refresh immediately. This new KEYDATA record will be * updated during the refresh. * * If the key zone is changed, set '*changed' to true. */ static isc_result_t create_keydata(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, dns_keynode_t *keynode, dns_name_t *keyname, bool *changed) { const char me[] = "create_keydata"; isc_result_t result = ISC_R_SUCCESS; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_keydata_t kd; unsigned char rrdata[4096]; isc_buffer_t rrdatabuf; isc_stdtime_t now; REQUIRE(keynode != NULL); ENTER; isc_stdtime_get(&now); /* * If the keynode has no trust anchor set, we shouldn't be here. */ if (!dns_keynode_dsset(keynode, NULL)) { return (ISC_R_FAILURE); } memset(&kd, 0, sizeof(kd)); kd.common.rdclass = zone->rdclass; kd.common.rdtype = dns_rdatatype_keydata; ISC_LINK_INIT(&kd.common, link); isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata)); CHECK(dns_rdata_fromstruct(&rdata, zone->rdclass, dns_rdatatype_keydata, &kd, &rrdatabuf)); /* Add rdata to zone. */ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADD, keyname, 0, &rdata)); *changed = true; /* Refresh new keys from the zone apex as soon as possible. */ set_refreshkeytimer(zone, &kd, now, true); return (ISC_R_SUCCESS); failure: return (result); } /* * Remove from the key zone all the KEYDATA records found in rdataset. */ static isc_result_t delete_keydata(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, dns_name_t *name, dns_rdataset_t *rdataset) { dns_rdata_t rdata = DNS_RDATA_INIT; isc_result_t result, uresult; for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdata_reset(&rdata); dns_rdataset_current(rdataset, &rdata); uresult = update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, 0, &rdata); if (uresult != ISC_R_SUCCESS) { return (uresult); } } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } return (result); } /* * Compute the DNSSEC key ID for a DNSKEY record. */ static isc_result_t compute_tag(dns_name_t *name, dns_rdata_dnskey_t *dnskey, isc_mem_t *mctx, dns_keytag_t *tag) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; unsigned char data[4096]; isc_buffer_t buffer; dst_key_t *dstkey = NULL; isc_buffer_init(&buffer, data, sizeof(data)); dns_rdata_fromstruct(&rdata, dnskey->common.rdclass, dns_rdatatype_dnskey, dnskey, &buffer); result = dns_dnssec_keyfromrdata(name, &rdata, mctx, &dstkey); if (result == ISC_R_SUCCESS) { *tag = dst_key_id(dstkey); dst_key_free(&dstkey); } return (result); } /* * Synth-from-dnssec callbacks to add/delete names from namespace tree. */ static void sfd_add(const dns_name_t *name, void *arg) { if (arg != NULL) { dns_view_sfd_add(arg, name); } } static void sfd_del(const dns_name_t *name, void *arg) { if (arg != NULL) { dns_view_sfd_del(arg, name); } } /* * Add key to the security roots. */ static void trust_key(dns_zone_t *zone, dns_name_t *keyname, dns_rdata_dnskey_t *dnskey, bool initial) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; unsigned char data[4096], digest[ISC_MAX_MD_SIZE]; isc_buffer_t buffer; dns_keytable_t *sr = NULL; dns_rdata_ds_t ds; result = dns_view_getsecroots(zone->view, &sr); if (result != ISC_R_SUCCESS) { return; } /* Build DS record for key. */ isc_buffer_init(&buffer, data, sizeof(data)); dns_rdata_fromstruct(&rdata, dnskey->common.rdclass, dns_rdatatype_dnskey, dnskey, &buffer); CHECK(dns_ds_fromkeyrdata(keyname, &rdata, DNS_DSDIGEST_SHA256, digest, &ds)); CHECK(dns_keytable_add(sr, true, initial, keyname, &ds, sfd_add, zone->view)); dns_keytable_detach(&sr); failure: if (sr != NULL) { dns_keytable_detach(&sr); } return; } /* * Add a null key to the security roots for so that all queries * to the zone will fail. */ static void fail_secure(dns_zone_t *zone, dns_name_t *keyname) { isc_result_t result; dns_keytable_t *sr = NULL; result = dns_view_getsecroots(zone->view, &sr); if (result == ISC_R_SUCCESS) { dns_keytable_marksecure(sr, keyname); dns_keytable_detach(&sr); } } /* * Scan a set of KEYDATA records from the key zone. The ones that are * valid (i.e., the add holddown timer has expired) become trusted keys. */ static void load_secroots(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *rdataset) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_keydata_t keydata; dns_rdata_dnskey_t dnskey; int trusted = 0, revoked = 0, pending = 0; isc_stdtime_t now; dns_keytable_t *sr = NULL; isc_stdtime_get(&now); result = dns_view_getsecroots(zone->view, &sr); if (result == ISC_R_SUCCESS) { dns_keytable_delete(sr, name, sfd_del, zone->view); dns_keytable_detach(&sr); } /* Now insert all the accepted trust anchors from this keydata set. */ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdata_reset(&rdata); dns_rdataset_current(rdataset, &rdata); /* Convert rdata to keydata. */ result = dns_rdata_tostruct(&rdata, &keydata, NULL); if (result == ISC_R_UNEXPECTEDEND) { continue; } RUNTIME_CHECK(result == ISC_R_SUCCESS); /* Set the key refresh timer to force a fast refresh. */ set_refreshkeytimer(zone, &keydata, now, true); /* If the removal timer is nonzero, this key was revoked. */ if (keydata.removehd != 0) { revoked++; continue; } /* * If the add timer is still pending, this key is not * trusted yet. */ if (now < keydata.addhd) { pending++; continue; } /* Convert keydata to dnskey. */ dns_keydata_todnskey(&keydata, &dnskey, NULL); /* Add to keytables. */ trusted++; trust_key(zone, name, &dnskey, (keydata.addhd == 0)); } if (trusted == 0 && pending != 0) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(name, namebuf, sizeof namebuf); dnssec_log(zone, ISC_LOG_ERROR, "No valid trust anchors for '%s'!", namebuf); dnssec_log(zone, ISC_LOG_ERROR, "%d key(s) revoked, %d still pending", revoked, pending); dnssec_log(zone, ISC_LOG_ERROR, "All queries to '%s' will fail", namebuf); fail_secure(zone, name); } } static isc_result_t do_one_tuple(dns_difftuple_t **tuple, dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff) { dns_diff_t temp_diff; isc_result_t result; /* * Create a singleton diff. */ dns_diff_init(diff->mctx, &temp_diff); ISC_LIST_APPEND(temp_diff.tuples, *tuple, link); /* * Apply it to the database. */ result = dns_diff_apply(&temp_diff, db, ver); ISC_LIST_UNLINK(temp_diff.tuples, *tuple, link); if (result != ISC_R_SUCCESS) { dns_difftuple_free(tuple); return (result); } /* * Merge it into the current pending journal entry. */ dns_diff_appendminimal(diff, tuple); /* * Do not clear temp_diff. */ return (ISC_R_SUCCESS); } static isc_result_t update_one_rr(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) { dns_difftuple_t *tuple = NULL; isc_result_t result; result = dns_difftuple_create(diff->mctx, op, name, ttl, rdata, &tuple); if (result != ISC_R_SUCCESS) { return (result); } return (do_one_tuple(&tuple, db, ver, diff)); } static isc_result_t update_soa_serial(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, isc_mem_t *mctx, dns_updatemethod_t method) { dns_difftuple_t *deltuple = NULL; dns_difftuple_t *addtuple = NULL; uint32_t serial; isc_result_t result; dns_updatemethod_t used = dns_updatemethod_none; INSIST(method != dns_updatemethod_none); CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_DEL, &deltuple)); CHECK(dns_difftuple_copy(deltuple, &addtuple)); addtuple->op = DNS_DIFFOP_ADD; serial = dns_soa_getserial(&addtuple->rdata); serial = dns_update_soaserial(serial, method, &used); if (method != used) { dns_zone_log(zone, ISC_LOG_WARNING, "update_soa_serial:new serial would be lower than " "old serial, using increment method instead"); } dns_soa_setserial(serial, &addtuple->rdata); CHECK(do_one_tuple(&deltuple, db, ver, diff)); CHECK(do_one_tuple(&addtuple, db, ver, diff)); result = ISC_R_SUCCESS; failure: if (addtuple != NULL) { dns_difftuple_free(&addtuple); } if (deltuple != NULL) { dns_difftuple_free(&deltuple); } return (result); } /* * Write all transactions in 'diff' to the zone journal file. */ static isc_result_t zone_journal(dns_zone_t *zone, dns_diff_t *diff, uint32_t *sourceserial, const char *caller) { const char me[] = "zone_journal"; const char *journalfile; isc_result_t result = ISC_R_SUCCESS; dns_journal_t *journal = NULL; unsigned int mode = DNS_JOURNAL_CREATE | DNS_JOURNAL_WRITE; ENTER; journalfile = dns_zone_getjournal(zone); if (journalfile != NULL) { result = dns_journal_open(zone->mctx, journalfile, mode, &journal); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "%s:dns_journal_open -> %s", caller, isc_result_totext(result)); return (result); } if (sourceserial != NULL) { dns_journal_set_sourceserial(journal, *sourceserial); } result = dns_journal_write_transaction(journal, diff); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "%s:dns_journal_write_transaction -> %s", caller, isc_result_totext(result)); } dns_journal_destroy(&journal); } return (result); } /* * Create an SOA record for a newly-created zone */ static isc_result_t add_soa(dns_zone_t *zone, dns_db_t *db) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; unsigned char buf[DNS_SOA_BUFFERSIZE]; dns_dbversion_t *ver = NULL; dns_diff_t diff; dns_zone_log(zone, ISC_LOG_DEBUG(1), "creating SOA"); dns_diff_init(zone->mctx, &diff); result = dns_db_newversion(db, &ver); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "add_soa:dns_db_newversion -> %s", isc_result_totext(result)); goto failure; } /* Build SOA record */ result = dns_soa_buildrdata(&zone->origin, dns_rootname, zone->rdclass, 0, 0, 0, 0, 0, buf, &rdata); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "add_soa:dns_soa_buildrdata -> %s", isc_result_totext(result)); goto failure; } result = update_one_rr(db, ver, &diff, DNS_DIFFOP_ADD, &zone->origin, 0, &rdata); failure: dns_diff_clear(&diff); if (ver != NULL) { dns_db_closeversion(db, &ver, (result == ISC_R_SUCCESS)); } INSIST(ver == NULL); return (result); } struct addifmissing_arg { dns_db_t *db; dns_dbversion_t *ver; dns_diff_t *diff; dns_zone_t *zone; bool *changed; isc_result_t result; }; static void addifmissing(dns_keytable_t *keytable, dns_keynode_t *keynode, dns_name_t *keyname, void *arg) { dns_db_t *db = ((struct addifmissing_arg *)arg)->db; dns_dbversion_t *ver = ((struct addifmissing_arg *)arg)->ver; dns_diff_t *diff = ((struct addifmissing_arg *)arg)->diff; dns_zone_t *zone = ((struct addifmissing_arg *)arg)->zone; bool *changed = ((struct addifmissing_arg *)arg)->changed; isc_result_t result; dns_fixedname_t fname; UNUSED(keytable); if (((struct addifmissing_arg *)arg)->result != ISC_R_SUCCESS) { return; } if (!dns_keynode_managed(keynode)) { return; } /* * If the keynode has no trust anchor set, return. */ if (!dns_keynode_dsset(keynode, NULL)) { return; } /* * Check whether there's already a KEYDATA entry for this name; * if so, we don't need to add another. */ dns_fixedname_init(&fname); result = dns_db_find(db, keyname, ver, dns_rdatatype_keydata, DNS_DBFIND_NOWILD, 0, NULL, dns_fixedname_name(&fname), NULL, NULL); if (result == ISC_R_SUCCESS) { return; } /* * Create the keydata. */ result = create_keydata(zone, db, ver, diff, keynode, keyname, changed); if (result != ISC_R_SUCCESS && result != ISC_R_NOMORE) { ((struct addifmissing_arg *)arg)->result = result; } } /* * Synchronize the set of initializing keys found in managed-keys {} * statements with the set of trust anchors found in the managed-keys.bind * zone. If a domain is no longer named in managed-keys, delete all keys * from that domain from the key zone. If a domain is configured as an * initial-key in trust-anchors, but there are no references to it in the * key zone, load the key zone with the initializing key(s) for that * domain and schedule a key refresh. If a domain is configured as * an initial-ds in trust-anchors, fetch the DNSKEY RRset, load the key * zone with the matching key, and schedule a key refresh. */ static isc_result_t sync_keyzone(dns_zone_t *zone, dns_db_t *db) { isc_result_t result = ISC_R_SUCCESS; bool changed = false; bool commit = false; dns_keynode_t *keynode = NULL; dns_view_t *view = zone->view; dns_keytable_t *sr = NULL; dns_dbversion_t *ver = NULL; dns_diff_t diff; dns_rriterator_t rrit; struct addifmissing_arg arg; dns_zone_log(zone, ISC_LOG_DEBUG(1), "synchronizing trusted keys"); dns_diff_init(zone->mctx, &diff); CHECK(dns_view_getsecroots(view, &sr)); result = dns_db_newversion(db, &ver); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "sync_keyzone:dns_db_newversion -> %s", isc_result_totext(result)); goto failure; } /* * Walk the zone DB. If we find any keys whose names are no longer * in trust-anchors, or which have been changed from initial to static, * (meaning they are permanent and not RFC5011-maintained), delete * them from the zone. Otherwise call load_secroots(), which * loads keys into secroots as appropriate. */ dns_rriterator_init(&rrit, db, ver, 0); for (result = dns_rriterator_first(&rrit); result == ISC_R_SUCCESS; result = dns_rriterator_nextrrset(&rrit)) { dns_rdataset_t *rdataset = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_keydata_t keydata; isc_stdtime_t now; bool load = true; dns_name_t *rrname = NULL; uint32_t ttl; isc_stdtime_get(&now); dns_rriterator_current(&rrit, &rrname, &ttl, &rdataset, NULL); if (!dns_rdataset_isassociated(rdataset)) { dns_rriterator_destroy(&rrit); goto failure; } if (rdataset->type != dns_rdatatype_keydata) { continue; } /* * The managed-keys zone can contain a placeholder instead of * legitimate data, in which case we will not use it, and we * will try to refresh it. */ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { isc_result_t iresult; dns_rdata_reset(&rdata); dns_rdataset_current(rdataset, &rdata); iresult = dns_rdata_tostruct(&rdata, &keydata, NULL); /* Do we have a valid placeholder KEYDATA record? */ if (iresult == ISC_R_SUCCESS && keydata.flags == 0 && keydata.protocol == 0 && keydata.algorithm == 0) { set_refreshkeytimer(zone, &keydata, now, true); load = false; } } /* * Release db wrlock to prevent LOR reports against * dns_keytable_forall() call below. */ dns_rriterator_pause(&rrit); result = dns_keytable_find(sr, rrname, &keynode); if (result != ISC_R_SUCCESS || !dns_keynode_managed(keynode)) { CHECK(delete_keydata(db, ver, &diff, rrname, rdataset)); changed = true; } else if (load) { load_secroots(zone, rrname, rdataset); } if (keynode != NULL) { dns_keytable_detachkeynode(sr, &keynode); } } dns_rriterator_destroy(&rrit); /* * Walk secroots to find any initial keys that aren't in * the zone. If we find any, add them to the zone directly. * If any DS-style initial keys are found, refresh the key * zone so that they'll be looked up. */ arg.db = db; arg.ver = ver; arg.result = ISC_R_SUCCESS; arg.diff = &diff; arg.zone = zone; arg.changed = &changed; dns_keytable_forall(sr, addifmissing, &arg); result = arg.result; if (changed) { /* Write changes to journal file. */ CHECK(update_soa_serial(zone, db, ver, &diff, zone->mctx, zone->updatemethod)); CHECK(zone_journal(zone, &diff, NULL, "sync_keyzone")); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); zone_needdump(zone, 30); commit = true; } failure: if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "unable to synchronize managed keys: %s", isc_result_totext(result)); isc_time_settoepoch(&zone->refreshkeytime); } if (keynode != NULL) { dns_keytable_detachkeynode(sr, &keynode); } if (sr != NULL) { dns_keytable_detach(&sr); } if (ver != NULL) { dns_db_closeversion(db, &ver, commit); } dns_diff_clear(&diff); INSIST(ver == NULL); return (result); } isc_result_t dns_zone_synckeyzone(dns_zone_t *zone) { isc_result_t result; dns_db_t *db = NULL; if (zone->type != dns_zone_key) { return (DNS_R_BADZONE); } CHECK(dns_zone_getdb(zone, &db)); LOCK_ZONE(zone); result = sync_keyzone(zone, db); UNLOCK_ZONE(zone); failure: if (db != NULL) { dns_db_detach(&db); } return (result); } static void maybe_send_secure(dns_zone_t *zone) { isc_result_t result; /* * We've finished loading, or else failed to load, an inline-signing * 'secure' zone. We now need information about the status of the * 'raw' zone. If we failed to load, then we need it to send a * copy of its database; if we succeeded, we need it to send its * serial number so that we can sync with it. If it has not yet * loaded, we set a flag so that it will send the necessary * information when it has finished loading. */ if (zone->raw->db != NULL) { if (zone->db != NULL) { uint32_t serial; unsigned int soacount; result = zone_get_from_db( zone->raw, zone->raw->db, NULL, &soacount, NULL, &serial, NULL, NULL, NULL, NULL, NULL); if (result == ISC_R_SUCCESS && soacount > 0U) { zone_send_secureserial(zone->raw, serial); } } else { zone_send_securedb(zone->raw, zone->raw->db); } } else { DNS_ZONE_SETFLAG(zone->raw, DNS_ZONEFLG_SENDSECURE); } } static bool zone_unchanged(dns_db_t *db1, dns_db_t *db2, isc_mem_t *mctx) { isc_result_t result; bool answer = false; dns_diff_t diff; dns_diff_init(mctx, &diff); result = dns_db_diffx(&diff, db1, NULL, db2, NULL, NULL); if (result == ISC_R_SUCCESS && ISC_LIST_EMPTY(diff.tuples)) { answer = true; } dns_diff_clear(&diff); return (answer); } /* * The zone is presumed to be locked. * If this is a inline_raw zone the secure version is also locked. */ static isc_result_t zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, isc_result_t result) { unsigned int soacount = 0; unsigned int nscount = 0; unsigned int errors = 0; uint32_t serial, oldserial, refresh, retry, expire, minimum, soattl; isc_time_t now; bool needdump = false; bool fixjournal = false; bool hasinclude = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HASINCLUDE); bool noprimary = false; bool had_db = false; dns_include_t *inc; bool is_dynamic = false; INSIST(LOCKED_ZONE(zone)); if (inline_raw(zone)) { INSIST(LOCKED_ZONE(zone->secure)); } TIME_NOW(&now); /* * Initiate zone transfer? We may need a error code that * indicates that the "permanent" form does not exist. * XXX better error feedback to log. */ if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_stub || (zone->type == dns_zone_redirect && zone->primaries == NULL)) { if (result == ISC_R_FILENOTFOUND) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "no master file"); } else if (result != DNS_R_NOMASTERFILE) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "loading from master file %s " "failed: %s", zone->masterfile, isc_result_totext(result)); } } else if (zone->type == dns_zone_primary && inline_secure(zone) && result == ISC_R_FILENOTFOUND) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "no master file, requesting db"); maybe_send_secure(zone); } else { int level = ISC_LOG_ERROR; if (zone->type == dns_zone_key && result == ISC_R_FILENOTFOUND) { level = ISC_LOG_DEBUG(1); } dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, level, "loading from master file %s failed: %s", zone->masterfile, isc_result_totext(result)); noprimary = true; } if (zone->type != dns_zone_key) { goto cleanup; } } dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(2), "number of nodes in database: %u", dns_db_nodecount(db, dns_dbtree_main)); if (result == DNS_R_SEENINCLUDE) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HASINCLUDE); } else { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HASINCLUDE); } /* * If there's no master file for a key zone, then the zone is new: * create an SOA record. (We do this now, instead of later, so that * if there happens to be a journal file, we can roll forward from * a sane starting point.) */ if (noprimary && zone->type == dns_zone_key) { result = add_soa(zone, db); if (result != ISC_R_SUCCESS) { goto cleanup; } } /* * Apply update log, if any, on initial load. */ if (zone->journal != NULL && !DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOMERGE) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) { result = zone_journal_rollforward(zone, db, &needdump, &fixjournal); if (result != ISC_R_SUCCESS) { goto cleanup; } } /* * Obtain ns, soa and cname counts for top of zone. */ INSIST(db != NULL); result = zone_get_from_db(zone, db, &nscount, &soacount, &soattl, &serial, &refresh, &retry, &expire, &minimum, &errors); if (result != ISC_R_SUCCESS && zone->type != dns_zone_key) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "could not find NS and/or SOA records"); } /* * Process any queued NSEC3PARAM change requests. Only for dynamic * zones, an inline-signing zone will perform this action when * receiving the secure db (receive_secure_db). */ is_dynamic = dns_zone_isdynamic(zone, true); if (is_dynamic) { isc_event_t *setnsec3param_event; dns_zone_t *dummy; while (!ISC_LIST_EMPTY(zone->setnsec3param_queue)) { setnsec3param_event = ISC_LIST_HEAD(zone->setnsec3param_queue); ISC_LIST_UNLINK(zone->setnsec3param_queue, setnsec3param_event, ev_link); dummy = NULL; zone_iattach(zone, &dummy); isc_task_send(zone->task, &setnsec3param_event); } } /* * Check to make sure the journal is up to date, and remove the * journal file if it isn't, as we wouldn't be able to apply * updates otherwise. */ if (zone->journal != NULL && is_dynamic && !DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS)) { uint32_t jserial; dns_journal_t *journal = NULL; bool empty = false; result = dns_journal_open(zone->mctx, zone->journal, DNS_JOURNAL_READ, &journal); if (result == ISC_R_SUCCESS) { jserial = dns_journal_last_serial(journal); empty = dns_journal_empty(journal); dns_journal_destroy(&journal); } else { jserial = serial; result = ISC_R_SUCCESS; } if (jserial != serial) { if (!empty) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, "journal file is out of date: " "removing journal file"); } if (remove(zone->journal) < 0 && errno != ENOENT) { char strbuf[ISC_STRERRORSIZE]; strerror_r(errno, strbuf, sizeof(strbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, ISC_LOG_WARNING, "unable to remove journal " "'%s': '%s'", zone->journal, strbuf); } } } dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "loaded; checking validity"); /* * Primary / Secondary / Mirror / Stub zones require both NS and SOA * records at the top of the zone. */ switch (zone->type) { case dns_zone_dlz: case dns_zone_primary: case dns_zone_secondary: case dns_zone_mirror: case dns_zone_stub: case dns_zone_redirect: if (soacount != 1) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "has %d SOA records", soacount); result = DNS_R_BADZONE; } if (nscount == 0) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "has no NS records"); result = DNS_R_BADZONE; } if (result != ISC_R_SUCCESS) { goto cleanup; } if (zone->type == dns_zone_primary && errors != 0) { result = DNS_R_BADZONE; goto cleanup; } if (zone->type != dns_zone_stub && zone->type != dns_zone_redirect) { result = check_nsec3param(zone, db); if (result != ISC_R_SUCCESS) { goto cleanup; } } if (zone->type == dns_zone_primary && DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKINTEGRITY) && !integrity_checks(zone, db)) { result = DNS_R_BADZONE; goto cleanup; } if (zone->type == dns_zone_primary && DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKDUPRR) && !zone_check_dup(zone, db)) { result = DNS_R_BADZONE; goto cleanup; } if (zone->type == dns_zone_primary) { result = dns_zone_cdscheck(zone, db, NULL); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "CDS/CDNSKEY consistency checks " "failed"); goto cleanup; } } result = dns_zone_verifydb(zone, db, NULL); if (result != ISC_R_SUCCESS) { goto cleanup; } if (zone->db != NULL) { unsigned int oldsoacount; /* * This is checked in zone_replacedb() for * secondary zones as they don't reload from disk. */ result = zone_get_from_db( zone, zone->db, NULL, &oldsoacount, NULL, &oldserial, NULL, NULL, NULL, NULL, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); RUNTIME_CHECK(oldsoacount > 0U); if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS) && !isc_serial_gt(serial, oldserial)) { uint32_t serialmin, serialmax; INSIST(zone->type == dns_zone_primary); INSIST(zone->raw == NULL); if (serial == oldserial && zone_unchanged(zone->db, db, zone->mctx)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, "ixfr-from-differences: " "unchanged"); zone->loadtime = loadtime; goto done; } serialmin = (oldserial + 1) & 0xffffffffU; serialmax = (oldserial + 0x7fffffffU) & 0xffffffffU; dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "ixfr-from-differences: " "new serial (%u) out of range " "[%u - %u]", serial, serialmin, serialmax); result = DNS_R_BADZONE; goto cleanup; } else if (!isc_serial_ge(serial, oldserial)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "zone serial (%u/%u) has gone " "backwards", serial, oldserial); } else if (serial == oldserial && !hasinclude && strcmp(zone->db_argv[0], "_builtin") != 0) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "zone serial (%u) unchanged. " "zone may fail to transfer " "to secondaries.", serial); } } if (zone->type == dns_zone_primary && (zone->update_acl != NULL || zone->ssutable != NULL) && dns_zone_getsigresigninginterval(zone) < (3 * refresh) && dns_db_issecure(db)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_WARNING, "sig-re-signing-interval less than " "3 * refresh."); } zone->refresh = RANGE(refresh, zone->minrefresh, zone->maxrefresh); zone->retry = RANGE(retry, zone->minretry, zone->maxretry); zone->expire = RANGE(expire, zone->refresh + zone->retry, DNS_MAX_EXPIRE); zone->soattl = soattl; zone->minimum = minimum; DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS); if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_stub || (zone->type == dns_zone_redirect && zone->primaries != NULL)) { isc_time_t t; uint32_t delay; result = isc_file_getmodtime(zone->journal, &t); if (result != ISC_R_SUCCESS) { result = isc_file_getmodtime(zone->masterfile, &t); } if (result == ISC_R_SUCCESS) { DNS_ZONE_TIME_ADD(&t, zone->expire, &zone->expiretime); } else { DNS_ZONE_TIME_ADD(&now, zone->retry, &zone->expiretime); } delay = (zone->retry - isc_random_uniform((zone->retry * 3) / 4)); DNS_ZONE_TIME_ADD(&now, delay, &zone->refreshtime); if (isc_time_compare(&zone->refreshtime, &zone->expiretime) >= 0) { zone->refreshtime = now; } } break; case dns_zone_key: /* Nothing needs to be done now */ break; default: UNEXPECTED_ERROR("unexpected zone type %d", zone->type); result = ISC_R_UNEXPECTED; goto cleanup; } /* * Check for weak DNSKEY's. */ if (zone->type == dns_zone_primary) { zone_check_dnskeys(zone, db); } /* * Schedule DNSSEC key refresh. */ if (zone->type == dns_zone_primary && DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) { zone->refreshkeytime = now; } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); if (zone->db != NULL) { had_db = true; result = zone_replacedb(zone, db, false); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); if (result != ISC_R_SUCCESS) { goto cleanup; } } else { zone_attachdb(zone, db); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDSTARTUPNOTIFY); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SENDSECURE) && inline_raw(zone)) { if (zone->secure->db == NULL) { zone_send_securedb(zone, db); } else { zone_send_secureserial(zone, serial); } } } /* * Finished loading inline-signing zone; need to get status * from the raw side now. */ if (zone->type == dns_zone_primary && inline_secure(zone)) { maybe_send_secure(zone); } result = ISC_R_SUCCESS; if (fixjournal) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FIXJOURNAL); zone_journal_compact(zone, zone->db, 0); } if (needdump) { if (zone->type == dns_zone_key) { zone_needdump(zone, 30); } else { zone_needdump(zone, DNS_DUMP_DELAY); } } if (zone->task != NULL) { if (zone->type == dns_zone_primary) { set_resigntime(zone); resume_signingwithkey(zone); resume_addnsec3chain(zone); } is_dynamic = dns_zone_isdynamic(zone, false); if (zone->type == dns_zone_primary && !DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_NORESIGN) && is_dynamic && dns_db_issecure(db) && !inline_raw(zone)) { 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)); dnssec_log( zone, ISC_LOG_DEBUG(3), "next resign: %s/%s " "in %d seconds", namebuf, typebuf, next.resign - timenow - dns_zone_getsigresigninginterval( zone)); dns_rdataset_disassociate(&next); } else { dnssec_log(zone, ISC_LOG_WARNING, "signed dynamic zone has no " "resign event scheduled"); } } zone_settimer(zone, &now); } /* * Clear old include list. */ for (inc = ISC_LIST_HEAD(zone->includes); inc != NULL; inc = ISC_LIST_HEAD(zone->includes)) { ISC_LIST_UNLINK(zone->includes, inc, link); isc_mem_free(zone->mctx, inc->name); isc_mem_put(zone->mctx, inc, sizeof(*inc)); } zone->nincludes = 0; /* * Transfer new include list. */ for (inc = ISC_LIST_HEAD(zone->newincludes); inc != NULL; inc = ISC_LIST_HEAD(zone->newincludes)) { ISC_LIST_UNLINK(zone->newincludes, inc, link); ISC_LIST_APPEND(zone->includes, inc, link); zone->nincludes++; } if (!dns_db_ispersistent(db)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, "loaded serial %u%s", serial, dns_db_issecure(db) ? " (DNSSEC signed)" : ""); } if (!had_db && zone->type == dns_zone_mirror) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, "mirror zone is now in use"); } zone->loadtime = loadtime; goto done; cleanup: if (result != ISC_R_SUCCESS) { dns_zone_rpz_disable_db(zone, db); dns_zone_catz_disable_db(zone, db); } for (inc = ISC_LIST_HEAD(zone->newincludes); inc != NULL; inc = ISC_LIST_HEAD(zone->newincludes)) { ISC_LIST_UNLINK(zone->newincludes, inc, link); isc_mem_free(zone->mctx, inc->name); isc_mem_put(zone->mctx, inc, sizeof(*inc)); } if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_stub || zone->type == dns_zone_key || (zone->type == dns_zone_redirect && zone->primaries != NULL)) { if (result != ISC_R_NOMEMORY) { if (zone->journal != NULL) { zone_saveunique(zone, zone->journal, "jn-XXXXXXXX"); } if (zone->masterfile != NULL) { zone_saveunique(zone, zone->masterfile, "db-XXXXXXXX"); } } /* Mark the zone for immediate refresh. */ zone->refreshtime = now; if (zone->task != NULL) { zone_settimer(zone, &now); } result = ISC_R_SUCCESS; } else if (zone->type == dns_zone_primary || zone->type == dns_zone_redirect) { if (!(inline_secure(zone) && result == ISC_R_FILENOTFOUND)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "not loaded due to errors."); } else if (zone->type == dns_zone_primary) { result = ISC_R_SUCCESS; } } done: DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADPENDING); /* * If this is an inline-signed zone and we were called for the raw * zone, we need to clear DNS_ZONEFLG_LOADPENDING for the secure zone * as well, but only if this is a reload, not an initial zone load: in * the former case, zone_postload() will not be run for the secure * zone; in the latter case, it will be. Check which case we are * dealing with by consulting the DNS_ZONEFLG_LOADED flag for the * secure zone: if it is set, this must be a reload. */ if (inline_raw(zone) && DNS_ZONE_FLAG(zone->secure, DNS_ZONEFLG_LOADED)) { DNS_ZONE_CLRFLAG(zone->secure, DNS_ZONEFLG_LOADPENDING); /* * Re-start zone maintenance if it had been stalled * due to DNS_ZONEFLG_LOADPENDING being set when * zone_maintenance was called. */ if (zone->secure->task != NULL) { zone_settimer(zone->secure, &now); } } zone_debuglog(zone, "zone_postload", 99, "done"); return (result); } static bool exit_check(dns_zone_t *zone) { REQUIRE(LOCKED_ZONE(zone)); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SHUTDOWN) && isc_refcount_current(&zone->irefs) == 0) { /* * DNS_ZONEFLG_SHUTDOWN can only be set if erefs == 0. */ INSIST(isc_refcount_current(&zone->erefs) == 0); return (true); } return (false); } static bool zone_check_ns(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, bool logit) { isc_result_t result; char namebuf[DNS_NAME_FORMATSIZE]; char altbuf[DNS_NAME_FORMATSIZE]; dns_fixedname_t fixed; dns_name_t *foundname; int level; if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOCHECKNS)) { return (true); } if (zone->type == dns_zone_primary) { level = ISC_LOG_ERROR; } else { level = ISC_LOG_WARNING; } foundname = dns_fixedname_initname(&fixed); result = dns_db_find(db, name, version, dns_rdatatype_a, 0, 0, NULL, foundname, NULL, NULL); if (result == ISC_R_SUCCESS) { return (true); } if (result == DNS_R_NXRRSET) { result = dns_db_find(db, name, version, dns_rdatatype_aaaa, 0, 0, NULL, foundname, NULL, NULL); if (result == ISC_R_SUCCESS) { return (true); } } if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN || result == DNS_R_EMPTYNAME) { if (logit) { dns_name_format(name, namebuf, sizeof namebuf); dns_zone_log(zone, level, "NS '%s' has no address " "records (A or AAAA)", namebuf); } return (false); } if (result == DNS_R_CNAME) { if (logit) { dns_name_format(name, namebuf, sizeof namebuf); dns_zone_log(zone, level, "NS '%s' is a CNAME " "(illegal)", namebuf); } return (false); } if (result == DNS_R_DNAME) { if (logit) { dns_name_format(name, namebuf, sizeof namebuf); dns_name_format(foundname, altbuf, sizeof altbuf); dns_zone_log(zone, level, "NS '%s' is below a DNAME " "'%s' (illegal)", namebuf, altbuf); } return (false); } return (true); } static isc_result_t zone_count_ns_rr(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, unsigned int *nscount, unsigned int *errors, bool logit) { isc_result_t result; unsigned int count = 0; unsigned int ecount = 0; dns_rdataset_t rdataset; dns_rdata_t rdata; dns_rdata_ns_t ns; dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, version, dns_rdatatype_ns, dns_rdatatype_none, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto success; } if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto invalidate_rdataset; } result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { if (errors != NULL && zone->rdclass == dns_rdataclass_in && (zone->type == dns_zone_primary || zone->type == dns_zone_secondary || zone->type == dns_zone_mirror)) { dns_rdata_init(&rdata); dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &ns, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (dns_name_issubdomain(&ns.name, &zone->origin) && !zone_check_ns(zone, db, version, &ns.name, logit)) { ecount++; } } count++; result = dns_rdataset_next(&rdataset); } dns_rdataset_disassociate(&rdataset); success: if (nscount != NULL) { *nscount = count; } if (errors != NULL) { *errors = ecount; } result = ISC_R_SUCCESS; invalidate_rdataset: dns_rdataset_invalidate(&rdataset); return (result); } #define SET_IF_NOT_NULL(obj, val) \ if (obj != NULL) { \ *obj = val; \ } #define SET_SOA_VALUES(soattl_v, serial_v, refresh_v, retry_v, expire_v, \ minimum_v) \ { \ SET_IF_NOT_NULL(soattl, soattl_v); \ SET_IF_NOT_NULL(serial, serial_v); \ SET_IF_NOT_NULL(refresh, refresh_v); \ SET_IF_NOT_NULL(retry, retry_v); \ SET_IF_NOT_NULL(expire, expire_v); \ SET_IF_NOT_NULL(minimum, minimum_v); \ } #define CLR_SOA_VALUES() \ { \ SET_SOA_VALUES(0, 0, 0, 0, 0, 0); \ } static isc_result_t zone_load_soa_rr(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, unsigned int *soacount, uint32_t *soattl, uint32_t *serial, uint32_t *refresh, uint32_t *retry, uint32_t *expire, uint32_t *minimum) { isc_result_t result; unsigned int count = 0; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa, dns_rdatatype_none, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { INSIST(!dns_rdataset_isassociated(&rdataset)); result = ISC_R_SUCCESS; goto invalidate_rdataset; } if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto invalidate_rdataset; } result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { dns_rdata_init(&rdata); dns_rdataset_current(&rdataset, &rdata); count++; if (count == 1) { dns_rdata_soa_t soa; result = dns_rdata_tostruct(&rdata, &soa, NULL); SET_SOA_VALUES(rdataset.ttl, soa.serial, soa.refresh, soa.retry, soa.expire, soa.minimum); RUNTIME_CHECK(result == ISC_R_SUCCESS); } result = dns_rdataset_next(&rdataset); dns_rdata_reset(&rdata); } dns_rdataset_disassociate(&rdataset); result = ISC_R_SUCCESS; invalidate_rdataset: SET_IF_NOT_NULL(soacount, count); if (count == 0) { CLR_SOA_VALUES(); } dns_rdataset_invalidate(&rdataset); return (result); } /* * zone must be locked. */ static isc_result_t zone_get_from_db(dns_zone_t *zone, dns_db_t *db, unsigned int *nscount, unsigned int *soacount, uint32_t *soattl, uint32_t *serial, uint32_t *refresh, uint32_t *retry, uint32_t *expire, uint32_t *minimum, unsigned int *errors) { isc_result_t result; isc_result_t answer = ISC_R_SUCCESS; dns_dbversion_t *version = NULL; dns_dbnode_t *node; REQUIRE(db != NULL); REQUIRE(zone != NULL); dns_db_currentversion(db, &version); SET_IF_NOT_NULL(nscount, 0); SET_IF_NOT_NULL(soacount, 0); SET_IF_NOT_NULL(errors, 0); CLR_SOA_VALUES(); node = NULL; result = dns_db_findnode(db, &zone->origin, false, &node); if (result != ISC_R_SUCCESS) { answer = result; goto closeversion; } if (nscount != NULL || errors != NULL) { result = zone_count_ns_rr(zone, db, node, version, nscount, errors, true); if (result != ISC_R_SUCCESS) { answer = result; } } if (soacount != NULL || soattl != NULL || serial != NULL || refresh != NULL || retry != NULL || expire != NULL || minimum != NULL) { result = zone_load_soa_rr(db, node, version, soacount, soattl, serial, refresh, retry, expire, minimum); if (result != ISC_R_SUCCESS) { answer = result; } } dns_db_detachnode(db, &node); closeversion: dns_db_closeversion(db, &version, false); return (answer); } void dns_zone_attach(dns_zone_t *source, dns_zone_t **target) { REQUIRE(DNS_ZONE_VALID(source)); REQUIRE(target != NULL && *target == NULL); isc_refcount_increment(&source->erefs); *target = source; } void dns_zone_detach(dns_zone_t **zonep) { REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep)); dns_zone_t *zone = *zonep; *zonep = NULL; if (isc_refcount_decrement(&zone->erefs) == 1) { isc_event_t *ev = &zone->ctlevent; isc_refcount_destroy(&zone->erefs); /* * Stop things being restarted after we cancel them below. */ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_EXITING); dns_zone_log(zone, ISC_LOG_DEBUG(1), "final reference detached"); if (zone->task != NULL) { /* * This zone has a task; it can clean * itself up asynchronously. */ isc_task_send(zone->task, &ev); return; } /* * This zone is unmanaged; we're probably running in * named-checkzone or a unit test. There's no task, * so we need to free it immediately. * * Unmanaged zones must not have null views; we have no way * of detaching from the view here without causing deadlock * because this code is called with the view already * locked. */ INSIST(zone->view == NULL); zone_shutdown(zone->task, ev); ev = NULL; } } void dns_zone_iattach(dns_zone_t *source, dns_zone_t **target) { REQUIRE(DNS_ZONE_VALID(source)); LOCK_ZONE(source); zone_iattach(source, target); UNLOCK_ZONE(source); } static void zone_iattach(dns_zone_t *source, dns_zone_t **target) { REQUIRE(DNS_ZONE_VALID(source)); REQUIRE(LOCKED_ZONE(source)); REQUIRE(target != NULL && *target == NULL); INSIST(isc_refcount_increment0(&source->irefs) + isc_refcount_current(&source->erefs) > 0); *target = source; } static void zone_idetach(dns_zone_t **zonep) { dns_zone_t *zone; /* * 'zone' locked by caller. */ REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep)); REQUIRE(LOCKED_ZONE(*zonep)); zone = *zonep; *zonep = NULL; INSIST(isc_refcount_decrement(&zone->irefs) - 1 + isc_refcount_current(&zone->erefs) > 0); } void dns_zone_idetach(dns_zone_t **zonep) { dns_zone_t *zone; REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep)); zone = *zonep; *zonep = NULL; if (isc_refcount_decrement(&zone->irefs) == 1) { bool free_needed; LOCK_ZONE(zone); free_needed = exit_check(zone); UNLOCK_ZONE(zone); if (free_needed) { zone_free(zone); } } } isc_mem_t * dns_zone_getmctx(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->mctx); } dns_zonemgr_t * dns_zone_getmgr(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->zmgr); } void dns_zone_setkasp(dns_zone_t *zone, dns_kasp_t *kasp) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->kasp != NULL) { dns_kasp_detach(&zone->kasp); } if (kasp != NULL) { dns_kasp_attach(kasp, &zone->kasp); } UNLOCK_ZONE(zone); } dns_kasp_t * dns_zone_getkasp(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->kasp); } void dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option, bool value) { REQUIRE(DNS_ZONE_VALID(zone)); if (value) { DNS_ZONE_SETOPTION(zone, option); } else { DNS_ZONE_CLROPTION(zone, option); } } dns_zoneopt_t dns_zone_getoptions(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (atomic_load_relaxed(&zone->options)); } void dns_zone_setkeyopt(dns_zone_t *zone, unsigned int keyopt, bool value) { REQUIRE(DNS_ZONE_VALID(zone)); if (value) { DNS_ZONEKEY_SETOPTION(zone, keyopt); } else { DNS_ZONEKEY_CLROPTION(zone, keyopt); } } unsigned int dns_zone_getkeyopts(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (atomic_load_relaxed(&zone->keyopts)); } isc_result_t dns_zone_setxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *xfrsource) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->xfrsource4 = *xfrsource; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_sockaddr_t * dns_zone_getxfrsource4(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (&zone->xfrsource4); } isc_result_t dns_zone_setxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *xfrsource) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->xfrsource6 = *xfrsource; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_sockaddr_t * dns_zone_getxfrsource6(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (&zone->xfrsource6); } isc_result_t dns_zone_setaltxfrsource4(dns_zone_t *zone, const isc_sockaddr_t *altxfrsource) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->altxfrsource4 = *altxfrsource; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_sockaddr_t * dns_zone_getaltxfrsource4(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (&zone->altxfrsource4); } isc_result_t dns_zone_setaltxfrsource6(dns_zone_t *zone, const isc_sockaddr_t *altxfrsource) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->altxfrsource6 = *altxfrsource; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_sockaddr_t * dns_zone_getaltxfrsource6(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (&zone->altxfrsource6); } isc_result_t dns_zone_setparentalsrc4(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->parentalsrc4 = *parentalsrc; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_sockaddr_t * dns_zone_getparentalsrc4(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (&zone->parentalsrc4); } isc_result_t dns_zone_setparentalsrc6(dns_zone_t *zone, const isc_sockaddr_t *parentalsrc) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->parentalsrc6 = *parentalsrc; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_sockaddr_t * dns_zone_getparentalsrc6(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (&zone->parentalsrc6); } isc_result_t dns_zone_setnotifysrc4(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->notifysrc4 = *notifysrc; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_sockaddr_t * dns_zone_getnotifysrc4(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (&zone->notifysrc4); } isc_result_t dns_zone_setnotifysrc6(dns_zone_t *zone, const isc_sockaddr_t *notifysrc) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->notifysrc6 = *notifysrc; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_sockaddr_t * dns_zone_getnotifysrc6(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (&zone->notifysrc6); } static bool same_addrs(isc_sockaddr_t const *oldlist, isc_sockaddr_t const *newlist, uint32_t count) { unsigned int i; for (i = 0; i < count; i++) { if (!isc_sockaddr_equal(&oldlist[i], &newlist[i])) { return (false); } } return (true); } static bool same_names(dns_name_t *const *oldlist, dns_name_t *const *newlist, uint32_t count) { unsigned int i; if (oldlist == NULL && newlist == NULL) { return (true); } if (oldlist == NULL || newlist == NULL) { return (false); } for (i = 0; i < count; i++) { if (oldlist[i] == NULL && newlist[i] == NULL) { continue; } if (oldlist[i] == NULL || newlist[i] == NULL || !dns_name_equal(oldlist[i], newlist[i])) { return (false); } } return (true); } static void clear_serverslist(isc_sockaddr_t **addrsp, dns_name_t ***keynamesp, dns_name_t ***tlsnamesp, unsigned int *countp, isc_mem_t *mctx) { unsigned int count; isc_sockaddr_t *addrs; dns_name_t **keynames; dns_name_t **tlsnames; REQUIRE(countp != NULL); REQUIRE(addrsp != NULL); REQUIRE(keynamesp != NULL); REQUIRE(tlsnamesp != NULL); count = *countp; *countp = 0; addrs = *addrsp; *addrsp = NULL; keynames = *keynamesp; *keynamesp = NULL; tlsnames = *tlsnamesp; *tlsnamesp = NULL; if (addrs != NULL) { isc_mem_put(mctx, addrs, count * sizeof(isc_sockaddr_t)); } if (keynames != NULL) { unsigned int i; for (i = 0; i < count; i++) { if (keynames[i] != NULL) { dns_name_free(keynames[i], mctx); isc_mem_put(mctx, keynames[i], sizeof(dns_name_t)); keynames[i] = NULL; } } isc_mem_put(mctx, keynames, count * sizeof(dns_name_t *)); } if (tlsnames != NULL) { unsigned int i; for (i = 0; i < count; i++) { if (tlsnames[i] != NULL) { dns_name_free(tlsnames[i], mctx); isc_mem_put(mctx, tlsnames[i], sizeof(dns_name_t)); tlsnames[i] = NULL; } } isc_mem_put(mctx, tlsnames, count * sizeof(dns_name_t *)); } } static void set_serverslist(unsigned int count, const isc_sockaddr_t *addrs, isc_sockaddr_t **newaddrsp, dns_name_t **keynames, dns_name_t ***newkeynamesp, dns_name_t **tlsnames, dns_name_t ***newtlsnamesp, isc_mem_t *mctx) { isc_sockaddr_t *newaddrs = NULL; dns_name_t **newkeynames = NULL; dns_name_t **newtlsnames = NULL; unsigned int i; REQUIRE(newaddrsp != NULL && *newaddrsp == NULL); REQUIRE(newkeynamesp != NULL && *newkeynamesp == NULL); REQUIRE(newtlsnamesp != NULL && *newtlsnamesp == NULL); newaddrs = isc_mem_get(mctx, count * sizeof(*newaddrs)); memmove(newaddrs, addrs, count * sizeof(*newaddrs)); if (keynames != NULL) { newkeynames = isc_mem_get(mctx, count * sizeof(*newkeynames)); for (i = 0; i < count; i++) { newkeynames[i] = NULL; } for (i = 0; i < count; i++) { if (keynames[i] != NULL) { newkeynames[i] = isc_mem_get(mctx, sizeof(dns_name_t)); dns_name_init(newkeynames[i], NULL); dns_name_dup(keynames[i], mctx, newkeynames[i]); } } } if (tlsnames != NULL) { newtlsnames = isc_mem_get(mctx, count * sizeof(*newtlsnames)); for (i = 0; i < count; i++) { newtlsnames[i] = NULL; } for (i = 0; i < count; i++) { if (tlsnames[i] != NULL) { newtlsnames[i] = isc_mem_get(mctx, sizeof(dns_name_t)); dns_name_init(newtlsnames[i], NULL); dns_name_dup(tlsnames[i], mctx, newtlsnames[i]); } } } *newaddrsp = newaddrs; *newkeynamesp = newkeynames; *newtlsnamesp = newtlsnames; } void dns_zone_setalsonotify(dns_zone_t *zone, const isc_sockaddr_t *notify, dns_name_t **keynames, dns_name_t **tlsnames, uint32_t count) { isc_sockaddr_t *newaddrs = NULL; dns_name_t **newkeynames = NULL; dns_name_t **newtlsnames = NULL; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(count == 0 || notify != NULL); if (keynames != NULL) { REQUIRE(count != 0); } LOCK_ZONE(zone); if (count == zone->notifycnt && same_addrs(zone->notify, notify, count) && same_names(zone->notifykeynames, keynames, count) && same_names(zone->notifytlsnames, tlsnames, count)) { goto unlock; } clear_serverslist(&zone->notify, &zone->notifykeynames, &zone->notifytlsnames, &zone->notifycnt, zone->mctx); if (count == 0) { goto unlock; } /* * Set up the notify and notifykey lists */ set_serverslist(count, notify, &newaddrs, keynames, &newkeynames, tlsnames, &newtlsnames, zone->mctx); /* * Everything is ok so attach to the zone. */ zone->notify = newaddrs; zone->notifykeynames = newkeynames; zone->notifytlsnames = newtlsnames; zone->notifycnt = count; unlock: UNLOCK_ZONE(zone); } void dns_zone_setprimaries(dns_zone_t *zone, const isc_sockaddr_t *primaries, dns_name_t **keynames, dns_name_t **tlsnames, uint32_t count) { isc_sockaddr_t *newaddrs = NULL; dns_name_t **newkeynames = NULL; dns_name_t **newtlsnames = NULL; bool *newok; unsigned int i; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(count == 0 || primaries != NULL); if (keynames != NULL || tlsnames != NULL) { REQUIRE(count != 0); } LOCK_ZONE(zone); /* * The refresh code assumes that 'primaries' wouldn't change under it. * If it will change then kill off any current refresh in progress * and update the primaries info. If it won't change then we can just * unlock and exit. */ if (count != zone->primariescnt || !same_addrs(zone->primaries, primaries, count) || !same_names(zone->primarykeynames, keynames, count) || !same_names(zone->primarytlsnames, tlsnames, count)) { if (zone->request != NULL) { dns_request_cancel(zone->request); } } else { goto unlock; } /* * This needs to happen before clear_addresskeylist() sets * zone->primariescnt to 0: */ if (zone->primariesok != NULL) { isc_mem_put(zone->mctx, zone->primariesok, zone->primariescnt * sizeof(bool)); zone->primariesok = NULL; } clear_serverslist(&zone->primaries, &zone->primarykeynames, &zone->primarytlsnames, &zone->primariescnt, zone->mctx); /* * If count == 0, don't allocate any space for primaries, primariesok or * keynames so internally, those pointers are NULL if count == 0 */ if (count == 0) { goto unlock; } /* * primariesok must contain count elements */ newok = isc_mem_get(zone->mctx, count * sizeof(*newok)); for (i = 0; i < count; i++) { newok[i] = false; } /* * Now set up the primaries and primary key lists */ set_serverslist(count, primaries, &newaddrs, keynames, &newkeynames, tlsnames, &newtlsnames, zone->mctx); /* * Everything is ok so attach to the zone. */ zone->curprimary = 0; zone->primariesok = newok; zone->primaries = newaddrs; zone->primarykeynames = newkeynames; zone->primarytlsnames = newtlsnames; zone->primariescnt = count; DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOPRIMARIES); unlock: UNLOCK_ZONE(zone); } void dns_zone_setparentals(dns_zone_t *zone, const isc_sockaddr_t *parentals, dns_name_t **keynames, dns_name_t **tlsnames, uint32_t count) { isc_sockaddr_t *newaddrs = NULL; dns_name_t **newkeynames = NULL; dns_name_t **newtlsnames = NULL; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(count == 0 || parentals != NULL); if (keynames != NULL || tlsnames != NULL) { REQUIRE(count != 0); } LOCK_ZONE(zone); clear_serverslist(&zone->parentals, &zone->parentalkeynames, &zone->parentaltlsnames, &zone->parentalscnt, zone->mctx); /* * If count == 0, don't allocate any space for parentals, or keynames * so internally, those pointers are NULL if count == 0 */ if (count == 0) { goto unlock; } /* * Now set up the parentals and parental key lists */ set_serverslist(count, parentals, &newaddrs, keynames, &newkeynames, tlsnames, &newtlsnames, zone->mctx); /* * Everything is ok so attach to the zone. */ zone->parentals = newaddrs; zone->parentalkeynames = newkeynames; zone->parentaltlsnames = newtlsnames; zone->parentalscnt = count; dns_zone_log(zone, ISC_LOG_NOTICE, "checkds: set %u parentals", count); unlock: UNLOCK_ZONE(zone); } isc_result_t dns_zone_getdb(dns_zone_t *zone, dns_db_t **dpb) { isc_result_t result = ISC_R_SUCCESS; REQUIRE(DNS_ZONE_VALID(zone)); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db == NULL) { result = DNS_R_NOTLOADED; } else { dns_db_attach(zone->db, dpb); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); return (result); } void dns_zone_setdb(dns_zone_t *zone, dns_db_t *db) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(zone->type == dns_zone_staticstub); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); REQUIRE(zone->db == NULL); dns_db_attach(db, &zone->db); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); } /* * Coordinates the starting of routine jobs. */ void dns_zone_maintenance(dns_zone_t *zone) { const char me[] = "dns_zone_maintenance"; isc_time_t now; REQUIRE(DNS_ZONE_VALID(zone)); ENTER; LOCK_ZONE(zone); TIME_NOW(&now); zone_settimer(zone, &now); UNLOCK_ZONE(zone); } static bool was_dumping(dns_zone_t *zone) { REQUIRE(LOCKED_ZONE(zone)); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { return (true); } DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP); isc_time_settoepoch(&zone->dumptime); return (false); } /*% * Find up to 'maxkeys' DNSSEC keys used for signing version 'ver' of database * 'db' for zone 'zone' in its key directory, then load these keys into 'keys'. * Only load the public part of a given key if it is not active at timestamp * 'now'. Store the number of keys found in 'nkeys'. */ isc_result_t dns__zone_findkeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, isc_mem_t *mctx, unsigned int maxkeys, dst_key_t **keys, unsigned int *nkeys) { isc_result_t result; dns_dbnode_t *node = NULL; const char *directory = dns_zone_getkeydirectory(zone); CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node)); memset(keys, 0, sizeof(*keys) * maxkeys); dns_zone_lock_keyfiles(zone); result = dns_dnssec_findzonekeys(db, ver, node, dns_db_origin(db), directory, now, mctx, maxkeys, keys, nkeys); dns_zone_unlock_keyfiles(zone); if (result == ISC_R_NOTFOUND) { result = ISC_R_SUCCESS; } failure: if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } /*% * Find DNSSEC keys used for signing zone with dnssec-policy. Load these keys * into 'keys'. Requires KASP to be locked. */ isc_result_t dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, dns_dnsseckeylist_t *keys) { isc_result_t result; const char *dir = dns_zone_getkeydirectory(zone); dns_dbnode_t *node = NULL; dns_dnsseckey_t *key, *key_next; dns_dnsseckeylist_t dnskeys; dns_name_t *origin = dns_zone_getorigin(zone); dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_rdataset_t keyset; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(kasp != NULL); ISC_LIST_INIT(dnskeys); dns_rdataset_init(&keyset); CHECK(dns_db_findnode(db, origin, false, &node)); /* Get keys from private key files. */ dns_zone_lock_keyfiles(zone); result = dns_dnssec_findmatchingkeys(origin, dir, now, dns_zone_getmctx(zone), keys); dns_zone_unlock_keyfiles(zone); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { goto failure; } /* Get public keys (dnskeys). */ dns_rdataset_init(&keyset); result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, dns_rdatatype_none, 0, &keyset, NULL); if (result == ISC_R_SUCCESS) { CHECK(dns_dnssec_keylistfromrdataset( origin, dir, dns_zone_getmctx(zone), &keyset, NULL, NULL, false, false, &dnskeys)); } else if (result != ISC_R_NOTFOUND) { CHECK(result); } /* Add new 'dnskeys' to 'keys'. */ for (dns_dnsseckey_t *k1 = ISC_LIST_HEAD(dnskeys); k1 != NULL; k1 = key_next) { dns_dnsseckey_t *k2 = NULL; key_next = ISC_LIST_NEXT(k1, link); for (k2 = ISC_LIST_HEAD(*keys); k2 != NULL; k2 = ISC_LIST_NEXT(k2, link)) { if (dst_key_compare(k1->key, k2->key)) { break; } } /* No match found, add the new key. */ if (k2 == NULL) { ISC_LIST_UNLINK(dnskeys, k1, link); ISC_LIST_APPEND(*keys, k1, link); } } failure: if (dns_rdataset_isassociated(&keyset)) { dns_rdataset_disassociate(&keyset); } if (node != NULL) { dns_db_detachnode(db, &node); } while (!ISC_LIST_EMPTY(dnskeys)) { key = ISC_LIST_HEAD(dnskeys); ISC_LIST_UNLINK(dnskeys, key, link); dns_dnsseckey_destroy(dns_zone_getmctx(zone), &key); } return (result); } static isc_result_t offline(dns_db_t *db, dns_dbversion_t *ver, dns__zonediff_t *zonediff, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) { isc_result_t result; if ((rdata->flags & DNS_RDATA_OFFLINE) != 0) { return (ISC_R_SUCCESS); } result = update_one_rr(db, ver, zonediff->diff, DNS_DIFFOP_DELRESIGN, name, ttl, rdata); if (result != ISC_R_SUCCESS) { return (result); } rdata->flags |= DNS_RDATA_OFFLINE; result = update_one_rr(db, ver, zonediff->diff, DNS_DIFFOP_ADDRESIGN, name, ttl, rdata); zonediff->offline = true; return (result); } static void set_key_expiry_warning(dns_zone_t *zone, isc_stdtime_t when, isc_stdtime_t now) { unsigned int delta; char timebuf[80]; LOCK_ZONE(zone); zone->key_expiry = when; if (when <= now) { dns_zone_log(zone, ISC_LOG_ERROR, "DNSKEY RRSIG(s) have expired"); isc_time_settoepoch(&zone->keywarntime); } else if (when < now + 7 * 24 * 3600) { isc_time_t t; isc_time_set(&t, when, 0); isc_time_formattimestamp(&t, timebuf, 80); dns_zone_log(zone, ISC_LOG_WARNING, "DNSKEY RRSIG(s) will expire within 7 days: %s", timebuf); delta = when - now; delta--; /* loop prevention */ delta /= 24 * 3600; /* to whole days */ delta *= 24 * 3600; /* to seconds */ isc_time_set(&zone->keywarntime, when - delta, 0); } else { isc_time_set(&zone->keywarntime, when - 7 * 24 * 3600, 0); isc_time_formattimestamp(&zone->keywarntime, timebuf, 80); dns_zone_log(zone, ISC_LOG_NOTICE, "setting keywarntime to %s", timebuf); } UNLOCK_ZONE(zone); } /* * Helper function to del_sigs(). We don't want to delete RRSIGs that * have no new key. */ static bool delsig_ok(dns_rdata_rrsig_t *rrsig_ptr, dst_key_t **keys, unsigned int nkeys, bool kasp, bool *warn) { unsigned int i = 0; isc_result_t ret; bool have_ksk = false, have_zsk = false; bool have_pksk = false, have_pzsk = false; for (i = 0; i < nkeys; i++) { bool ksk, zsk; if (have_pksk && have_ksk && have_pzsk && have_zsk) { break; } if (rrsig_ptr->algorithm != dst_key_alg(keys[i])) { continue; } ret = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); if (ret != ISC_R_SUCCESS) { ksk = KSK(keys[i]); } ret = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); if (ret != ISC_R_SUCCESS) { zsk = !KSK(keys[i]); } if (ksk) { have_ksk = true; if (dst_key_isprivate(keys[i])) { have_pksk = true; } } if (zsk) { have_zsk = true; if (dst_key_isprivate(keys[i])) { have_pzsk = true; } } } if (have_zsk && have_ksk && !have_pzsk) { *warn = true; } if (have_pksk && have_pzsk) { return (true); } /* * Deleting the SOA RRSIG is always okay. */ if (rrsig_ptr->covered == dns_rdatatype_soa) { return (true); } /* * It's okay to delete a signature if there is an active key with the * same algorithm to replace it, unless that violates the DNSSEC * policy. */ if (have_pksk || have_pzsk) { if (kasp && have_pzsk) { return (true); } return (!kasp); } /* * Failing that, it is *not* okay to delete a signature * if the associated public key is still in the DNSKEY RRset */ for (i = 0; i < nkeys; i++) { if ((rrsig_ptr->algorithm == dst_key_alg(keys[i])) && (rrsig_ptr->keyid == dst_key_id(keys[i]))) { return (false); } } /* * But if the key is gone, then go ahead. */ return (true); } /* * Delete expired RRsigs and any RRsigs we are about to re-sign. * See also update.c:del_keysigs(). */ static isc_result_t del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, dns_rdatatype_t type, dns__zonediff_t *zonediff, dst_key_t **keys, unsigned int nkeys, isc_stdtime_t now, bool incremental) { isc_result_t result; dns_dbnode_t *node = NULL; dns_rdataset_t rdataset; unsigned int i; dns_rdata_rrsig_t rrsig; bool kasp = (dns_zone_getkasp(zone) != NULL); bool found; int64_t timewarn = 0, timemaybe = 0; dns_rdataset_init(&rdataset); if (type == dns_rdatatype_nsec3) { result = dns_db_findnsec3node(db, name, false, &node); } else { result = dns_db_findnode(db, name, false, &node); } if (result == ISC_R_NOTFOUND) { return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { goto failure; } result = dns_db_findrdataset(db, node, ver, dns_rdatatype_rrsig, type, (isc_stdtime_t)0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result == ISC_R_NOTFOUND) { INSIST(!dns_rdataset_isassociated(&rdataset)); return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto failure; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (type != dns_rdatatype_dnskey && type != dns_rdatatype_cds && type != dns_rdatatype_cdnskey) { bool warn = false, deleted = false; if (delsig_ok(&rrsig, keys, nkeys, kasp, &warn)) { result = update_one_rr(db, ver, zonediff->diff, DNS_DIFFOP_DELRESIGN, name, rdataset.ttl, &rdata); if (result != ISC_R_SUCCESS) { break; } deleted = true; } if (warn && !deleted) { /* * At this point, we've got an RRSIG, * which is signed by an inactive key. * An administrator needs to provide a new * key/alg, but until that time, we want to * keep the old RRSIG. Marking the key as * offline will prevent us spinning waiting * for the private part. */ if (incremental) { result = offline(db, ver, zonediff, name, rdataset.ttl, &rdata); if (result != ISC_R_SUCCESS) { break; } } /* * Log the key id and algorithm of * the inactive key with no replacement */ if (zone->log_key_expired_timer <= now) { char origin[DNS_NAME_FORMATSIZE]; char algbuf[DNS_NAME_FORMATSIZE]; dns_name_format(&zone->origin, origin, sizeof(origin)); dns_secalg_format(rrsig.algorithm, algbuf, sizeof(algbuf)); dns_zone_log(zone, ISC_LOG_WARNING, "Key %s/%s/%d " "missing or inactive " "and has no replacement: " "retaining signatures.", origin, algbuf, rrsig.keyid); zone->log_key_expired_timer = now + 3600; } } continue; } /* * KSK RRSIGs requires special processing. */ found = false; for (i = 0; i < nkeys; i++) { if (rrsig.algorithm == dst_key_alg(keys[i]) && rrsig.keyid == dst_key_id(keys[i])) { found = true; /* * Mark offline DNSKEY. * We want the earliest offline expire time * iff there is a new offline signature. */ if (!dst_key_inactive(keys[i]) && !dst_key_isprivate(keys[i])) { int64_t timeexpire = dns_time64_from32( rrsig.timeexpire); if (timewarn != 0 && timewarn > timeexpire) { timewarn = timeexpire; } if (rdata.flags & DNS_RDATA_OFFLINE) { if (timemaybe == 0 || timemaybe > timeexpire) { timemaybe = timeexpire; } break; } if (timewarn == 0) { timewarn = timemaybe; } if (timewarn == 0 || timewarn > timeexpire) { timewarn = timeexpire; } result = offline(db, ver, zonediff, name, rdataset.ttl, &rdata); break; } result = update_one_rr(db, ver, zonediff->diff, DNS_DIFFOP_DELRESIGN, name, rdataset.ttl, &rdata); break; } } /* * If there is not a matching DNSKEY then * delete the RRSIG. */ if (!found) { result = update_one_rr(db, ver, zonediff->diff, DNS_DIFFOP_DELRESIGN, name, rdataset.ttl, &rdata); } if (result != ISC_R_SUCCESS) { break; } } dns_rdataset_disassociate(&rdataset); if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } if (timewarn > 0) { isc_stdtime_t stdwarn = (isc_stdtime_t)timewarn; if (timewarn == stdwarn) { set_key_expiry_warning(zone, (isc_stdtime_t)timewarn, now); } else { dns_zone_log(zone, ISC_LOG_ERROR, "key expiry warning time out of range"); } } failure: if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } static isc_result_t add_sigs(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, dns_zone_t *zone, dns_rdatatype_t type, dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys, isc_mem_t *mctx, isc_stdtime_t now, isc_stdtime_t inception, isc_stdtime_t expire, bool check_ksk, bool keyset_kskonly) { isc_result_t result; dns_dbnode_t *node = NULL; dns_stats_t *dnssecsignstats; dns_rdataset_t rdataset; dns_rdata_t sig_rdata = DNS_RDATA_INIT; unsigned char data[1024]; /* XXX */ isc_buffer_t buffer; unsigned int i, j; bool use_kasp = false; if (dns_zone_getkasp(zone) != NULL) { check_ksk = false; keyset_kskonly = true; use_kasp = true; } dns_rdataset_init(&rdataset); isc_buffer_init(&buffer, data, sizeof(data)); if (type == dns_rdatatype_nsec3) { result = dns_db_findnsec3node(db, name, false, &node); } else { result = dns_db_findnode(db, name, false, &node); } if (result == ISC_R_NOTFOUND) { return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { goto failure; } result = dns_db_findrdataset(db, node, ver, type, 0, (isc_stdtime_t)0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result == ISC_R_NOTFOUND) { INSIST(!dns_rdataset_isassociated(&rdataset)); return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto failure; } for (i = 0; i < nkeys; i++) { bool both = false; /* Don't add signatures for offline or inactive keys */ if (!dst_key_isprivate(keys[i])) { continue; } if (dst_key_inactive(keys[i])) { continue; } if (check_ksk && !REVOKE(keys[i])) { bool have_ksk, have_nonksk; if (KSK(keys[i])) { have_ksk = true; have_nonksk = false; } else { have_ksk = false; have_nonksk = true; } for (j = 0; j < nkeys; j++) { if (j == i || ALG(keys[i]) != ALG(keys[j])) { continue; } /* * Don't consider inactive keys, however * the KSK may be temporary offline, so do * consider keys which private key files are * unavailable. */ if (dst_key_inactive(keys[j])) { continue; } if (REVOKE(keys[j])) { continue; } if (KSK(keys[j])) { have_ksk = true; } else if (dst_key_isprivate(keys[j])) { have_nonksk = true; } both = have_ksk && have_nonksk; if (both) { break; } } } if (use_kasp) { /* * A dnssec-policy is found. Check what RRsets this * key should sign. */ isc_result_t kresult; isc_stdtime_t when; bool ksk = false; bool zsk = false; bool have_ksk = false; bool have_zsk = false; kresult = dst_key_getbool(keys[i], DST_BOOL_KSK, &ksk); if (kresult != ISC_R_SUCCESS) { if (KSK(keys[i])) { ksk = true; } } kresult = dst_key_getbool(keys[i], DST_BOOL_ZSK, &zsk); if (kresult != ISC_R_SUCCESS) { if (!KSK(keys[i])) { zsk = true; } } have_ksk = ksk; have_zsk = zsk; both = have_ksk && have_zsk; for (j = 0; j < nkeys; j++) { if (both) { break; } if (j == i || ALG(keys[i]) != ALG(keys[j])) { continue; } /* * Don't consider inactive keys or offline keys. */ if (!dst_key_isprivate(keys[j])) { continue; } if (dst_key_inactive(keys[j])) { continue; } if (REVOKE(keys[j])) { continue; } if (!have_ksk) { kresult = dst_key_getbool(keys[j], DST_BOOL_KSK, &have_ksk); if (kresult != ISC_R_SUCCESS) { if (KSK(keys[j])) { have_ksk = true; } } } if (!have_zsk) { kresult = dst_key_getbool(keys[j], DST_BOOL_ZSK, &have_zsk); if (kresult != ISC_R_SUCCESS) { if (!KSK(keys[j])) { have_zsk = true; } } } both = have_ksk && have_zsk; } if (type == dns_rdatatype_dnskey || type == dns_rdatatype_cdnskey || type == dns_rdatatype_cds) { /* * DNSKEY RRset is signed with KSK. * CDS and CDNSKEY RRsets too (RFC 7344, 4.1). */ if (!ksk) { continue; } } else if (!zsk) { /* * Other RRsets are signed with ZSK. */ if (type != dns_rdatatype_soa && type != zone->privatetype) { continue; } if (have_zsk) { continue; } } else if (!dst_key_is_signing(keys[i], DST_BOOL_ZSK, now, &when)) { /* * This key is not active for zone-signing. */ continue; } /* * If this key is revoked, it may only sign the * DNSKEY RRset. */ if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { continue; } } else if (both) { /* * CDS and CDNSKEY are signed with KSK (RFC 7344, 4.1). */ if (type == dns_rdatatype_dnskey || type == dns_rdatatype_cdnskey || type == dns_rdatatype_cds) { if (!KSK(keys[i]) && keyset_kskonly) { continue; } } else if (KSK(keys[i])) { continue; } } else if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey) { continue; } /* Calculate the signature, creating a RRSIG RDATA. */ isc_buffer_clear(&buffer); CHECK(dns_dnssec_sign(name, &rdataset, keys[i], &inception, &expire, mctx, &buffer, &sig_rdata)); /* Update the database and journal with the RRSIG. */ /* XXX inefficient - will cause dataset merging */ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADDRESIGN, name, rdataset.ttl, &sig_rdata)); dns_rdata_reset(&sig_rdata); isc_buffer_init(&buffer, data, sizeof(data)); /* Update DNSSEC sign statistics. */ dnssecsignstats = dns_zone_getdnssecsignstats(zone); if (dnssecsignstats != NULL) { /* Generated a new signature. */ dns_dnssecsignstats_increment(dnssecsignstats, ID(keys[i]), (uint8_t)ALG(keys[i]), dns_dnssecsignstats_sign); /* This is a refresh. */ dns_dnssecsignstats_increment( dnssecsignstats, ID(keys[i]), (uint8_t)ALG(keys[i]), dns_dnssecsignstats_refresh); } } failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } static void zone_resigninc(dns_zone_t *zone) { const char *me = "zone_resigninc"; dns_db_t *db = NULL; dns_dbversion_t *version = NULL; dns_diff_t _sig_diff; dns__zonediff_t zonediff; dns_fixedname_t fixed; dns_name_t *name; dns_rdataset_t rdataset; dns_rdatatype_t covers; dst_key_t *zone_keys[DNS_MAXZONEKEYS]; bool check_ksk, keyset_kskonly = false; isc_result_t result; isc_stdtime_t now, inception, soaexpire, expire, fullexpire, stop; uint32_t sigvalidityinterval, expiryinterval; unsigned int i; unsigned int nkeys = 0; unsigned int resign; ENTER; dns_rdataset_init(&rdataset); dns_diff_init(zone->mctx, &_sig_diff); zonediff_init(&zonediff, &_sig_diff); /* * Zone is frozen or automatic resigning is disabled. * Pause for 5 minutes. */ if (zone->update_disabled || DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_NORESIGN)) { result = ISC_R_FAILURE; goto failure; } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { result = ISC_R_FAILURE; goto failure; } result = dns_db_newversion(db, &version); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_resigninc:dns_db_newversion -> %s", isc_result_totext(result)); goto failure; } isc_stdtime_get(&now); result = dns__zone_findkeys(zone, db, version, now, zone->mctx, DNS_MAXZONEKEYS, zone_keys, &nkeys); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_resigninc:dns__zone_findkeys -> %s", isc_result_totext(result)); goto failure; } sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); inception = now - 3600; /* Allow for clock skew. */ soaexpire = now + sigvalidityinterval; expiryinterval = dns_zone_getsigresigninginterval(zone); if (expiryinterval > sigvalidityinterval) { expiryinterval = sigvalidityinterval; } else { expiryinterval = sigvalidityinterval - expiryinterval; } /* * Spread out signatures over time if they happen to be * clumped. We don't do this for each add_sigs() call as * we still want some clustering to occur. In normal operations * the records should be re-signed as they fall due and they should * already be spread out. However if the server is off for a * period we need to ensure that the clusters don't become * synchronised by using the full jitter range. */ if (sigvalidityinterval >= 3600U) { uint32_t normaljitter, fulljitter; if (sigvalidityinterval > 7200U) { normaljitter = isc_random_uniform(3600); fulljitter = isc_random_uniform(expiryinterval); } else { normaljitter = fulljitter = isc_random_uniform(1200); } expire = soaexpire - normaljitter - 1; fullexpire = soaexpire - fulljitter - 1; } else { expire = fullexpire = soaexpire - 1; } stop = now + 5; check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); name = dns_fixedname_initname(&fixed); result = dns_db_getsigningtime(db, &rdataset, name); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_resigninc:dns_db_getsigningtime -> %s", isc_result_totext(result)); } i = 0; while (result == ISC_R_SUCCESS) { resign = rdataset.resign - dns_zone_getsigresigninginterval(zone); covers = rdataset.covers; dns_rdataset_disassociate(&rdataset); /* * Stop if we hit the SOA as that means we have walked the * entire zone. The SOA record should always be the most * recent signature. */ /* XXXMPA increase number of RRsets signed pre call */ if ((covers == dns_rdatatype_soa && dns_name_equal(name, &zone->origin)) || i++ > zone->signatures || resign > stop) { break; } result = del_sigs(zone, db, version, name, covers, &zonediff, zone_keys, nkeys, now, true); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_resigninc:del_sigs -> %s", isc_result_totext(result)); break; } /* * If re-signing is over 5 minutes late use 'fullexpire' * to redistribute the signature over the complete * re-signing window, otherwise only add a small amount * of jitter. */ result = add_sigs(db, version, name, zone, covers, zonediff.diff, zone_keys, nkeys, zone->mctx, now, inception, resign > (now - 300) ? expire : fullexpire, check_ksk, keyset_kskonly); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_resigninc:add_sigs -> %s", isc_result_totext(result)); break; } result = dns_db_getsigningtime(db, &rdataset, name); if (nkeys == 0 && result == ISC_R_NOTFOUND) { result = ISC_R_SUCCESS; break; } if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_resigninc:dns_db_getsigningtime -> " "%s", isc_result_totext(result)); } } if (result != ISC_R_NOMORE && result != ISC_R_SUCCESS) { goto failure; } result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa, &zonediff, zone_keys, nkeys, now, true); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_resigninc:del_sigs -> %s", isc_result_totext(result)); goto failure; } /* * Did we change anything in the zone? */ if (ISC_LIST_EMPTY(zonediff.diff->tuples)) { /* * Commit the changes if any key has been marked as offline. */ if (zonediff.offline) { dns_db_closeversion(db, &version, true); } goto failure; } /* Increment SOA serial if we have made changes */ result = update_soa_serial(zone, db, version, zonediff.diff, zone->mctx, zone->updatemethod); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_resigninc:update_soa_serial -> %s", isc_result_totext(result)); goto failure; } /* * Generate maximum life time signatures so that the above loop * termination is sensible. */ result = add_sigs(db, version, &zone->origin, zone, dns_rdatatype_soa, zonediff.diff, zone_keys, nkeys, zone->mctx, now, inception, soaexpire, check_ksk, keyset_kskonly); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_resigninc:add_sigs -> %s", isc_result_totext(result)); goto failure; } /* Write changes to journal file. */ CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_resigninc")); /* Everything has succeeded. Commit the changes. */ dns_db_closeversion(db, &version, true); failure: dns_diff_clear(&_sig_diff); for (i = 0; i < nkeys; i++) { dst_key_free(&zone_keys[i]); } if (version != NULL) { dns_db_closeversion(db, &version, false); dns_db_detach(&db); } else if (db != NULL) { dns_db_detach(&db); } LOCK_ZONE(zone); if (result == ISC_R_SUCCESS) { set_resigntime(zone); zone_needdump(zone, DNS_DUMP_DELAY); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); } else { /* * Something failed. Retry in 5 minutes. */ isc_interval_t ival; isc_interval_set(&ival, 300, 0); isc_time_nowplusinterval(&zone->resigntime, &ival); } UNLOCK_ZONE(zone); INSIST(version == NULL); } static isc_result_t next_active(dns_db_t *db, dns_dbversion_t *version, dns_name_t *oldname, dns_name_t *newname, bool bottom) { isc_result_t result; dns_dbiterator_t *dbit = NULL; dns_rdatasetiter_t *rdsit = NULL; dns_dbnode_t *node = NULL; CHECK(dns_db_createiterator(db, DNS_DB_NONSEC3, &dbit)); CHECK(dns_dbiterator_seek(dbit, oldname)); do { result = dns_dbiterator_next(dbit); if (result == ISC_R_NOMORE) { CHECK(dns_dbiterator_first(dbit)); } CHECK(dns_dbiterator_current(dbit, &node, newname)); if (bottom && dns_name_issubdomain(newname, oldname) && !dns_name_equal(newname, oldname)) { dns_db_detachnode(db, &node); continue; } /* * Is this node empty? */ CHECK(dns_db_allrdatasets(db, node, version, 0, 0, &rdsit)); result = dns_rdatasetiter_first(rdsit); dns_db_detachnode(db, &node); dns_rdatasetiter_destroy(&rdsit); if (result != ISC_R_NOMORE) { break; } } while (1); failure: if (node != NULL) { dns_db_detachnode(db, &node); } if (dbit != NULL) { dns_dbiterator_destroy(&dbit); } return (result); } static bool signed_with_good_key(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, dns_rdatatype_t type, dst_key_t *key) { isc_result_t result; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_rrsig_t rrsig; int count = 0; dns_kasp_t *kasp = dns_zone_getkasp(zone); dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, version, dns_rdatatype_rrsig, type, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); return (false); } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); INSIST(result == ISC_R_SUCCESS); if (rrsig.algorithm == dst_key_alg(key) && rrsig.keyid == dst_key_id(key)) { dns_rdataset_disassociate(&rdataset); return (true); } if (rrsig.algorithm == dst_key_alg(key)) { count++; } dns_rdata_reset(&rdata); } if (dns_zone_getkasp(zone) != NULL) { dns_kasp_key_t *kkey; int zsk_count = 0; bool approved; KASP_LOCK(kasp); for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) { if (dns_kasp_key_algorithm(kkey) != dst_key_alg(key)) { continue; } if (dns_kasp_key_zsk(kkey)) { zsk_count++; } } KASP_UNLOCK(kasp); if (type == dns_rdatatype_dnskey || type == dns_rdatatype_cdnskey || type == dns_rdatatype_cds) { /* * CDS and CDNSKEY are signed with KSK like DNSKEY. * (RFC 7344, section 4.1 specifies that they must * be signed with a key in the current DS RRset, * which would only include KSK's.) */ approved = false; } else { approved = (zsk_count == count); } dns_rdataset_disassociate(&rdataset); return (approved); } dns_rdataset_disassociate(&rdataset); return (false); } static isc_result_t add_nsec(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, dns_dbnode_t *node, dns_ttl_t ttl, bool bottom, dns_diff_t *diff) { dns_fixedname_t fixed; dns_name_t *next; dns_rdata_t rdata = DNS_RDATA_INIT; isc_result_t result; unsigned char nsecbuffer[DNS_NSEC_BUFFERSIZE]; next = dns_fixedname_initname(&fixed); CHECK(next_active(db, version, name, next, bottom)); CHECK(dns_nsec_buildrdata(db, version, node, next, nsecbuffer, &rdata)); CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_ADD, name, ttl, &rdata)); failure: return (result); } static isc_result_t check_if_bottom_of_zone(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, bool *is_bottom_of_zone) { isc_result_t result; dns_rdatasetiter_t *iterator = NULL; dns_rdataset_t rdataset; bool seen_soa = false, seen_ns = false, seen_dname = false; REQUIRE(is_bottom_of_zone != NULL); result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator); if (result != ISC_R_SUCCESS) { if (result == ISC_R_NOTFOUND) { result = ISC_R_SUCCESS; } return (result); } dns_rdataset_init(&rdataset); for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(iterator)) { dns_rdatasetiter_current(iterator, &rdataset); switch (rdataset.type) { case dns_rdatatype_soa: seen_soa = true; break; case dns_rdatatype_ns: seen_ns = true; break; case dns_rdatatype_dname: seen_dname = true; break; } dns_rdataset_disassociate(&rdataset); } if (result != ISC_R_NOMORE) { goto failure; } if ((seen_ns && !seen_soa) || seen_dname) { *is_bottom_of_zone = true; } result = ISC_R_SUCCESS; failure: dns_rdatasetiter_destroy(&iterator); return (result); } static isc_result_t sign_a_node(dns_db_t *db, dns_zone_t *zone, dns_name_t *name, dns_dbnode_t *node, dns_dbversion_t *version, bool build_nsec3, bool build_nsec, dst_key_t *key, isc_stdtime_t now, isc_stdtime_t inception, isc_stdtime_t expire, dns_ttl_t nsecttl, bool is_ksk, bool is_zsk, bool keyset_kskonly, bool is_bottom_of_zone, dns_diff_t *diff, int32_t *signatures, isc_mem_t *mctx) { isc_result_t result; dns_rdatasetiter_t *iterator = NULL; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; dns_stats_t *dnssecsignstats; isc_buffer_t buffer; unsigned char data[1024]; bool seen_soa, seen_ns, seen_rr, seen_nsec, seen_nsec3, seen_ds; result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator); if (result != ISC_R_SUCCESS) { if (result == ISC_R_NOTFOUND) { result = ISC_R_SUCCESS; } return (result); } dns_rdataset_init(&rdataset); isc_buffer_init(&buffer, data, sizeof(data)); seen_rr = seen_soa = seen_ns = seen_nsec = seen_nsec3 = seen_ds = false; for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(iterator)) { dns_rdatasetiter_current(iterator, &rdataset); if (rdataset.type == dns_rdatatype_soa) { seen_soa = true; } else if (rdataset.type == dns_rdatatype_ns) { seen_ns = true; } else if (rdataset.type == dns_rdatatype_ds) { seen_ds = true; } else if (rdataset.type == dns_rdatatype_nsec) { seen_nsec = true; } else if (rdataset.type == dns_rdatatype_nsec3) { seen_nsec3 = true; } if (rdataset.type != dns_rdatatype_rrsig) { seen_rr = true; } dns_rdataset_disassociate(&rdataset); } if (result != ISC_R_NOMORE) { goto failure; } /* * Going from insecure to NSEC3. * Don't generate NSEC3 records for NSEC3 records. */ if (build_nsec3 && !seen_nsec3 && seen_rr) { bool unsecure = !seen_ds && seen_ns && !seen_soa; CHECK(dns_nsec3_addnsec3s(db, version, name, nsecttl, unsecure, diff)); (*signatures)--; } /* * Going from insecure to NSEC. * Don't generate NSEC records for NSEC3 records. */ if (build_nsec && !seen_nsec3 && !seen_nsec && seen_rr) { /* * Build a NSEC record except at the origin. */ if (!dns_name_equal(name, dns_db_origin(db))) { CHECK(add_nsec(db, version, name, node, nsecttl, is_bottom_of_zone, diff)); /* Count a NSEC generation as a signature generation. */ (*signatures)--; } } result = dns_rdatasetiter_first(iterator); while (result == ISC_R_SUCCESS) { isc_stdtime_t when; dns_rdatasetiter_current(iterator, &rdataset); if (rdataset.type == dns_rdatatype_soa || rdataset.type == dns_rdatatype_rrsig) { goto next_rdataset; } if (rdataset.type == dns_rdatatype_dnskey || rdataset.type == dns_rdatatype_cdnskey || rdataset.type == dns_rdatatype_cds) { /* * CDS and CDNSKEY are signed with KSK like DNSKEY. * (RFC 7344, section 4.1 specifies that they must * be signed with a key in the current DS RRset, * which would only include KSK's.) */ if (!is_ksk && keyset_kskonly) { goto next_rdataset; } } else if (!is_zsk) { goto next_rdataset; } else if (is_zsk && !dst_key_is_signing(key, DST_BOOL_ZSK, now, &when)) { /* Only applies to dnssec-policy. */ if (dns_zone_getkasp(zone) != NULL) { goto next_rdataset; } } if (seen_ns && !seen_soa && rdataset.type != dns_rdatatype_ds && rdataset.type != dns_rdatatype_nsec) { goto next_rdataset; } if (signed_with_good_key(zone, db, node, version, rdataset.type, key)) { goto next_rdataset; } /* Calculate the signature, creating a RRSIG RDATA. */ isc_buffer_clear(&buffer); CHECK(dns_dnssec_sign(name, &rdataset, key, &inception, &expire, mctx, &buffer, &rdata)); /* Update the database and journal with the RRSIG. */ /* XXX inefficient - will cause dataset merging */ CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_ADDRESIGN, name, rdataset.ttl, &rdata)); dns_rdata_reset(&rdata); /* Update DNSSEC sign statistics. */ dnssecsignstats = dns_zone_getdnssecsignstats(zone); if (dnssecsignstats != NULL) { /* Generated a new signature. */ dns_dnssecsignstats_increment(dnssecsignstats, ID(key), ALG(key), dns_dnssecsignstats_sign); /* This is a refresh. */ dns_dnssecsignstats_increment( dnssecsignstats, ID(key), ALG(key), dns_dnssecsignstats_refresh); } (*signatures)--; next_rdataset: dns_rdataset_disassociate(&rdataset); result = dns_rdatasetiter_next(iterator); } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (iterator != NULL) { dns_rdatasetiter_destroy(&iterator); } return (result); } /* * If 'update_only' is set then don't create a NSEC RRset if it doesn't exist. */ static isc_result_t updatesecure(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, dns_ttl_t nsecttl, bool update_only, dns_diff_t *diff) { isc_result_t result; dns_rdataset_t rdataset; dns_dbnode_t *node = NULL; CHECK(dns_db_getoriginnode(db, &node)); if (update_only) { dns_rdataset_init(&rdataset); result = dns_db_findrdataset( db, node, version, dns_rdatatype_nsec, dns_rdatatype_none, 0, &rdataset, NULL); if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (result == ISC_R_NOTFOUND) { goto success; } if (result != ISC_R_SUCCESS) { goto failure; } } CHECK(delete_nsec(db, version, node, name, diff)); CHECK(add_nsec(db, version, name, node, nsecttl, false, diff)); success: result = ISC_R_SUCCESS; failure: if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } static isc_result_t updatesignwithkey(dns_zone_t *zone, dns_signing_t *signing, dns_dbversion_t *version, bool build_nsec3, dns_ttl_t nsecttl, dns_diff_t *diff) { isc_result_t result; dns_dbnode_t *node = NULL; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; unsigned char data[5]; bool seen_done = false; bool have_rr = false; dns_rdataset_init(&rdataset); result = dns_db_getoriginnode(signing->db, &node); if (result != ISC_R_SUCCESS) { goto failure; } result = dns_db_findrdataset(signing->db, node, version, zone->privatetype, dns_rdatatype_none, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { INSIST(!dns_rdataset_isassociated(&rdataset)); result = ISC_R_SUCCESS; goto failure; } if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto failure; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdataset_current(&rdataset, &rdata); /* * If we don't match the algorithm or keyid skip the record. */ if (rdata.length != 5 || rdata.data[0] != signing->algorithm || rdata.data[1] != ((signing->keyid >> 8) & 0xff) || rdata.data[2] != (signing->keyid & 0xff)) { have_rr = true; dns_rdata_reset(&rdata); continue; } /* * We have a match. If we were signing (!signing->deleteit) * and we already have a record indicating that we have * finished signing (rdata.data[4] != 0) then keep it. * Otherwise it needs to be deleted as we have removed all * the signatures (signing->deleteit), so any record indicating * completion is now out of date, or we have finished signing * with the new record so we no longer need to remember that * we need to sign the zone with the matching key across a * nameserver re-start. */ if (!signing->deleteit && rdata.data[4] != 0) { seen_done = true; have_rr = true; } else { CHECK(update_one_rr(signing->db, version, diff, DNS_DIFFOP_DEL, &zone->origin, rdataset.ttl, &rdata)); } dns_rdata_reset(&rdata); } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } if (!signing->deleteit && !seen_done) { /* * If we were signing then we need to indicate that we have * finished signing the zone with this key. If it is already * there we don't need to add it a second time. */ data[0] = signing->algorithm; data[1] = (signing->keyid >> 8) & 0xff; data[2] = signing->keyid & 0xff; data[3] = 0; data[4] = 1; rdata.length = sizeof(data); rdata.data = data; rdata.type = zone->privatetype; rdata.rdclass = dns_db_class(signing->db); CHECK(update_one_rr(signing->db, version, diff, DNS_DIFFOP_ADD, &zone->origin, rdataset.ttl, &rdata)); } else if (!have_rr) { dns_name_t *origin = dns_db_origin(signing->db); /* * Rebuild the NSEC/NSEC3 record for the origin as we no * longer have any private records. */ if (build_nsec3) { CHECK(dns_nsec3_addnsec3s(signing->db, version, origin, nsecttl, false, diff)); } CHECK(updatesecure(signing->db, version, origin, nsecttl, true, diff)); } failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(signing->db, &node); } return (result); } /* * Called from zone_nsec3chain() in order to update zone records indicating * processing status of given NSEC3 chain: * * - If the supplied dns_nsec3chain_t structure has been fully processed * (which is indicated by "active" being set to false): * * - remove all NSEC3PARAM records matching the relevant NSEC3 chain, * * - remove all private-type records containing NSEC3PARAM RDATA matching * the relevant NSEC3 chain. * * - If the supplied dns_nsec3chain_t structure has not been fully processed * (which is indicated by "active" being set to true), only remove the * NSEC3PARAM record which matches the relevant NSEC3 chain and has the * "flags" field set to 0. * * - If given NSEC3 chain is being added, add an NSEC3PARAM record contained * in the relevant private-type record, but with the "flags" field set to * 0, indicating that this NSEC3 chain is now complete for this zone. * * Note that this function is called at different processing stages for NSEC3 * chain additions vs. removals and needs to handle all cases properly. */ static isc_result_t fixup_nsec3param(dns_db_t *db, dns_dbversion_t *ver, dns_nsec3chain_t *chain, bool active, dns_rdatatype_t privatetype, dns_diff_t *diff) { dns_dbnode_t *node = NULL; dns_name_t *name = dns_db_origin(db); dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t rdataset; dns_rdata_nsec3param_t nsec3param; isc_result_t result; isc_buffer_t buffer; unsigned char parambuf[DNS_NSEC3PARAM_BUFFERSIZE]; dns_ttl_t ttl = 0; bool nseconly = false, nsec3ok = false; dns_rdataset_init(&rdataset); result = dns_db_getoriginnode(db, &node); RUNTIME_CHECK(result == ISC_R_SUCCESS); result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { goto try_private; } if (result != ISC_R_SUCCESS) { goto failure; } /* * Preserve the existing ttl. */ ttl = rdataset.ttl; /* * Delete all NSEC3PARAM records which match that in nsec3chain. */ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); if (nsec3param.hash != chain->nsec3param.hash || (active && nsec3param.flags != 0) || nsec3param.iterations != chain->nsec3param.iterations || nsec3param.salt_length != chain->nsec3param.salt_length || memcmp(nsec3param.salt, chain->nsec3param.salt, nsec3param.salt_length)) { dns_rdata_reset(&rdata); continue; } CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, rdataset.ttl, &rdata)); dns_rdata_reset(&rdata); } if (result != ISC_R_NOMORE) { goto failure; } dns_rdataset_disassociate(&rdataset); try_private: if (active) { goto add; } result = dns_nsec_nseconly(db, ver, diff, &nseconly); nsec3ok = (result == ISC_R_SUCCESS && !nseconly); /* * Delete all private records which match that in nsec3chain. */ result = dns_db_findrdataset(db, node, ver, privatetype, 0, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { goto add; } if (result != ISC_R_SUCCESS) { goto failure; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t private = DNS_RDATA_INIT; unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; dns_rdataset_current(&rdataset, &private); if (!dns_nsec3param_fromprivate(&private, &rdata, buf, sizeof(buf))) { continue; } CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); if ((!nsec3ok && (nsec3param.flags & DNS_NSEC3FLAG_INITIAL) != 0) || nsec3param.hash != chain->nsec3param.hash || nsec3param.iterations != chain->nsec3param.iterations || nsec3param.salt_length != chain->nsec3param.salt_length || memcmp(nsec3param.salt, chain->nsec3param.salt, nsec3param.salt_length)) { dns_rdata_reset(&rdata); continue; } CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, rdataset.ttl, &private)); dns_rdata_reset(&rdata); } if (result != ISC_R_NOMORE) { goto failure; } add: if ((chain->nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) { result = ISC_R_SUCCESS; goto failure; } /* * Add a NSEC3PARAM record which matches that in nsec3chain but * with all flags bits cleared. * * Note: we do not clear chain->nsec3param.flags as this change * may be reversed. */ isc_buffer_init(&buffer, ¶mbuf, sizeof(parambuf)); CHECK(dns_rdata_fromstruct(&rdata, dns_db_class(db), dns_rdatatype_nsec3param, &chain->nsec3param, &buffer)); rdata.data[1] = 0; /* Clear flag bits. */ CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADD, name, ttl, &rdata)); failure: dns_db_detachnode(db, &node); if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } return (result); } static isc_result_t delete_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, dns_name_t *name, dns_diff_t *diff) { dns_rdataset_t rdataset; isc_result_t result; dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, 0, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { return (result); } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, rdataset.ttl, &rdata)); } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } failure: dns_rdataset_disassociate(&rdataset); return (result); } static isc_result_t deletematchingnsec3(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, dns_name_t *name, const dns_rdata_nsec3param_t *param, dns_diff_t *diff) { dns_rdataset_t rdataset; dns_rdata_nsec3_t nsec3; isc_result_t result; dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3, 0, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { return (result); } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &nsec3, NULL)); if (nsec3.hash != param->hash || nsec3.iterations != param->iterations || nsec3.salt_length != param->salt_length || memcmp(nsec3.salt, param->salt, nsec3.salt_length)) { continue; } CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_DEL, name, rdataset.ttl, &rdata)); } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } failure: dns_rdataset_disassociate(&rdataset); return (result); } static isc_result_t need_nsec_chain(dns_db_t *db, dns_dbversion_t *ver, const dns_rdata_nsec3param_t *param, bool *answer) { dns_dbnode_t *node = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_nsec3param_t myparam; dns_rdataset_t rdataset; isc_result_t result; *answer = false; result = dns_db_getoriginnode(db, &node); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec, 0, 0, &rdataset, NULL); if (result == ISC_R_SUCCESS) { dns_rdataset_disassociate(&rdataset); dns_db_detachnode(db, &node); return (result); } if (result != ISC_R_NOTFOUND) { dns_db_detachnode(db, &node); return (result); } result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { *answer = true; dns_db_detachnode(db, &node); return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { dns_db_detachnode(db, &node); return (result); } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &myparam, NULL)); dns_rdata_reset(&rdata); /* * Ignore any NSEC3PARAM removals. */ if (NSEC3REMOVE(myparam.flags)) { continue; } /* * Ignore the chain that we are in the process of deleting. */ if (myparam.hash == param->hash && myparam.iterations == param->iterations && myparam.salt_length == param->salt_length && !memcmp(myparam.salt, param->salt, myparam.salt_length)) { continue; } /* * Found an active NSEC3 chain. */ break; } if (result == ISC_R_NOMORE) { *answer = true; result = ISC_R_SUCCESS; } failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } dns_db_detachnode(db, &node); return (result); } /*% * Given a tuple which is part of a diff, return a pointer to the next tuple in * that diff which has the same name and type (or NULL if no such tuple is * found). */ static dns_difftuple_t * find_next_matching_tuple(dns_difftuple_t *cur) { dns_difftuple_t *next = cur; while ((next = ISC_LIST_NEXT(next, link)) != NULL) { if (cur->rdata.type == next->rdata.type && dns_name_equal(&cur->name, &next->name)) { return (next); } } return (NULL); } /*% * Remove all tuples with the same name and type as 'cur' from 'src' and append * them to 'dst'. */ static void move_matching_tuples(dns_difftuple_t *cur, dns_diff_t *src, dns_diff_t *dst) { do { dns_difftuple_t *next = find_next_matching_tuple(cur); ISC_LIST_UNLINK(src->tuples, cur, link); dns_diff_appendminimal(dst, &cur); cur = next; } while (cur != NULL); } /*% * Add/remove DNSSEC signatures for the list of "raw" zone changes supplied in * 'diff'. Gradually remove tuples from 'diff' and append them to 'zonediff' * along with tuples representing relevant signature changes. */ isc_result_t dns__zone_updatesigs(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *version, dst_key_t *zone_keys[], unsigned int nkeys, dns_zone_t *zone, isc_stdtime_t inception, isc_stdtime_t expire, isc_stdtime_t keyexpire, isc_stdtime_t now, bool check_ksk, bool keyset_kskonly, dns__zonediff_t *zonediff) { dns_difftuple_t *tuple; isc_result_t result; while ((tuple = ISC_LIST_HEAD(diff->tuples)) != NULL) { isc_stdtime_t exp = expire; if (keyexpire != 0 && (tuple->rdata.type == dns_rdatatype_dnskey || tuple->rdata.type == dns_rdatatype_cdnskey || tuple->rdata.type == dns_rdatatype_cds)) { exp = keyexpire; } result = del_sigs(zone, db, version, &tuple->name, tuple->rdata.type, zonediff, zone_keys, nkeys, now, false); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "dns__zone_updatesigs:del_sigs -> %s", isc_result_totext(result)); return (result); } result = add_sigs(db, version, &tuple->name, zone, tuple->rdata.type, zonediff->diff, zone_keys, nkeys, zone->mctx, now, inception, exp, check_ksk, keyset_kskonly); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "dns__zone_updatesigs:add_sigs -> %s", isc_result_totext(result)); return (result); } /* * Signature changes for all RRs with name tuple->name and type * tuple->rdata.type were appended to zonediff->diff. Now we * remove all the "raw" changes with the same name and type * from diff (so that they are not processed by this loop * again) and append them to zonediff so that they get applied. */ move_matching_tuples(tuple, diff, zonediff->diff); } return (ISC_R_SUCCESS); } /* * Incrementally build and sign a new NSEC3 chain using the parameters * requested. */ static void zone_nsec3chain(dns_zone_t *zone) { const char *me = "zone_nsec3chain"; dns_db_t *db = NULL; dns_dbnode_t *node = NULL; dns_dbversion_t *version = NULL; dns_diff_t _sig_diff; dns_diff_t nsec_diff; dns_diff_t nsec3_diff; dns_diff_t param_diff; dns__zonediff_t zonediff; dns_fixedname_t fixed; dns_fixedname_t nextfixed; dns_name_t *name, *nextname; dns_rdataset_t rdataset; dns_nsec3chain_t *nsec3chain = NULL, *nextnsec3chain; dns_nsec3chainlist_t cleanup; dst_key_t *zone_keys[DNS_MAXZONEKEYS]; int32_t signatures; bool check_ksk, keyset_kskonly; bool delegation; bool first; isc_result_t result; isc_stdtime_t now, inception, soaexpire, expire; uint32_t jitter, sigvalidityinterval, expiryinterval; unsigned int i; unsigned int nkeys = 0; uint32_t nodes; bool unsecure = false; bool seen_soa, seen_ns, seen_dname, seen_ds; bool seen_nsec, seen_nsec3, seen_rr; dns_rdatasetiter_t *iterator = NULL; bool buildnsecchain; bool updatensec = false; dns_rdatatype_t privatetype = zone->privatetype; ENTER; dns_rdataset_init(&rdataset); name = dns_fixedname_initname(&fixed); nextname = dns_fixedname_initname(&nextfixed); dns_diff_init(zone->mctx, ¶m_diff); dns_diff_init(zone->mctx, &nsec3_diff); dns_diff_init(zone->mctx, &nsec_diff); dns_diff_init(zone->mctx, &_sig_diff); zonediff_init(&zonediff, &_sig_diff); ISC_LIST_INIT(cleanup); /* * Updates are disabled. Pause for 5 minutes. */ if (zone->update_disabled) { result = ISC_R_FAILURE; goto failure; } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); /* * This function is called when zone timer fires, after the latter gets * set by zone_addnsec3chain(). If the action triggering the call to * zone_addnsec3chain() is closely followed by a zone deletion request, * it might turn out that the timer thread will not be woken up until * after the zone is deleted by rmzone(), which calls dns_db_detach() * for zone->db, causing the latter to become NULL. Return immediately * if that happens. */ if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { return; } result = dns_db_newversion(db, &version); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:dns_db_newversion -> %s", isc_result_totext(result)); goto failure; } isc_stdtime_get(&now); result = dns__zone_findkeys(zone, db, version, now, zone->mctx, DNS_MAXZONEKEYS, zone_keys, &nkeys); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:dns__zone_findkeys -> %s", isc_result_totext(result)); goto failure; } sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); inception = now - 3600; /* Allow for clock skew. */ soaexpire = now + sigvalidityinterval; expiryinterval = dns_zone_getsigresigninginterval(zone); if (expiryinterval > sigvalidityinterval) { expiryinterval = sigvalidityinterval; } else { expiryinterval = sigvalidityinterval - expiryinterval; } /* * Spread out signatures over time if they happen to be * clumped. We don't do this for each add_sigs() call as * we still want some clustering to occur. */ if (sigvalidityinterval >= 3600U) { if (sigvalidityinterval > 7200U) { jitter = isc_random_uniform(expiryinterval); } else { jitter = isc_random_uniform(1200); } expire = soaexpire - jitter - 1; } else { expire = soaexpire - 1; } check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); /* * We keep pulling nodes off each iterator in turn until * we have no more nodes to pull off or we reach the limits * for this quantum. */ nodes = zone->nodes; signatures = zone->signatures; LOCK_ZONE(zone); nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); UNLOCK_ZONE(zone); first = true; if (nsec3chain != NULL) { nsec3chain->save_delete_nsec = nsec3chain->delete_nsec; } /* * Generate new NSEC3 chains first. * * The following while loop iterates over nodes in the zone database, * updating the NSEC3 chain by calling dns_nsec3_addnsec3() for each of * them. Once all nodes are processed, the "delete_nsec" field is * consulted to check whether we are supposed to remove NSEC records * from the zone database; if so, the database iterator is reset to * point to the first node and the loop traverses all of them again, * this time removing NSEC records. If we hit a node which is obscured * by a delegation or a DNAME, nodes are skipped over until we find one * that is not obscured by the same obscuring name and then normal * processing is resumed. * * The above is repeated until all requested NSEC3 chain changes are * applied or when we reach the limits for this quantum, whichever * happens first. * * Note that the "signatures" variable is only used here to limit the * amount of work performed. Actual DNSSEC signatures are only * generated by dns__zone_updatesigs() calls later in this function. */ while (nsec3chain != NULL && nodes-- > 0 && signatures > 0) { dns_dbiterator_pause(nsec3chain->dbiterator); LOCK_ZONE(zone); nextnsec3chain = ISC_LIST_NEXT(nsec3chain, link); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (nsec3chain->done || nsec3chain->db != zone->db) { ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link); ISC_LIST_APPEND(cleanup, nsec3chain, link); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); UNLOCK_ZONE(zone); if (ISC_LIST_TAIL(cleanup) == nsec3chain) { goto next_addchain; } /* * Possible future db. */ if (nsec3chain->db != db) { goto next_addchain; } if (NSEC3REMOVE(nsec3chain->nsec3param.flags)) { goto next_addchain; } dns_dbiterator_current(nsec3chain->dbiterator, &node, name); if (nsec3chain->delete_nsec) { delegation = false; dns_dbiterator_pause(nsec3chain->dbiterator); CHECK(delete_nsec(db, version, node, name, &nsec_diff)); goto next_addnode; } /* * On the first pass we need to check if the current node * has not been obscured. */ delegation = false; unsecure = false; if (first) { dns_fixedname_t ffound; dns_name_t *found; found = dns_fixedname_initname(&ffound); result = dns_db_find( db, name, version, dns_rdatatype_soa, DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL); if ((result == DNS_R_DELEGATION || result == DNS_R_DNAME) && !dns_name_equal(name, found)) { /* * Remember the obscuring name so that * we skip all obscured names. */ dns_name_copy(found, name); delegation = true; goto next_addnode; } } /* * Check to see if this is a bottom of zone node. */ result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator); if (result == ISC_R_NOTFOUND) { /* Empty node? */ goto next_addnode; } if (result != ISC_R_SUCCESS) { goto failure; } seen_soa = seen_ns = seen_dname = seen_ds = seen_nsec = false; for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(iterator)) { dns_rdatasetiter_current(iterator, &rdataset); INSIST(rdataset.type != dns_rdatatype_nsec3); if (rdataset.type == dns_rdatatype_soa) { seen_soa = true; } else if (rdataset.type == dns_rdatatype_ns) { seen_ns = true; } else if (rdataset.type == dns_rdatatype_dname) { seen_dname = true; } else if (rdataset.type == dns_rdatatype_ds) { seen_ds = true; } else if (rdataset.type == dns_rdatatype_nsec) { seen_nsec = true; } dns_rdataset_disassociate(&rdataset); } dns_rdatasetiter_destroy(&iterator); /* * Is there a NSEC chain than needs to be cleaned up? */ if (seen_nsec) { nsec3chain->seen_nsec = true; } if (seen_ns && !seen_soa && !seen_ds) { unsecure = true; } if ((seen_ns && !seen_soa) || seen_dname) { delegation = true; } /* * Process one node. */ dns_dbiterator_pause(nsec3chain->dbiterator); result = dns_nsec3_addnsec3( db, version, name, &nsec3chain->nsec3param, zone_nsecttl(zone), unsecure, &nsec3_diff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:" "dns_nsec3_addnsec3 -> %s", isc_result_totext(result)); goto failure; } /* * Treat each call to dns_nsec3_addnsec3() as if it's cost is * two signatures. Additionally there will, in general, be * two signature generated below. * * If we are only changing the optout flag the cost is half * that of the cost of generating a completely new chain. */ signatures -= 4; /* * Go onto next node. */ next_addnode: first = false; dns_db_detachnode(db, &node); do { result = dns_dbiterator_next(nsec3chain->dbiterator); if (result == ISC_R_NOMORE && nsec3chain->delete_nsec) { dns_dbiterator_pause(nsec3chain->dbiterator); CHECK(fixup_nsec3param(db, version, nsec3chain, false, privatetype, ¶m_diff)); LOCK_ZONE(zone); ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link); UNLOCK_ZONE(zone); ISC_LIST_APPEND(cleanup, nsec3chain, link); goto next_addchain; } if (result == ISC_R_NOMORE) { dns_dbiterator_pause(nsec3chain->dbiterator); if (nsec3chain->seen_nsec) { CHECK(fixup_nsec3param( db, version, nsec3chain, true, privatetype, ¶m_diff)); nsec3chain->delete_nsec = true; goto same_addchain; } CHECK(fixup_nsec3param(db, version, nsec3chain, false, privatetype, ¶m_diff)); LOCK_ZONE(zone); ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link); UNLOCK_ZONE(zone); ISC_LIST_APPEND(cleanup, nsec3chain, link); goto next_addchain; } else if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:" "dns_dbiterator_next -> %s", isc_result_totext(result)); goto failure; } else if (delegation) { dns_dbiterator_current(nsec3chain->dbiterator, &node, nextname); dns_db_detachnode(db, &node); if (!dns_name_issubdomain(nextname, name)) { break; } } else { break; } } while (1); continue; same_addchain: CHECK(dns_dbiterator_first(nsec3chain->dbiterator)); first = true; continue; next_addchain: dns_dbiterator_pause(nsec3chain->dbiterator); nsec3chain = nextnsec3chain; first = true; if (nsec3chain != NULL) { nsec3chain->save_delete_nsec = nsec3chain->delete_nsec; } } if (nsec3chain != NULL) { goto skip_removals; } /* * Process removals. * * This is a counterpart of the above while loop which takes care of * removing an NSEC3 chain. It starts with determining whether the * zone needs to switch from NSEC3 to NSEC; if so, it first builds an * NSEC chain by iterating over all nodes in the zone database and only * then goes on to remove NSEC3 records be iterating over all nodes * again and calling deletematchingnsec3() for each of them; otherwise, * it starts removing NSEC3 records immediately. Rules for processing * obscured nodes and interrupting work are the same as for the while * loop above. */ LOCK_ZONE(zone); nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); UNLOCK_ZONE(zone); first = true; buildnsecchain = false; while (nsec3chain != NULL && nodes-- > 0 && signatures > 0) { dns_dbiterator_pause(nsec3chain->dbiterator); LOCK_ZONE(zone); nextnsec3chain = ISC_LIST_NEXT(nsec3chain, link); UNLOCK_ZONE(zone); if (nsec3chain->db != db) { goto next_removechain; } if (!NSEC3REMOVE(nsec3chain->nsec3param.flags)) { goto next_removechain; } /* * Work out if we need to build a NSEC chain as a consequence * of removing this NSEC3 chain. */ if (first && !updatensec && (nsec3chain->nsec3param.flags & DNS_NSEC3FLAG_NONSEC) == 0) { result = need_nsec_chain(db, version, &nsec3chain->nsec3param, &buildnsecchain); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:" "need_nsec_chain -> %s", isc_result_totext(result)); goto failure; } } if (first) { dnssec_log(zone, ISC_LOG_DEBUG(3), "zone_nsec3chain:buildnsecchain = %u\n", buildnsecchain); } dns_dbiterator_current(nsec3chain->dbiterator, &node, name); dns_dbiterator_pause(nsec3chain->dbiterator); delegation = false; if (!buildnsecchain) { /* * Delete the NSEC3PARAM record matching this chain. */ if (first) { result = fixup_nsec3param( db, version, nsec3chain, true, privatetype, ¶m_diff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:" "fixup_nsec3param -> %s", isc_result_totext(result)); goto failure; } } /* * Delete the NSEC3 records. */ result = deletematchingnsec3(db, version, node, name, &nsec3chain->nsec3param, &nsec3_diff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:" "deletematchingnsec3 -> %s", isc_result_totext(result)); goto failure; } goto next_removenode; } if (first) { dns_fixedname_t ffound; dns_name_t *found; found = dns_fixedname_initname(&ffound); result = dns_db_find( db, name, version, dns_rdatatype_soa, DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL); if ((result == DNS_R_DELEGATION || result == DNS_R_DNAME) && !dns_name_equal(name, found)) { /* * Remember the obscuring name so that * we skip all obscured names. */ dns_name_copy(found, name); delegation = true; goto next_removenode; } } /* * Check to see if this is a bottom of zone node. */ result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator); if (result == ISC_R_NOTFOUND) { /* Empty node? */ goto next_removenode; } if (result != ISC_R_SUCCESS) { goto failure; } seen_soa = seen_ns = seen_dname = seen_nsec3 = seen_nsec = seen_rr = false; for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(iterator)) { dns_rdatasetiter_current(iterator, &rdataset); if (rdataset.type == dns_rdatatype_soa) { seen_soa = true; } else if (rdataset.type == dns_rdatatype_ns) { seen_ns = true; } else if (rdataset.type == dns_rdatatype_dname) { seen_dname = true; } else if (rdataset.type == dns_rdatatype_nsec) { seen_nsec = true; } else if (rdataset.type == dns_rdatatype_nsec3) { seen_nsec3 = true; } else if (rdataset.type != dns_rdatatype_rrsig) { seen_rr = true; } dns_rdataset_disassociate(&rdataset); } dns_rdatasetiter_destroy(&iterator); if (!seen_rr || seen_nsec3 || seen_nsec) { goto next_removenode; } if ((seen_ns && !seen_soa) || seen_dname) { delegation = true; } /* * Add a NSEC record except at the origin. */ if (!dns_name_equal(name, dns_db_origin(db))) { dns_dbiterator_pause(nsec3chain->dbiterator); CHECK(add_nsec(db, version, name, node, zone_nsecttl(zone), delegation, &nsec_diff)); signatures--; } next_removenode: first = false; dns_db_detachnode(db, &node); do { result = dns_dbiterator_next(nsec3chain->dbiterator); if (result == ISC_R_NOMORE && buildnsecchain) { /* * The NSEC chain should now be built. * We can now remove the NSEC3 chain. */ updatensec = true; goto same_removechain; } if (result == ISC_R_NOMORE) { dns_dbiterator_pause(nsec3chain->dbiterator); LOCK_ZONE(zone); ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain, link); UNLOCK_ZONE(zone); ISC_LIST_APPEND(cleanup, nsec3chain, link); result = fixup_nsec3param( db, version, nsec3chain, false, privatetype, ¶m_diff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:" "fixup_nsec3param -> %s", isc_result_totext(result)); goto failure; } goto next_removechain; } else if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:" "dns_dbiterator_next -> %s", isc_result_totext(result)); goto failure; } else if (delegation) { dns_dbiterator_current(nsec3chain->dbiterator, &node, nextname); dns_db_detachnode(db, &node); if (!dns_name_issubdomain(nextname, name)) { break; } } else { break; } } while (1); continue; same_removechain: CHECK(dns_dbiterator_first(nsec3chain->dbiterator)); buildnsecchain = false; first = true; continue; next_removechain: dns_dbiterator_pause(nsec3chain->dbiterator); nsec3chain = nextnsec3chain; first = true; } skip_removals: /* * We may need to update the NSEC/NSEC3 records for the zone apex. */ if (!ISC_LIST_EMPTY(param_diff.tuples)) { bool rebuild_nsec = false, rebuild_nsec3 = false; result = dns_db_getoriginnode(db, &node); RUNTIME_CHECK(result == ISC_R_SUCCESS); result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:dns_db_allrdatasets -> %s", isc_result_totext(result)); goto failure; } for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(iterator)) { dns_rdatasetiter_current(iterator, &rdataset); if (rdataset.type == dns_rdatatype_nsec) { rebuild_nsec = true; } else if (rdataset.type == dns_rdatatype_nsec3param) { rebuild_nsec3 = true; } dns_rdataset_disassociate(&rdataset); } dns_rdatasetiter_destroy(&iterator); dns_db_detachnode(db, &node); if (rebuild_nsec) { if (nsec3chain != NULL) { dns_dbiterator_pause(nsec3chain->dbiterator); } result = updatesecure(db, version, &zone->origin, zone_nsecttl(zone), true, &nsec_diff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:updatesecure -> %s", isc_result_totext(result)); goto failure; } } if (rebuild_nsec3) { if (nsec3chain != NULL) { dns_dbiterator_pause(nsec3chain->dbiterator); } result = dns_nsec3_addnsec3s( db, version, dns_db_origin(db), zone_nsecttl(zone), false, &nsec3_diff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:" "dns_nsec3_addnsec3s -> %s", isc_result_totext(result)); goto failure; } } } /* * Add / update signatures for the NSEC3 records. */ if (nsec3chain != NULL) { dns_dbiterator_pause(nsec3chain->dbiterator); } result = dns__zone_updatesigs(&nsec3_diff, db, version, zone_keys, nkeys, zone, inception, expire, 0, now, check_ksk, keyset_kskonly, &zonediff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:dns__zone_updatesigs -> %s", isc_result_totext(result)); goto failure; } /* * We have changed the NSEC3PARAM or private RRsets * above so we need to update the signatures. */ result = dns__zone_updatesigs(¶m_diff, db, version, zone_keys, nkeys, zone, inception, expire, 0, now, check_ksk, keyset_kskonly, &zonediff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:dns__zone_updatesigs -> %s", isc_result_totext(result)); goto failure; } if (updatensec) { result = updatesecure(db, version, &zone->origin, zone_nsecttl(zone), false, &nsec_diff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:updatesecure -> %s", isc_result_totext(result)); goto failure; } } result = dns__zone_updatesigs(&nsec_diff, db, version, zone_keys, nkeys, zone, inception, expire, 0, now, check_ksk, keyset_kskonly, &zonediff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:dns__zone_updatesigs -> %s", isc_result_totext(result)); goto failure; } /* * If we made no effective changes to the zone then we can just * cleanup otherwise we need to increment the serial. */ if (ISC_LIST_EMPTY(zonediff.diff->tuples)) { /* * No need to call dns_db_closeversion() here as it is * called with commit = true below. */ goto done; } result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa, &zonediff, zone_keys, nkeys, now, false); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:del_sigs -> %s", isc_result_totext(result)); goto failure; } result = update_soa_serial(zone, db, version, zonediff.diff, zone->mctx, zone->updatemethod); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:update_soa_serial -> %s", isc_result_totext(result)); goto failure; } result = add_sigs(db, version, &zone->origin, zone, dns_rdatatype_soa, zonediff.diff, zone_keys, nkeys, zone->mctx, now, inception, soaexpire, check_ksk, keyset_kskonly); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:add_sigs -> %s", isc_result_totext(result)); goto failure; } /* Write changes to journal file. */ CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_nsec3chain")); LOCK_ZONE(zone); zone_needdump(zone, DNS_DUMP_DELAY); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); UNLOCK_ZONE(zone); done: /* * Pause all iterators so that dns_db_closeversion() can succeed. */ LOCK_ZONE(zone); for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); nsec3chain != NULL; nsec3chain = ISC_LIST_NEXT(nsec3chain, link)) { dns_dbiterator_pause(nsec3chain->dbiterator); } UNLOCK_ZONE(zone); /* * Everything has succeeded. Commit the changes. * Unconditionally commit as zonediff.offline not checked above. */ dns_db_closeversion(db, &version, true); /* * Everything succeeded so we can clean these up now. */ nsec3chain = ISC_LIST_HEAD(cleanup); while (nsec3chain != NULL) { ISC_LIST_UNLINK(cleanup, nsec3chain, link); dns_db_detach(&nsec3chain->db); dns_dbiterator_destroy(&nsec3chain->dbiterator); isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); nsec3chain = ISC_LIST_HEAD(cleanup); } LOCK_ZONE(zone); set_resigntime(zone); UNLOCK_ZONE(zone); failure: if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain: %s", isc_result_totext(result)); } /* * On error roll back the current nsec3chain. */ if (result != ISC_R_SUCCESS && nsec3chain != NULL) { if (nsec3chain->done) { dns_db_detach(&nsec3chain->db); dns_dbiterator_destroy(&nsec3chain->dbiterator); isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); } else { result = dns_dbiterator_first(nsec3chain->dbiterator); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_dbiterator_pause(nsec3chain->dbiterator); nsec3chain->delete_nsec = nsec3chain->save_delete_nsec; } } /* * Rollback the cleanup list. */ nsec3chain = ISC_LIST_TAIL(cleanup); while (nsec3chain != NULL) { ISC_LIST_UNLINK(cleanup, nsec3chain, link); if (nsec3chain->done) { dns_db_detach(&nsec3chain->db); dns_dbiterator_destroy(&nsec3chain->dbiterator); isc_mem_put(zone->mctx, nsec3chain, sizeof *nsec3chain); } else { LOCK_ZONE(zone); ISC_LIST_PREPEND(zone->nsec3chain, nsec3chain, link); UNLOCK_ZONE(zone); result = dns_dbiterator_first(nsec3chain->dbiterator); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_dbiterator_pause(nsec3chain->dbiterator); nsec3chain->delete_nsec = nsec3chain->save_delete_nsec; } nsec3chain = ISC_LIST_TAIL(cleanup); } LOCK_ZONE(zone); for (nsec3chain = ISC_LIST_HEAD(zone->nsec3chain); nsec3chain != NULL; nsec3chain = ISC_LIST_NEXT(nsec3chain, link)) { dns_dbiterator_pause(nsec3chain->dbiterator); } UNLOCK_ZONE(zone); dns_diff_clear(¶m_diff); dns_diff_clear(&nsec3_diff); dns_diff_clear(&nsec_diff); dns_diff_clear(&_sig_diff); if (iterator != NULL) { dns_rdatasetiter_destroy(&iterator); } for (i = 0; i < nkeys; i++) { dst_key_free(&zone_keys[i]); } if (node != NULL) { dns_db_detachnode(db, &node); } if (version != NULL) { dns_db_closeversion(db, &version, false); dns_db_detach(&db); } else if (db != NULL) { dns_db_detach(&db); } LOCK_ZONE(zone); if (ISC_LIST_HEAD(zone->nsec3chain) != NULL) { isc_interval_t interval; if (zone->update_disabled || result != ISC_R_SUCCESS) { isc_interval_set(&interval, 60, 0); /* 1 minute */ } else { isc_interval_set(&interval, 0, 10000000); /* 10 ms */ } isc_time_nowplusinterval(&zone->nsec3chaintime, &interval); } else { isc_time_settoepoch(&zone->nsec3chaintime); } UNLOCK_ZONE(zone); INSIST(version == NULL); } /*% * Delete all RRSIG records with the given algorithm and keyid. * Remove the NSEC record and RRSIGs if nkeys is zero. * If all remaining RRsets are signed with the given algorithm * set *has_algp to true. */ static isc_result_t del_sig(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, dns_dbnode_t *node, unsigned int nkeys, dns_secalg_t algorithm, uint16_t keyid, bool *has_algp, dns_diff_t *diff) { dns_rdata_rrsig_t rrsig; dns_rdataset_t rdataset; dns_rdatasetiter_t *iterator = NULL; isc_result_t result; bool alg_missed = false; bool alg_found = false; char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(name, namebuf, sizeof(namebuf)); result = dns_db_allrdatasets(db, node, version, 0, 0, &iterator); if (result != ISC_R_SUCCESS) { if (result == ISC_R_NOTFOUND) { result = ISC_R_SUCCESS; } return (result); } dns_rdataset_init(&rdataset); for (result = dns_rdatasetiter_first(iterator); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(iterator)) { bool has_alg = false; dns_rdatasetiter_current(iterator, &rdataset); if (nkeys == 0 && rdataset.type == dns_rdatatype_nsec) { for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_DEL, name, rdataset.ttl, &rdata)); } if (result != ISC_R_NOMORE) { goto failure; } dns_rdataset_disassociate(&rdataset); continue; } if (rdataset.type != dns_rdatatype_rrsig) { dns_rdataset_disassociate(&rdataset); continue; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &rrsig, NULL)); if (nkeys != 0 && (rrsig.algorithm != algorithm || rrsig.keyid != keyid)) { if (rrsig.algorithm == algorithm) { has_alg = true; } continue; } CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_DELRESIGN, name, rdataset.ttl, &rdata)); } dns_rdataset_disassociate(&rdataset); if (result != ISC_R_NOMORE) { break; } /* * After deleting, if there's still a signature for * 'algorithm', set alg_found; if not, set alg_missed. */ if (has_alg) { alg_found = true; } else { alg_missed = true; } } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } /* * Set `has_algp` if the algorithm was found in every RRset: * i.e., found in at least one, and not missing from any. */ *has_algp = (alg_found && !alg_missed); failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } dns_rdatasetiter_destroy(&iterator); return (result); } /* * Prevent the zone entering a inconsistent state where * NSEC only DNSKEYs are present with NSEC3 chains. */ bool dns_zone_check_dnskey_nsec3(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff, dst_key_t **keys, unsigned int numkeys) { uint8_t alg; dns_rdatatype_t privatetype; ; bool nseconly = false, nsec3 = false; isc_result_t result; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(db != NULL); privatetype = dns_zone_getprivatetype(zone); /* Scan the tuples for an NSEC-only DNSKEY */ if (diff != NULL) { for (dns_difftuple_t *tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link)) { if (nseconly && nsec3) { break; } if (tuple->op != DNS_DIFFOP_ADD) { continue; } if (tuple->rdata.type == dns_rdatatype_nsec3param) { nsec3 = true; } if (tuple->rdata.type != dns_rdatatype_dnskey) { continue; } alg = tuple->rdata.data[3]; if (alg == DNS_KEYALG_RSAMD5 || alg == DNS_KEYALG_DH || alg == DNS_KEYALG_DSA || alg == DNS_KEYALG_RSASHA1) { nseconly = true; } } } /* Scan the zone keys for an NSEC-only DNSKEY */ if (keys != NULL && !nseconly) { for (unsigned int i = 0; i < numkeys; i++) { alg = dst_key_alg(keys[i]); if (alg == DNS_KEYALG_RSAMD5 || alg == DNS_KEYALG_DH || alg == DNS_KEYALG_DSA || alg == DNS_KEYALG_RSASHA1) { nseconly = true; break; } } } /* Check DB for NSEC-only DNSKEY */ if (!nseconly) { result = dns_nsec_nseconly(db, ver, diff, &nseconly); /* * Adding an NSEC3PARAM record can proceed without a * DNSKEY (it will trigger a delayed change), so we can * ignore ISC_R_NOTFOUND here. */ if (result == ISC_R_NOTFOUND) { result = ISC_R_SUCCESS; } CHECK(result); } /* Check existing DB for NSEC3 */ if (!nsec3) { CHECK(dns_nsec3_activex(db, ver, false, privatetype, &nsec3)); } /* Check kasp for NSEC3PARAM settings */ if (!nsec3) { dns_kasp_t *kasp = dns_zone_getkasp(zone); if (kasp != NULL) { nsec3 = dns_kasp_nsec3(kasp); } } /* Refuse to allow NSEC3 with NSEC-only keys */ if (nseconly && nsec3) { goto failure; } return (true); failure: return (false); } /* * Incrementally sign the zone using the keys requested. * Builds the NSEC chain if required. */ static void zone_sign(dns_zone_t *zone) { const char *me = "zone_sign"; dns_db_t *db = NULL; dns_dbnode_t *node = NULL; dns_dbversion_t *version = NULL; dns_diff_t _sig_diff; dns_diff_t post_diff; dns__zonediff_t zonediff; dns_fixedname_t fixed; dns_fixedname_t nextfixed; dns_kasp_t *kasp; dns_name_t *name, *nextname; dns_rdataset_t rdataset; dns_signing_t *signing, *nextsigning; dns_signinglist_t cleanup; dst_key_t *zone_keys[DNS_MAXZONEKEYS]; int32_t signatures; bool check_ksk, keyset_kskonly, is_ksk, is_zsk; bool with_ksk, with_zsk; bool commit = false; bool is_bottom_of_zone; bool build_nsec = false; bool build_nsec3 = false; bool use_kasp = false; bool first; isc_result_t result; isc_stdtime_t now, inception, soaexpire, expire; uint32_t jitter, sigvalidityinterval, expiryinterval; unsigned int i, j; unsigned int nkeys = 0; uint32_t nodes; ENTER; dns_rdataset_init(&rdataset); name = dns_fixedname_initname(&fixed); nextname = dns_fixedname_initname(&nextfixed); dns_diff_init(zone->mctx, &_sig_diff); dns_diff_init(zone->mctx, &post_diff); zonediff_init(&zonediff, &_sig_diff); ISC_LIST_INIT(cleanup); /* * Updates are disabled. Pause for 1 minute. */ if (zone->update_disabled) { result = ISC_R_FAILURE; goto cleanup; } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { result = ISC_R_FAILURE; goto cleanup; } result = dns_db_newversion(db, &version); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:dns_db_newversion -> %s", isc_result_totext(result)); goto cleanup; } isc_stdtime_get(&now); result = dns__zone_findkeys(zone, db, version, now, zone->mctx, DNS_MAXZONEKEYS, zone_keys, &nkeys); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:dns__zone_findkeys -> %s", isc_result_totext(result)); goto cleanup; } kasp = dns_zone_getkasp(zone); sigvalidityinterval = dns_zone_getsigvalidityinterval(zone); inception = now - 3600; /* Allow for clock skew. */ soaexpire = now + sigvalidityinterval; expiryinterval = dns_zone_getsigresigninginterval(zone); if (expiryinterval > sigvalidityinterval) { expiryinterval = sigvalidityinterval; } else { expiryinterval = sigvalidityinterval - expiryinterval; } /* * Spread out signatures over time if they happen to be * clumped. We don't do this for each add_sigs() call as * we still want some clustering to occur. */ if (sigvalidityinterval >= 3600U) { if (sigvalidityinterval > 7200U) { jitter = isc_random_uniform(expiryinterval); } else { jitter = isc_random_uniform(1200); } expire = soaexpire - jitter - 1; } else { expire = soaexpire - 1; } /* * We keep pulling nodes off each iterator in turn until * we have no more nodes to pull off or we reach the limits * for this quantum. */ nodes = zone->nodes; signatures = zone->signatures; signing = ISC_LIST_HEAD(zone->signing); first = true; if (dns_zone_getkasp(zone) != NULL) { check_ksk = false; keyset_kskonly = true; use_kasp = true; } else { check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); } dnssec_log(zone, ISC_LOG_DEBUG(3), "zone_sign:use kasp -> %s", use_kasp ? "yes" : "no"); /* Determine which type of chain to build */ CHECK(dns_private_chains(db, version, zone->privatetype, &build_nsec, &build_nsec3)); if (!build_nsec && !build_nsec3) { if (use_kasp) { build_nsec3 = dns_kasp_nsec3(kasp); if (!dns_zone_check_dnskey_nsec3( zone, db, version, NULL, (dst_key_t **)&zone_keys, nkeys)) { dnssec_log(zone, ISC_LOG_INFO, "wait building NSEC3 chain until " "NSEC only DNSKEYs are removed"); build_nsec3 = false; } build_nsec = !build_nsec3; } else { /* If neither chain is found, default to NSEC */ build_nsec = true; } } while (signing != NULL && nodes-- > 0 && signatures > 0) { bool has_alg = false; dns_dbiterator_pause(signing->dbiterator); nextsigning = ISC_LIST_NEXT(signing, link); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (signing->done || signing->db != zone->db) { /* * The zone has been reloaded. We will have to * created new signings as part of the reload * process so we can destroy this one. */ ISC_LIST_UNLINK(zone->signing, signing, link); ISC_LIST_APPEND(cleanup, signing, link); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); goto next_signing; } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (signing->db != db) { goto next_signing; } is_bottom_of_zone = false; if (first && signing->deleteit) { /* * Remove the key we are deleting from consideration. */ for (i = 0, j = 0; i < nkeys; i++) { /* * Find the key we want to remove. */ if (ALG(zone_keys[i]) == signing->algorithm && dst_key_id(zone_keys[i]) == signing->keyid) { dst_key_free(&zone_keys[i]); continue; } zone_keys[j] = zone_keys[i]; j++; } for (i = j; i < nkeys; i++) { zone_keys[i] = NULL; } nkeys = j; } dns_dbiterator_current(signing->dbiterator, &node, name); if (signing->deleteit) { dns_dbiterator_pause(signing->dbiterator); CHECK(del_sig(db, version, name, node, nkeys, signing->algorithm, signing->keyid, &has_alg, zonediff.diff)); } /* * On the first pass we need to check if the current node * has not been obscured. */ if (first) { dns_fixedname_t ffound; dns_name_t *found; found = dns_fixedname_initname(&ffound); result = dns_db_find( db, name, version, dns_rdatatype_soa, DNS_DBFIND_NOWILD, 0, NULL, found, NULL, NULL); if ((result == DNS_R_DELEGATION || result == DNS_R_DNAME) && !dns_name_equal(name, found)) { /* * Remember the obscuring name so that * we skip all obscured names. */ dns_name_copy(found, name); is_bottom_of_zone = true; goto next_node; } } /* * Process one node. */ with_ksk = false; with_zsk = false; dns_dbiterator_pause(signing->dbiterator); CHECK(check_if_bottom_of_zone(db, node, version, &is_bottom_of_zone)); for (i = 0; !has_alg && i < nkeys; i++) { bool both = false; /* * Find the keys we want to sign with. */ if (!dst_key_isprivate(zone_keys[i])) { continue; } if (dst_key_inactive(zone_keys[i])) { continue; } /* * When adding look for the specific key. */ if (!signing->deleteit && (dst_key_alg(zone_keys[i]) != signing->algorithm || dst_key_id(zone_keys[i]) != signing->keyid)) { continue; } /* * When deleting make sure we are properly signed * with the algorithm that was being removed. */ if (signing->deleteit && ALG(zone_keys[i]) != signing->algorithm) { continue; } /* * Do we do KSK processing? */ if (check_ksk && !REVOKE(zone_keys[i])) { bool have_ksk, have_nonksk; if (KSK(zone_keys[i])) { have_ksk = true; have_nonksk = false; } else { have_ksk = false; have_nonksk = true; } for (j = 0; j < nkeys; j++) { if (j == i || (ALG(zone_keys[i]) != ALG(zone_keys[j]))) { continue; } /* * Don't consider inactive keys, however * the key may be temporary offline, so * do consider KSKs which private key * files are unavailable. */ if (dst_key_inactive(zone_keys[j])) { continue; } if (REVOKE(zone_keys[j])) { continue; } if (KSK(zone_keys[j])) { have_ksk = true; } else if (dst_key_isprivate( zone_keys[j])) { have_nonksk = true; } both = have_ksk && have_nonksk; if (both) { break; } } } if (use_kasp) { /* * A dnssec-policy is found. Check what * RRsets this key can sign. */ isc_result_t kresult; is_ksk = false; kresult = dst_key_getbool( zone_keys[i], DST_BOOL_KSK, &is_ksk); if (kresult != ISC_R_SUCCESS) { if (KSK(zone_keys[i])) { is_ksk = true; } } is_zsk = false; kresult = dst_key_getbool( zone_keys[i], DST_BOOL_ZSK, &is_zsk); if (kresult != ISC_R_SUCCESS) { if (!KSK(zone_keys[i])) { is_zsk = true; } } /* Treat as if we have both KSK and ZSK. */ both = true; } else if (both || REVOKE(zone_keys[i])) { is_ksk = KSK(zone_keys[i]); is_zsk = !KSK(zone_keys[i]); } else { is_ksk = false; is_zsk = true; } /* * If deleting signatures, we need to ensure that * the RRset is still signed at least once by a * KSK and a ZSK. */ if (signing->deleteit && is_zsk && with_zsk) { continue; } if (signing->deleteit && is_ksk && with_ksk) { continue; } CHECK(sign_a_node( db, zone, name, node, version, build_nsec3, build_nsec, zone_keys[i], now, inception, expire, zone_nsecttl(zone), is_ksk, is_zsk, (both && keyset_kskonly), is_bottom_of_zone, zonediff.diff, &signatures, zone->mctx)); /* * If we are adding we are done. Look for other keys * of the same algorithm if deleting. */ if (!signing->deleteit) { break; } if (is_zsk) { with_zsk = true; } if (is_ksk) { with_ksk = true; } } /* * Go onto next node. */ next_node: first = false; dns_db_detachnode(db, &node); do { result = dns_dbiterator_next(signing->dbiterator); if (result == ISC_R_NOMORE) { ISC_LIST_UNLINK(zone->signing, signing, link); ISC_LIST_APPEND(cleanup, signing, link); dns_dbiterator_pause(signing->dbiterator); if (nkeys != 0 && build_nsec) { /* * We have finished regenerating the * zone with a zone signing key. * The NSEC chain is now complete and * there is a full set of signatures * for the zone. We can now clear the * OPT bit from the NSEC record. */ result = updatesecure( db, version, &zone->origin, zone_nsecttl(zone), false, &post_diff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "updatesecure -> %s", isc_result_totext( result)); goto cleanup; } } result = updatesignwithkey( zone, signing, version, build_nsec3, zone_nsecttl(zone), &post_diff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "updatesignwithkey -> %s", isc_result_totext(result)); goto cleanup; } build_nsec = false; goto next_signing; } else if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:" "dns_dbiterator_next -> %s", isc_result_totext(result)); goto cleanup; } else if (is_bottom_of_zone) { dns_dbiterator_current(signing->dbiterator, &node, nextname); dns_db_detachnode(db, &node); if (!dns_name_issubdomain(nextname, name)) { break; } } else { break; } } while (1); continue; next_signing: dns_dbiterator_pause(signing->dbiterator); signing = nextsigning; first = true; } if (ISC_LIST_HEAD(post_diff.tuples) != NULL) { result = dns__zone_updatesigs(&post_diff, db, version, zone_keys, nkeys, zone, inception, expire, 0, now, check_ksk, keyset_kskonly, &zonediff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:dns__zone_updatesigs -> %s", isc_result_totext(result)); goto cleanup; } } /* * Have we changed anything? */ if (ISC_LIST_EMPTY(zonediff.diff->tuples)) { if (zonediff.offline) { commit = true; } result = ISC_R_SUCCESS; goto pauseall; } commit = true; result = del_sigs(zone, db, version, &zone->origin, dns_rdatatype_soa, &zonediff, zone_keys, nkeys, now, false); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:del_sigs -> %s", isc_result_totext(result)); goto cleanup; } result = update_soa_serial(zone, db, version, zonediff.diff, zone->mctx, zone->updatemethod); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:update_soa_serial -> %s", isc_result_totext(result)); goto cleanup; } /* * Generate maximum life time signatures so that the above loop * termination is sensible. */ result = add_sigs(db, version, &zone->origin, zone, dns_rdatatype_soa, zonediff.diff, zone_keys, nkeys, zone->mctx, now, inception, soaexpire, check_ksk, keyset_kskonly); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:add_sigs -> %s", isc_result_totext(result)); goto cleanup; } /* * Write changes to journal file. */ CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_sign")); pauseall: /* * Pause all iterators so that dns_db_closeversion() can succeed. */ for (signing = ISC_LIST_HEAD(zone->signing); signing != NULL; signing = ISC_LIST_NEXT(signing, link)) { dns_dbiterator_pause(signing->dbiterator); } for (signing = ISC_LIST_HEAD(cleanup); signing != NULL; signing = ISC_LIST_NEXT(signing, link)) { dns_dbiterator_pause(signing->dbiterator); } /* * Everything has succeeded. Commit the changes. */ dns_db_closeversion(db, &version, commit); /* * Everything succeeded so we can clean these up now. */ signing = ISC_LIST_HEAD(cleanup); while (signing != NULL) { ISC_LIST_UNLINK(cleanup, signing, link); dns_db_detach(&signing->db); dns_dbiterator_destroy(&signing->dbiterator); isc_mem_put(zone->mctx, signing, sizeof *signing); signing = ISC_LIST_HEAD(cleanup); } LOCK_ZONE(zone); set_resigntime(zone); if (commit) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); zone_needdump(zone, DNS_DUMP_DELAY); } UNLOCK_ZONE(zone); failure: if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_sign: failed: %s", isc_result_totext(result)); } cleanup: /* * Pause all dbiterators. */ for (signing = ISC_LIST_HEAD(zone->signing); signing != NULL; signing = ISC_LIST_NEXT(signing, link)) { dns_dbiterator_pause(signing->dbiterator); } /* * Rollback the cleanup list. */ signing = ISC_LIST_HEAD(cleanup); while (signing != NULL) { ISC_LIST_UNLINK(cleanup, signing, link); ISC_LIST_PREPEND(zone->signing, signing, link); dns_dbiterator_first(signing->dbiterator); dns_dbiterator_pause(signing->dbiterator); signing = ISC_LIST_HEAD(cleanup); } dns_diff_clear(&_sig_diff); for (i = 0; i < nkeys; i++) { dst_key_free(&zone_keys[i]); } if (node != NULL) { dns_db_detachnode(db, &node); } if (version != NULL) { dns_db_closeversion(db, &version, false); dns_db_detach(&db); } else if (db != NULL) { dns_db_detach(&db); } LOCK_ZONE(zone); if (ISC_LIST_HEAD(zone->signing) != NULL) { isc_interval_t interval; if (zone->update_disabled || result != ISC_R_SUCCESS) { isc_interval_set(&interval, 60, 0); /* 1 minute */ } else { isc_interval_set(&interval, 0, 10000000); /* 10 ms */ } isc_time_nowplusinterval(&zone->signingtime, &interval); } else { isc_time_settoepoch(&zone->signingtime); } UNLOCK_ZONE(zone); INSIST(version == NULL); } static isc_result_t normalize_key(dns_rdata_t *rr, dns_rdata_t *target, unsigned char *data, int size) { dns_rdata_dnskey_t dnskey; dns_rdata_keydata_t keydata; isc_buffer_t buf; isc_result_t result; dns_rdata_reset(target); isc_buffer_init(&buf, data, size); switch (rr->type) { case dns_rdatatype_dnskey: result = dns_rdata_tostruct(rr, &dnskey, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dnskey.flags &= ~DNS_KEYFLAG_REVOKE; dns_rdata_fromstruct(target, rr->rdclass, dns_rdatatype_dnskey, &dnskey, &buf); break; case dns_rdatatype_keydata: result = dns_rdata_tostruct(rr, &keydata, NULL); if (result == ISC_R_UNEXPECTEDEND) { return (result); } RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_keydata_todnskey(&keydata, &dnskey, NULL); dns_rdata_fromstruct(target, rr->rdclass, dns_rdatatype_dnskey, &dnskey, &buf); break; default: UNREACHABLE(); } return (ISC_R_SUCCESS); } /* * 'rdset' contains either a DNSKEY rdataset from the zone apex, or * a KEYDATA rdataset from the key zone. * * 'rr' contains either a DNSKEY record, or a KEYDATA record * * After normalizing keys to the same format (DNSKEY, with revoke bit * cleared), return true if a key that matches 'rr' is found in * 'rdset', or false if not. */ static bool matchkey(dns_rdataset_t *rdset, dns_rdata_t *rr) { unsigned char data1[4096], data2[4096]; dns_rdata_t rdata, rdata1, rdata2; isc_result_t result; dns_rdata_init(&rdata); dns_rdata_init(&rdata1); dns_rdata_init(&rdata2); result = normalize_key(rr, &rdata1, data1, sizeof(data1)); if (result != ISC_R_SUCCESS) { return (false); } for (result = dns_rdataset_first(rdset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdset)) { dns_rdata_reset(&rdata); dns_rdataset_current(rdset, &rdata); result = normalize_key(&rdata, &rdata2, data2, sizeof(data2)); if (result != ISC_R_SUCCESS) { continue; } if (dns_rdata_compare(&rdata1, &rdata2) == 0) { return (true); } } return (false); } /* * Calculate the refresh interval for a keydata zone, per * RFC5011: MAX(1 hr, * MIN(15 days, * 1/2 * OrigTTL, * 1/2 * RRSigExpirationInterval)) * or for retries: MAX(1 hr, * MIN(1 day, * 1/10 * OrigTTL, * 1/10 * RRSigExpirationInterval)) */ static isc_stdtime_t refresh_time(dns_keyfetch_t *kfetch, bool retry) { isc_result_t result; uint32_t t; dns_rdataset_t *rdset; dns_rdata_t sigrr = DNS_RDATA_INIT; dns_rdata_sig_t sig; isc_stdtime_t now; isc_stdtime_get(&now); if (dns_rdataset_isassociated(&kfetch->dnskeysigset)) { rdset = &kfetch->dnskeysigset; } else { return (now + dns_zone_mkey_hour); } result = dns_rdataset_first(rdset); if (result != ISC_R_SUCCESS) { return (now + dns_zone_mkey_hour); } dns_rdataset_current(rdset, &sigrr); result = dns_rdata_tostruct(&sigrr, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (!retry) { t = sig.originalttl / 2; if (isc_serial_gt(sig.timeexpire, now)) { uint32_t exp = (sig.timeexpire - now) / 2; if (t > exp) { t = exp; } } if (t > (15 * dns_zone_mkey_day)) { t = (15 * dns_zone_mkey_day); } if (t < dns_zone_mkey_hour) { t = dns_zone_mkey_hour; } } else { t = sig.originalttl / 10; if (isc_serial_gt(sig.timeexpire, now)) { uint32_t exp = (sig.timeexpire - now) / 10; if (t > exp) { t = exp; } } if (t > dns_zone_mkey_day) { t = dns_zone_mkey_day; } if (t < dns_zone_mkey_hour) { t = dns_zone_mkey_hour; } } return (now + t); } /* * This routine is called when no changes are needed in a KEYDATA * record except to simply update the refresh timer. Caller should * hold zone lock. */ static isc_result_t minimal_update(dns_keyfetch_t *kfetch, dns_dbversion_t *ver, dns_diff_t *diff) { isc_result_t result; isc_buffer_t keyb; unsigned char key_buf[4096]; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_keydata_t keydata; dns_name_t *name; dns_zone_t *zone = kfetch->zone; isc_stdtime_t now; name = dns_fixedname_name(&kfetch->name); isc_stdtime_get(&now); for (result = dns_rdataset_first(&kfetch->keydataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&kfetch->keydataset)) { dns_rdata_reset(&rdata); dns_rdataset_current(&kfetch->keydataset, &rdata); /* Delete old version */ CHECK(update_one_rr(kfetch->db, ver, diff, DNS_DIFFOP_DEL, name, 0, &rdata)); /* Update refresh timer */ result = dns_rdata_tostruct(&rdata, &keydata, NULL); if (result == ISC_R_UNEXPECTEDEND) { continue; } if (result != ISC_R_SUCCESS) { goto failure; } keydata.refresh = refresh_time(kfetch, true); set_refreshkeytimer(zone, &keydata, now, false); dns_rdata_reset(&rdata); isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); CHECK(dns_rdata_fromstruct(&rdata, zone->rdclass, dns_rdatatype_keydata, &keydata, &keyb)); /* Insert updated version */ CHECK(update_one_rr(kfetch->db, ver, diff, DNS_DIFFOP_ADD, name, 0, &rdata)); } result = ISC_R_SUCCESS; failure: return (result); } /* * Verify that DNSKEY set is signed by the key specified in 'keydata'. */ static bool revocable(dns_keyfetch_t *kfetch, dns_rdata_keydata_t *keydata) { isc_result_t result; dns_name_t *keyname; isc_mem_t *mctx; dns_rdata_t sigrr = DNS_RDATA_INIT; dns_rdata_t rr = DNS_RDATA_INIT; dns_rdata_rrsig_t sig; dns_rdata_dnskey_t dnskey; dst_key_t *dstkey = NULL; unsigned char key_buf[4096]; isc_buffer_t keyb; bool answer = false; REQUIRE(kfetch != NULL && keydata != NULL); REQUIRE(dns_rdataset_isassociated(&kfetch->dnskeysigset)); keyname = dns_fixedname_name(&kfetch->name); mctx = kfetch->zone->view->mctx; /* Generate a key from keydata */ isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); dns_keydata_todnskey(keydata, &dnskey, NULL); dns_rdata_fromstruct(&rr, keydata->common.rdclass, dns_rdatatype_dnskey, &dnskey, &keyb); result = dns_dnssec_keyfromrdata(keyname, &rr, mctx, &dstkey); if (result != ISC_R_SUCCESS) { return (false); } /* See if that key generated any of the signatures */ for (result = dns_rdataset_first(&kfetch->dnskeysigset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&kfetch->dnskeysigset)) { dns_fixedname_t fixed; dns_fixedname_init(&fixed); dns_rdata_reset(&sigrr); dns_rdataset_current(&kfetch->dnskeysigset, &sigrr); result = dns_rdata_tostruct(&sigrr, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (dst_key_alg(dstkey) == sig.algorithm && dst_key_rid(dstkey) == sig.keyid) { result = dns_dnssec_verify( keyname, &kfetch->dnskeyset, dstkey, false, 0, mctx, &sigrr, dns_fixedname_name(&fixed)); dnssec_log(kfetch->zone, ISC_LOG_DEBUG(3), "Confirm revoked DNSKEY is self-signed: %s", isc_result_totext(result)); if (result == ISC_R_SUCCESS) { answer = true; break; } } } dst_key_free(&dstkey); return (answer); } /* * A DNSKEY set has been fetched from the zone apex of a zone whose trust * anchors are being managed; scan the keyset, and update the key zone and the * local trust anchors according to RFC5011. */ static void keyfetch_done(isc_task_t *task, isc_event_t *event) { isc_result_t result, eresult; dns_fetchevent_t *devent; dns_keyfetch_t *kfetch; dns_zone_t *zone; isc_mem_t *mctx = NULL; dns_keytable_t *secroots = NULL; dns_dbversion_t *ver = NULL; dns_diff_t diff; bool alldone = false; bool commit = false; dns_name_t *keyname = NULL; dns_rdata_t sigrr = DNS_RDATA_INIT; dns_rdata_t dnskeyrr = DNS_RDATA_INIT; dns_rdata_t keydatarr = DNS_RDATA_INIT; dns_rdata_rrsig_t sig; dns_rdata_dnskey_t dnskey; dns_rdata_keydata_t keydata; bool initializing; char namebuf[DNS_NAME_FORMATSIZE]; unsigned char key_buf[4096]; isc_buffer_t keyb; dst_key_t *dstkey = NULL; isc_stdtime_t now; int pending = 0; bool secure = false, initial = false; bool free_needed; dns_keynode_t *keynode = NULL; dns_rdataset_t *dnskeys = NULL, *dnskeysigs = NULL; dns_rdataset_t *keydataset = NULL, dsset; UNUSED(task); INSIST(event != NULL && event->ev_type == DNS_EVENT_FETCHDONE); INSIST(event->ev_arg != NULL); kfetch = event->ev_arg; zone = kfetch->zone; mctx = kfetch->mctx; keyname = dns_fixedname_name(&kfetch->name); dnskeys = &kfetch->dnskeyset; dnskeysigs = &kfetch->dnskeysigset; keydataset = &kfetch->keydataset; devent = (dns_fetchevent_t *)event; eresult = devent->result; /* 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(&kfetch->fetch); LOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || zone->view == NULL) { goto cleanup; } isc_stdtime_get(&now); dns_name_format(keyname, namebuf, sizeof(namebuf)); result = dns_view_getsecroots(zone->view, &secroots); INSIST(result == ISC_R_SUCCESS); dns_diff_init(mctx, &diff); CHECK(dns_db_newversion(kfetch->db, &ver)); zone->refreshkeycount--; alldone = (zone->refreshkeycount == 0); if (alldone) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESHING); } dnssec_log(zone, ISC_LOG_DEBUG(3), "Returned from key fetch in keyfetch_done() for '%s': %s", namebuf, isc_result_totext(eresult)); /* Fetch failed */ if (eresult != ISC_R_SUCCESS || !dns_rdataset_isassociated(dnskeys)) { dnssec_log(zone, ISC_LOG_WARNING, "Unable to fetch DNSKEY set '%s': %s", namebuf, isc_result_totext(eresult)); CHECK(minimal_update(kfetch, ver, &diff)); goto done; } /* No RRSIGs found */ if (!dns_rdataset_isassociated(dnskeysigs)) { dnssec_log(zone, ISC_LOG_WARNING, "No DNSKEY RRSIGs found for '%s': %s", namebuf, isc_result_totext(eresult)); CHECK(minimal_update(kfetch, ver, &diff)); goto done; } /* * Clear any cached trust level, as we need to run validation * over again; trusted keys might have changed. */ dnskeys->trust = dnskeysigs->trust = dns_trust_none; /* Look up the trust anchor */ result = dns_keytable_find(secroots, keyname, &keynode); if (result != ISC_R_SUCCESS) { goto anchors_done; } /* * If the keynode has a DS trust anchor, use it for verification. */ dns_rdataset_init(&dsset); if (dns_keynode_dsset(keynode, &dsset)) { for (result = dns_rdataset_first(dnskeysigs); result == ISC_R_SUCCESS; result = dns_rdataset_next(dnskeysigs)) { isc_result_t tresult; dns_rdata_t keyrdata = DNS_RDATA_INIT; dns_rdata_reset(&sigrr); dns_rdataset_current(dnskeysigs, &sigrr); result = dns_rdata_tostruct(&sigrr, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); for (tresult = dns_rdataset_first(&dsset); tresult == ISC_R_SUCCESS; tresult = dns_rdataset_next(&dsset)) { dns_rdata_t dsrdata = DNS_RDATA_INIT; dns_rdata_ds_t ds; dns_rdata_reset(&dsrdata); dns_rdataset_current(&dsset, &dsrdata); tresult = dns_rdata_tostruct(&dsrdata, &ds, NULL); RUNTIME_CHECK(tresult == ISC_R_SUCCESS); if (ds.key_tag != sig.keyid || ds.algorithm != sig.algorithm) { continue; } result = dns_dnssec_matchdskey( keyname, &dsrdata, dnskeys, &keyrdata); if (result == ISC_R_SUCCESS) { break; } } if (tresult == ISC_R_NOMORE) { continue; } result = dns_dnssec_keyfromrdata(keyname, &keyrdata, mctx, &dstkey); if (result != ISC_R_SUCCESS) { continue; } result = dns_dnssec_verify(keyname, dnskeys, dstkey, false, 0, mctx, &sigrr, NULL); dst_key_free(&dstkey); dnssec_log(zone, ISC_LOG_DEBUG(3), "Verifying DNSKEY set for zone " "'%s' using DS %d/%d: %s", namebuf, sig.keyid, sig.algorithm, isc_result_totext(result)); if (result == ISC_R_SUCCESS) { dnskeys->trust = dns_trust_secure; dnskeysigs->trust = dns_trust_secure; initial = dns_keynode_initial(keynode); dns_keynode_trust(keynode); secure = true; break; } } dns_rdataset_disassociate(&dsset); } anchors_done: if (keynode != NULL) { dns_keytable_detachkeynode(secroots, &keynode); } /* * If we were not able to verify the answer using the current * trusted keys then all we can do is look at any revoked keys. */ if (!secure) { dnssec_log(zone, ISC_LOG_INFO, "DNSKEY set for zone '%s' could not be verified " "with current keys", namebuf); } /* * First scan keydataset to find keys that are not in dnskeyset * - Missing keys which are not scheduled for removal, * log a warning * - Missing keys which are scheduled for removal and * the remove hold-down timer has completed should * be removed from the key zone * - Missing keys whose acceptance timers have not yet * completed, log a warning and reset the acceptance * timer to 30 days in the future * - All keys not being removed have their refresh timers * updated */ initializing = true; for (result = dns_rdataset_first(keydataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(keydataset)) { dns_keytag_t keytag; dns_rdata_reset(&keydatarr); dns_rdataset_current(keydataset, &keydatarr); result = dns_rdata_tostruct(&keydatarr, &keydata, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_keydata_todnskey(&keydata, &dnskey, NULL); result = compute_tag(keyname, &dnskey, mctx, &keytag); if (result != ISC_R_SUCCESS) { /* * Skip if we cannot compute the key tag. * This may happen if the algorithm is unsupported */ dns_zone_log(zone, ISC_LOG_ERROR, "Cannot compute tag for key in zone %s: " "%s " "(skipping)", namebuf, isc_result_totext(result)); continue; } RUNTIME_CHECK(result == ISC_R_SUCCESS); /* * If any keydata record has a nonzero add holddown, then * there was a pre-existing trust anchor for this domain; * that means we are *not* initializing it and shouldn't * automatically trust all the keys we find at the zone apex. */ initializing = initializing && (keydata.addhd == 0); if (!matchkey(dnskeys, &keydatarr)) { bool deletekey = false; if (!secure) { if (keydata.removehd != 0 && keydata.removehd <= now) { deletekey = true; } } else if (keydata.addhd == 0) { deletekey = true; } else if (keydata.addhd > now) { dnssec_log(zone, ISC_LOG_INFO, "Pending key %d for zone %s " "unexpectedly missing from DNSKEY " "RRset: restarting 30-day " "acceptance timer", keytag, namebuf); if (keydata.addhd < now + dns_zone_mkey_month) { keydata.addhd = now + dns_zone_mkey_month; } keydata.refresh = refresh_time(kfetch, false); } else if (keydata.removehd == 0) { dnssec_log(zone, ISC_LOG_INFO, "Active key %d for zone %s " "unexpectedly missing from DNSKEY " "RRset", keytag, namebuf); keydata.refresh = now + dns_zone_mkey_hour; } else if (keydata.removehd <= now) { deletekey = true; dnssec_log( zone, ISC_LOG_INFO, "Revoked key %d for zone %s no longer " "present in DNSKEY RRset: deleting " "from managed keys database", keytag, namebuf); } else { keydata.refresh = refresh_time(kfetch, false); } if (secure || deletekey) { /* Delete old version */ CHECK(update_one_rr(kfetch->db, ver, &diff, DNS_DIFFOP_DEL, keyname, 0, &keydatarr)); } if (!secure || deletekey) { continue; } dns_rdata_reset(&keydatarr); isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); dns_rdata_fromstruct(&keydatarr, zone->rdclass, dns_rdatatype_keydata, &keydata, &keyb); /* Insert updated version */ CHECK(update_one_rr(kfetch->db, ver, &diff, DNS_DIFFOP_ADD, keyname, 0, &keydatarr)); set_refreshkeytimer(zone, &keydata, now, false); } } /* * Next scan dnskeyset: * - If new keys are found (i.e., lacking a match in keydataset) * add them to the key zone and set the acceptance timer * to 30 days in the future (or to immediately if we've * determined that we're initializing the zone for the * first time) * - Previously-known keys that have been revoked * must be scheduled for removal from the key zone (or, * if they hadn't been accepted as trust anchors yet * anyway, removed at once) * - Previously-known unrevoked keys whose acceptance timers * have completed are promoted to trust anchors * - All keys not being removed have their refresh * timers updated */ for (result = dns_rdataset_first(dnskeys); result == ISC_R_SUCCESS; result = dns_rdataset_next(dnskeys)) { bool revoked = false; bool newkey = false; bool updatekey = false; bool deletekey = false; bool trustkey = false; dns_keytag_t keytag; dns_rdata_reset(&dnskeyrr); dns_rdataset_current(dnskeys, &dnskeyrr); result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); /* Skip ZSK's */ if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0) { continue; } result = compute_tag(keyname, &dnskey, mctx, &keytag); if (result != ISC_R_SUCCESS) { /* * Skip if we cannot compute the key tag. * This may happen if the algorithm is unsupported */ dns_zone_log(zone, ISC_LOG_ERROR, "Cannot compute tag for key in zone %s: " "%s " "(skipping)", namebuf, isc_result_totext(result)); continue; } RUNTIME_CHECK(result == ISC_R_SUCCESS); revoked = ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0); if (matchkey(keydataset, &dnskeyrr)) { dns_rdata_reset(&keydatarr); dns_rdataset_current(keydataset, &keydatarr); result = dns_rdata_tostruct(&keydatarr, &keydata, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (revoked && revocable(kfetch, &keydata)) { if (keydata.addhd > now) { /* * Key wasn't trusted yet, and now * it's been revoked? Just remove it */ deletekey = true; dnssec_log(zone, ISC_LOG_INFO, "Pending key %d for " "zone %s is now revoked: " "deleting from the " "managed keys database", keytag, namebuf); } else if (keydata.removehd == 0) { /* * Remove key from secroots. */ dns_view_untrust(zone->view, keyname, &dnskey); /* If initializing, delete now */ if (keydata.addhd == 0) { deletekey = true; } else { keydata.removehd = now + dns_zone_mkey_month; keydata.flags |= DNS_KEYFLAG_REVOKE; } dnssec_log(zone, ISC_LOG_INFO, "Trusted key %d for " "zone %s is now revoked", keytag, namebuf); } else if (keydata.removehd < now) { /* Scheduled for removal */ deletekey = true; dnssec_log(zone, ISC_LOG_INFO, "Revoked key %d for " "zone %s removal timer " "complete: deleting from " "the managed keys database", keytag, namebuf); } } else if (revoked && keydata.removehd == 0) { dnssec_log(zone, ISC_LOG_WARNING, "Active key %d for zone " "%s is revoked but " "did not self-sign; " "ignoring", keytag, namebuf); continue; } else if (secure) { if (keydata.removehd != 0) { /* * Key isn't revoked--but it * seems it used to be. * Remove it now and add it * back as if it were a fresh key, * with a 30-day acceptance timer. */ deletekey = true; newkey = true; keydata.removehd = 0; keydata.addhd = now + dns_zone_mkey_month; dnssec_log(zone, ISC_LOG_INFO, "Revoked key %d for " "zone %s has returned: " "starting 30-day " "acceptance timer", keytag, namebuf); } else if (keydata.addhd > now) { pending++; } else if (keydata.addhd == 0) { keydata.addhd = now; } if (keydata.addhd <= now) { trustkey = true; dnssec_log(zone, ISC_LOG_INFO, "Key %d for zone %s " "is now trusted (%s)", keytag, namebuf, initial ? "initializing key " "verified" : "acceptance timer " "complete"); } } else if (keydata.addhd > now) { /* * Not secure, and key is pending: * reset the acceptance timer */ pending++; keydata.addhd = now + dns_zone_mkey_month; dnssec_log(zone, ISC_LOG_INFO, "Pending key %d " "for zone %s was " "not validated: restarting " "30-day acceptance timer", keytag, namebuf); } if (!deletekey && !newkey) { updatekey = true; } } else if (secure) { /* * Key wasn't in the key zone but it's * revoked now anyway, so just skip it */ if (revoked) { continue; } /* Key wasn't in the key zone: add it */ newkey = true; if (initializing) { dnssec_log(zone, ISC_LOG_WARNING, "Initializing automatic trust " "anchor management for zone '%s'; " "DNSKEY ID %d is now trusted, " "waiving the normal 30-day " "waiting period.", namebuf, keytag); trustkey = true; } else { dnssec_log(zone, ISC_LOG_INFO, "New key %d observed " "for zone '%s': " "starting 30-day " "acceptance timer", keytag, namebuf); } } else { /* * No previously known key, and the key is not * secure, so skip it. */ continue; } /* Delete old version */ if (deletekey || !newkey) { CHECK(update_one_rr(kfetch->db, ver, &diff, DNS_DIFFOP_DEL, keyname, 0, &keydatarr)); } if (updatekey) { /* Set refresh timer */ keydata.refresh = refresh_time(kfetch, false); dns_rdata_reset(&keydatarr); isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); dns_rdata_fromstruct(&keydatarr, zone->rdclass, dns_rdatatype_keydata, &keydata, &keyb); /* Insert updated version */ CHECK(update_one_rr(kfetch->db, ver, &diff, DNS_DIFFOP_ADD, keyname, 0, &keydatarr)); } else if (newkey) { /* Convert DNSKEY to KEYDATA */ result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_keydata_fromdnskey(&keydata, &dnskey, 0, 0, 0, NULL); keydata.addhd = initializing ? now : now + dns_zone_mkey_month; keydata.refresh = refresh_time(kfetch, false); dns_rdata_reset(&keydatarr); isc_buffer_init(&keyb, key_buf, sizeof(key_buf)); dns_rdata_fromstruct(&keydatarr, zone->rdclass, dns_rdatatype_keydata, &keydata, &keyb); /* Insert into key zone */ CHECK(update_one_rr(kfetch->db, ver, &diff, DNS_DIFFOP_ADD, keyname, 0, &keydatarr)); } if (trustkey) { /* Trust this key. */ result = dns_rdata_tostruct(&dnskeyrr, &dnskey, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); trust_key(zone, keyname, &dnskey, false); } if (secure && !deletekey) { INSIST(newkey || updatekey); set_refreshkeytimer(zone, &keydata, now, false); } } /* * RFC5011 says, "A trust point that has all of its trust anchors * revoked is considered deleted and is treated as if the trust * point was never configured." But if someone revoked their * active key before the standby was trusted, that would mean the * zone would suddenly be nonsecured. We avoid this by checking to * see if there's pending keydata. If so, we put a null key in * the security roots; then all queries to the zone will fail. */ if (pending != 0) { fail_secure(zone, keyname); } done: if (!ISC_LIST_EMPTY(diff.tuples)) { /* Write changes to journal file. */ CHECK(update_soa_serial(zone, kfetch->db, ver, &diff, mctx, zone->updatemethod)); CHECK(zone_journal(zone, &diff, NULL, "keyfetch_done")); commit = true; DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); zone_needdump(zone, 30); } else if (result == ISC_R_NOMORE) { /* * If "updatekey" was true for all keys found in the DNSKEY * response and the previous update of those keys happened * during the same second (only possible if a key refresh was * externally triggered), it may happen that all relevant * update_one_rr() calls will return ISC_R_SUCCESS, but * diff.tuples will remain empty. Reset result to * ISC_R_SUCCESS to prevent a bogus warning from being logged. */ result = ISC_R_SUCCESS; } failure: if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "error during managed-keys processing (%s): " "DNSSEC validation may be at risk", isc_result_totext(result)); } dns_diff_clear(&diff); if (ver != NULL) { dns_db_closeversion(kfetch->db, &ver, commit); } cleanup: dns_db_detach(&kfetch->db); /* The zone must be managed */ INSIST(kfetch->zone->task != NULL); isc_refcount_decrement(&zone->irefs); if (dns_rdataset_isassociated(keydataset)) { dns_rdataset_disassociate(keydataset); } if (dns_rdataset_isassociated(dnskeys)) { dns_rdataset_disassociate(dnskeys); } if (dns_rdataset_isassociated(dnskeysigs)) { dns_rdataset_disassociate(dnskeysigs); } dns_name_free(keyname, mctx); isc_mem_putanddetach(&kfetch->mctx, kfetch, sizeof(dns_keyfetch_t)); if (secroots != NULL) { dns_keytable_detach(&secroots); } free_needed = exit_check(zone); UNLOCK_ZONE(zone); if (free_needed) { zone_free(zone); } INSIST(ver == NULL); } static void retry_keyfetch(dns_keyfetch_t *kfetch, dns_name_t *kname) { isc_time_t timenow, timethen; dns_zone_t *zone = kfetch->zone; bool free_needed; char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(kname, namebuf, sizeof(namebuf)); dnssec_log(zone, ISC_LOG_WARNING, "Failed to create fetch for %s DNSKEY update", namebuf); /* * Error during a key fetch; cancel and retry in an hour. */ LOCK_ZONE(zone); zone->refreshkeycount--; isc_refcount_decrement(&zone->irefs); dns_db_detach(&kfetch->db); dns_rdataset_disassociate(&kfetch->keydataset); dns_name_free(kname, zone->mctx); isc_mem_putanddetach(&kfetch->mctx, kfetch, sizeof(*kfetch)); if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { /* Don't really retry if we are exiting */ char timebuf[80]; TIME_NOW(&timenow); DNS_ZONE_TIME_ADD(&timenow, dns_zone_mkey_hour, &timethen); zone->refreshkeytime = timethen; zone_settimer(zone, &timenow); isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); dnssec_log(zone, ISC_LOG_DEBUG(1), "retry key refresh: %s", timebuf); } free_needed = exit_check(zone); UNLOCK_ZONE(zone); if (free_needed) { zone_free(zone); } } static void do_keyfetch(isc_task_t *task, isc_event_t *event) { isc_result_t result; dns_keyfetch_t *kfetch = (dns_keyfetch_t *)event->ev_arg; dns_name_t *kname = dns_fixedname_name(&kfetch->name); dns_zone_t *zone = kfetch->zone; UNUSED(task); isc_event_free(&event); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { retry_keyfetch(kfetch, kname); return; } /* * Use of DNS_FETCHOPT_NOCACHED is essential here. If it is not * set and the cache still holds a non-expired, validated version * of the RRset being queried for by the time the response is * received, the cached RRset will be passed to keyfetch_done() * instead of the one received in the response as the latter will * have a lower trust level due to not being validated until * keyfetch_done() is called. */ result = dns_resolver_createfetch( zone->view->resolver, kname, dns_rdatatype_dnskey, NULL, NULL, NULL, NULL, 0, DNS_FETCHOPT_NOVALIDATE | DNS_FETCHOPT_UNSHARED | DNS_FETCHOPT_NOCACHED, 0, NULL, zone->task, keyfetch_done, kfetch, &kfetch->dnskeyset, &kfetch->dnskeysigset, &kfetch->fetch); if (result != ISC_R_SUCCESS) { retry_keyfetch(kfetch, kname); } } /* * Refresh the data in the key zone. Initiate a fetch to look up * DNSKEY records at the trust anchor name. */ static void zone_refreshkeys(dns_zone_t *zone) { const char me[] = "zone_refreshkeys"; isc_result_t result; dns_rriterator_t rrit; dns_db_t *db = NULL; dns_dbversion_t *ver = NULL; dns_diff_t diff; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_keydata_t kd; isc_stdtime_t now; bool commit = false; bool fetching = false; bool timerset = false; ENTER; REQUIRE(zone->db != NULL); isc_stdtime_get(&now); LOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { isc_time_settoepoch(&zone->refreshkeytime); UNLOCK_ZONE(zone); return; } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); dns_db_attach(zone->db, &db); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); dns_diff_init(zone->mctx, &diff); CHECK(dns_db_newversion(db, &ver)); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESHING); dns_rriterator_init(&rrit, db, ver, 0); for (result = dns_rriterator_first(&rrit); result == ISC_R_SUCCESS; result = dns_rriterator_nextrrset(&rrit)) { isc_stdtime_t timer = 0xffffffff; dns_name_t *name = NULL, *kname = NULL; dns_rdataset_t *kdset = NULL; uint32_t ttl; dns_rriterator_current(&rrit, &name, &ttl, &kdset, NULL); if (kdset == NULL || kdset->type != dns_rdatatype_keydata || !dns_rdataset_isassociated(kdset)) { continue; } /* * Scan the stored keys looking for ones that need * removal or refreshing */ for (result = dns_rdataset_first(kdset); result == ISC_R_SUCCESS; result = dns_rdataset_next(kdset)) { dns_rdata_reset(&rdata); dns_rdataset_current(kdset, &rdata); result = dns_rdata_tostruct(&rdata, &kd, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); /* Removal timer expired? */ if (kd.removehd != 0 && kd.removehd < now) { dns_rriterator_pause(&rrit); CHECK(update_one_rr(db, ver, &diff, DNS_DIFFOP_DEL, name, ttl, &rdata)); continue; } /* Acceptance timer expired? */ if (kd.addhd <= now) { timer = kd.addhd; } /* Or do we just need to refresh the keyset? */ if (timer > kd.refresh) { timer = kd.refresh; } dns_rriterator_pause(&rrit); set_refreshkeytimer(zone, &kd, now, false); timerset = true; } if (timer > now) { continue; } dns_rriterator_pause(&rrit); #ifdef ENABLE_AFL if (!dns_fuzzing_resolver) { #endif /* ifdef ENABLE_AFL */ dns_keyfetch_t *kfetch = NULL; isc_event_t *e; kfetch = isc_mem_get(zone->mctx, sizeof(dns_keyfetch_t)); *kfetch = (dns_keyfetch_t){ .zone = zone }; isc_mem_attach(zone->mctx, &kfetch->mctx); zone->refreshkeycount++; isc_refcount_increment0(&zone->irefs); kname = dns_fixedname_initname(&kfetch->name); dns_name_dup(name, zone->mctx, kname); dns_rdataset_init(&kfetch->dnskeyset); dns_rdataset_init(&kfetch->dnskeysigset); dns_rdataset_init(&kfetch->keydataset); dns_rdataset_clone(kdset, &kfetch->keydataset); dns_db_attach(db, &kfetch->db); if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(kname, namebuf, sizeof(namebuf)); dnssec_log(zone, ISC_LOG_DEBUG(3), "Creating key fetch in " "zone_refreshkeys() for '%s'", namebuf); } e = isc_event_allocate(zone->mctx, NULL, DNS_EVENT_ZONE, do_keyfetch, kfetch, sizeof(isc_event_t)); isc_task_send(zone->task, &e); fetching = true; #ifdef ENABLE_AFL } #endif /* ifdef ENABLE_AFL */ } if (!ISC_LIST_EMPTY(diff.tuples)) { CHECK(update_soa_serial(zone, db, ver, &diff, zone->mctx, zone->updatemethod)); CHECK(zone_journal(zone, &diff, NULL, "zone_refreshkeys")); commit = true; DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); zone_needdump(zone, 30); } failure: if (!timerset) { isc_time_settoepoch(&zone->refreshkeytime); } if (!fetching) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESHING); } dns_diff_clear(&diff); if (ver != NULL) { dns_rriterator_destroy(&rrit); dns_db_closeversion(db, &ver, commit); } dns_db_detach(&db); UNLOCK_ZONE(zone); INSIST(ver == NULL); } static void zone_maintenance(dns_zone_t *zone) { const char me[] = "zone_maintenance"; isc_time_t now; isc_result_t result; bool load_pending, exiting, dumping, viewok, notify; bool refreshkeys, sign, resign, rekey, chain, warn_expire; REQUIRE(DNS_ZONE_VALID(zone)); ENTER; /* * Are we pending load/reload, exiting, or unconfigured * (e.g. because of a syntax failure in the config file)? * If so, don't attempt maintenance. */ LOCK_ZONE(zone); load_pending = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING); exiting = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING); viewok = (zone->view != NULL && zone->view->adb != NULL); UNLOCK_ZONE(zone); if (load_pending || exiting || !viewok) { return; } TIME_NOW(&now); /* * Expire check. */ switch (zone->type) { case dns_zone_redirect: if (zone->primaries == NULL) { break; } FALLTHROUGH; case dns_zone_secondary: case dns_zone_mirror: case dns_zone_stub: LOCK_ZONE(zone); if (isc_time_compare(&now, &zone->expiretime) >= 0 && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) { zone_expire(zone); zone->refreshtime = now; } UNLOCK_ZONE(zone); break; default: break; } /* * Up to date check. */ switch (zone->type) { case dns_zone_redirect: if (zone->primaries == NULL) { break; } FALLTHROUGH; case dns_zone_secondary: case dns_zone_mirror: case dns_zone_stub: LOCK_ZONE(zone); if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH) && isc_time_compare(&now, &zone->refreshtime) >= 0) { zone_refresh(zone); } UNLOCK_ZONE(zone); break; default: break; } /* * Secondaries send notifies before backing up to disk, * primaries after. */ LOCK_ZONE(zone); notify = (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror) && (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) || DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) && isc_time_compare(&now, &zone->notifytime) >= 0; UNLOCK_ZONE(zone); if (notify) { zone_notify(zone, &now); } /* * Do we need to consolidate the backing store? */ switch (zone->type) { case dns_zone_primary: case dns_zone_secondary: case dns_zone_mirror: case dns_zone_key: case dns_zone_redirect: case dns_zone_stub: LOCK_ZONE(zone); if (zone->masterfile != NULL && isc_time_compare(&now, &zone->dumptime) >= 0 && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP)) { dumping = was_dumping(zone); } else { dumping = true; } UNLOCK_ZONE(zone); if (!dumping) { result = zone_dump(zone, true); /* task locked */ if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_WARNING, "dump failed: %s", isc_result_totext(result)); } } break; default: break; } /* * Primary/redirect zones send notifies now, if needed */ switch (zone->type) { case dns_zone_primary: case dns_zone_redirect: LOCK_ZONE(zone); notify = (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) || DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) && isc_time_compare(&now, &zone->notifytime) >= 0; UNLOCK_ZONE(zone); if (notify) { zone_notify(zone, &now); } default: break; } /* * Do we need to refresh keys? */ switch (zone->type) { case dns_zone_key: LOCK_ZONE(zone); refreshkeys = isc_time_compare(&now, &zone->refreshkeytime) >= 0 && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING); UNLOCK_ZONE(zone); if (refreshkeys) { zone_refreshkeys(zone); } break; case dns_zone_primary: LOCK_ZONE(zone); rekey = (!isc_time_isepoch(&zone->refreshkeytime) && isc_time_compare(&now, &zone->refreshkeytime) >= 0 && zone->rss_event == NULL); UNLOCK_ZONE(zone); if (rekey) { zone_rekey(zone); } default: break; } switch (zone->type) { case dns_zone_primary: case dns_zone_redirect: case dns_zone_secondary: /* * Do we need to sign/resign some RRsets? */ LOCK_ZONE(zone); if (zone->rss_event != NULL) { UNLOCK_ZONE(zone); break; } sign = !isc_time_isepoch(&zone->signingtime) && isc_time_compare(&now, &zone->signingtime) >= 0; resign = !isc_time_isepoch(&zone->resigntime) && isc_time_compare(&now, &zone->resigntime) >= 0; chain = !isc_time_isepoch(&zone->nsec3chaintime) && isc_time_compare(&now, &zone->nsec3chaintime) >= 0; warn_expire = !isc_time_isepoch(&zone->keywarntime) && isc_time_compare(&now, &zone->keywarntime) >= 0; UNLOCK_ZONE(zone); if (sign) { zone_sign(zone); } else if (resign) { zone_resigninc(zone); } else if (chain) { zone_nsec3chain(zone); } /* * Do we need to issue a key expiry warning? */ if (warn_expire) { set_key_expiry_warning(zone, zone->key_expiry, isc_time_seconds(&now)); } break; default: break; } LOCK_ZONE(zone); zone_settimer(zone, &now); UNLOCK_ZONE(zone); } void dns_zone_markdirty(dns_zone_t *zone) { uint32_t serial; isc_result_t result = ISC_R_SUCCESS; dns_zone_t *secure = NULL; /* * Obtaining a lock on the zone->secure (see zone_send_secureserial) * could result in a deadlock due to a LOR so we will spin if we * can't obtain the both locks. */ again: LOCK_ZONE(zone); if (zone->type == dns_zone_primary) { if (inline_raw(zone)) { unsigned int soacount; secure = zone->secure; INSIST(secure != zone); TRYLOCK_ZONE(result, secure); if (result != ISC_R_SUCCESS) { UNLOCK_ZONE(zone); secure = NULL; isc_thread_yield(); goto again; } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { result = zone_get_from_db( zone, zone->db, NULL, &soacount, NULL, &serial, NULL, NULL, NULL, NULL, NULL); } else { result = DNS_R_NOTLOADED; } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (result == ISC_R_SUCCESS && soacount > 0U) { zone_send_secureserial(zone, serial); } } /* XXXMPA make separate call back */ if (result == ISC_R_SUCCESS) { set_resigntime(zone); if (zone->task != NULL) { isc_time_t now; TIME_NOW(&now); zone_settimer(zone, &now); } } } if (secure != NULL) { UNLOCK_ZONE(secure); } zone_needdump(zone, DNS_DUMP_DELAY); UNLOCK_ZONE(zone); } void dns_zone_expire(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone_expire(zone); UNLOCK_ZONE(zone); } static void zone_expire(dns_zone_t *zone) { dns_db_t *db = NULL; /* * 'zone' locked by caller. */ REQUIRE(LOCKED_ZONE(zone)); dns_zone_log(zone, ISC_LOG_WARNING, "expired"); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_EXPIRED); zone->refresh = DNS_ZONE_DEFAULTREFRESH; zone->retry = DNS_ZONE_DEFAULTRETRY; DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS); /* * An RPZ zone has expired; before unloading it, we must * first remove it from the RPZ summary database. The * easiest way to do this is "update" it with an empty * database so that the update callback synchronizes * the diff automatically. */ if (zone->rpzs != NULL && zone->rpz_num != DNS_RPZ_INVALID_NUM) { isc_result_t result; dns_rpz_zone_t *rpz = zone->rpzs->zones[zone->rpz_num]; CHECK(dns_db_create(zone->mctx, "rbt", &zone->origin, dns_dbtype_zone, zone->rdclass, 0, NULL, &db)); CHECK(dns_rpz_dbupdate_callback(db, rpz)); dns_zone_log(zone, ISC_LOG_WARNING, "response-policy zone expired; " "policies unloaded"); } failure: if (db != NULL) { dns_db_detach(&db); } zone_unload(zone); } static void zone_refresh(dns_zone_t *zone) { isc_interval_t i; uint32_t oldflags; unsigned int j; isc_result_t result; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(LOCKED_ZONE(zone)); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { return; } /* * Set DNS_ZONEFLG_REFRESH so that there is only one refresh operation * in progress at a time. */ oldflags = atomic_load(&zone->flags); if (zone->primariescnt == 0) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOPRIMARIES); if ((oldflags & DNS_ZONEFLG_NOPRIMARIES) == 0) { dns_zone_log(zone, ISC_LOG_ERROR, "cannot refresh: no primaries"); } return; } DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); if ((oldflags & (DNS_ZONEFLG_REFRESH | DNS_ZONEFLG_LOADING)) != 0) { return; } /* * Set the next refresh time as if refresh check has failed. * Setting this to the retry time will do that. XXXMLG * If we are successful it will be reset using zone->refresh. */ isc_interval_set(&i, zone->retry - isc_random_uniform(zone->retry / 4), 0); result = isc_time_nowplusinterval(&zone->refreshtime, &i); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_WARNING, "isc_time_nowplusinterval() failed: %s", isc_result_totext(result)); } /* * When lacking user-specified timer values from the SOA, * do exponential backoff of the retry time up to a * maximum of six hours. */ if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS)) { zone->retry = ISC_MIN(zone->retry * 2, 6 * 3600); } zone->curprimary = 0; for (j = 0; j < zone->primariescnt; j++) { zone->primariesok[j] = false; } /* initiate soa query */ queue_soa_query(zone); } void dns_zone_refresh(dns_zone_t *zone) { LOCK_ZONE(zone); zone_refresh(zone); UNLOCK_ZONE(zone); } static isc_result_t zone_journal_rollforward(dns_zone_t *zone, dns_db_t *db, bool *needdump, bool *fixjournal) { dns_journal_t *journal = NULL; unsigned int options; isc_result_t result; if (zone->type == dns_zone_primary && (inline_secure(zone) || (zone->update_acl != NULL || zone->ssutable != NULL))) { options = DNS_JOURNALOPT_RESIGN; } else { options = 0; } result = dns_journal_open(zone->mctx, zone->journal, DNS_JOURNAL_READ, &journal); if (result == ISC_R_NOTFOUND) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(3), "no journal file, but that's OK "); return (ISC_R_SUCCESS); } else if (result != ISC_R_SUCCESS) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "journal open failed: %s", isc_result_totext(result)); return (result); } if (dns_journal_empty(journal)) { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "journal empty"); dns_journal_destroy(&journal); return (ISC_R_SUCCESS); } result = dns_journal_rollforward(journal, db, options); switch (result) { case ISC_R_SUCCESS: *needdump = true; FALLTHROUGH; case DNS_R_UPTODATE: if (dns_journal_recovered(journal)) { *fixjournal = true; dns_zone_logc( zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "journal rollforward completed successfully " "using old journal format: %s", isc_result_totext(result)); } else { dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "journal rollforward completed " "successfully: %s", isc_result_totext(result)); } dns_journal_destroy(&journal); return (ISC_R_SUCCESS); case ISC_R_NOTFOUND: case ISC_R_RANGE: dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "journal rollforward failed: journal out of sync " "with zone"); dns_journal_destroy(&journal); return (result); default: dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_ERROR, "journal rollforward failed: %s", isc_result_totext(result)); dns_journal_destroy(&journal); return (result); } } static void zone_journal_compact(dns_zone_t *zone, dns_db_t *db, uint32_t serial) { isc_result_t result; int32_t journalsize; dns_dbversion_t *ver = NULL; uint64_t dbsize; uint32_t options = 0; INSIST(LOCKED_ZONE(zone)); if (inline_raw(zone)) { INSIST(LOCKED_ZONE(zone->secure)); } journalsize = zone->journalsize; if (journalsize == -1) { journalsize = DNS_JOURNAL_SIZE_MAX; dns_db_currentversion(db, &ver); result = dns_db_getsize(db, ver, NULL, &dbsize); dns_db_closeversion(db, &ver, false); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "zone_journal_compact: " "could not get zone size: %s", isc_result_totext(result)); } else if (dbsize < DNS_JOURNAL_SIZE_MAX / 2) { journalsize = (int32_t)dbsize * 2; } } if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FIXJOURNAL)) { options |= DNS_JOURNAL_COMPACTALL; DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FIXJOURNAL); zone_debuglog(zone, "zone_journal_compact", 1, "repair full journal"); } else { zone_debuglog(zone, "zone_journal_compact", 1, "target journal size %d", journalsize); } result = dns_journal_compact(zone->mctx, zone->journal, serial, options, journalsize); switch (result) { case ISC_R_SUCCESS: case ISC_R_NOSPACE: case ISC_R_NOTFOUND: dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_journal_compact: %s", isc_result_totext(result)); break; default: dns_zone_log(zone, ISC_LOG_ERROR, "dns_journal_compact failed: %s", isc_result_totext(result)); break; } } isc_result_t dns_zone_flush(dns_zone_t *zone) { isc_result_t result = ISC_R_SUCCESS; bool dumping; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FLUSH); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && zone->masterfile != NULL) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDCOMPACT); result = ISC_R_ALREADYRUNNING; dumping = was_dumping(zone); } else { dumping = true; } UNLOCK_ZONE(zone); if (!dumping) { result = zone_dump(zone, true); /* Unknown task. */ } return (result); } isc_result_t dns_zone_dump(dns_zone_t *zone) { isc_result_t result = ISC_R_ALREADYRUNNING; bool dumping; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); dumping = was_dumping(zone); UNLOCK_ZONE(zone); if (!dumping) { result = zone_dump(zone, false); /* Unknown task. */ } return (result); } static void zone_needdump(dns_zone_t *zone, unsigned int delay) { const char me[] = "zone_needdump"; isc_time_t dumptime; isc_time_t now; /* * 'zone' locked by caller */ REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(LOCKED_ZONE(zone)); ENTER; /* * Do we have a place to dump to and are we loaded? */ if (zone->masterfile == NULL || DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) == 0) { return; } TIME_NOW(&now); /* add some noise */ DNS_ZONE_JITTER_ADD(&now, delay, &dumptime); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDDUMP); if (isc_time_isepoch(&zone->dumptime) || isc_time_compare(&zone->dumptime, &dumptime) > 0) { zone->dumptime = dumptime; } if (zone->task != NULL) { zone_settimer(zone, &now); } } static void dump_done(void *arg, isc_result_t result) { const char me[] = "dump_done"; dns_zone_t *zone = arg; dns_zone_t *secure = NULL; dns_db_t *db; dns_dbversion_t *version; bool again = false; bool compact = false; uint32_t serial; isc_result_t tresult; REQUIRE(DNS_ZONE_VALID(zone)); ENTER; if (result == ISC_R_SUCCESS && zone->journal != NULL) { /* * We don't own these, zone->dctx must stay valid. */ db = dns_dumpctx_db(zone->dctx); version = dns_dumpctx_version(zone->dctx); tresult = dns_db_getsoaserial(db, version, &serial); /* * Handle lock order inversion. */ again: LOCK_ZONE(zone); if (inline_raw(zone)) { secure = zone->secure; INSIST(secure != zone); TRYLOCK_ZONE(result, secure); if (result != ISC_R_SUCCESS) { UNLOCK_ZONE(zone); secure = NULL; isc_thread_yield(); goto again; } } /* * If there is a secure version of this zone * use its serial if it is less than ours. */ if (tresult == ISC_R_SUCCESS && secure != NULL) { uint32_t sserial; isc_result_t mresult; ZONEDB_LOCK(&secure->dblock, isc_rwlocktype_read); if (secure->db != NULL) { mresult = dns_db_getsoaserial(zone->secure->db, NULL, &sserial); if (mresult == ISC_R_SUCCESS && isc_serial_lt(sserial, serial)) { serial = sserial; } } ZONEDB_UNLOCK(&secure->dblock, isc_rwlocktype_read); } if (tresult == ISC_R_SUCCESS && zone->xfr == NULL) { dns_db_t *zdb = NULL; if (dns_zone_getdb(zone, &zdb) == ISC_R_SUCCESS) { zone_journal_compact(zone, zdb, serial); dns_db_detach(&zdb); } } else if (tresult == ISC_R_SUCCESS) { compact = true; zone->compact_serial = serial; } if (secure != NULL) { UNLOCK_ZONE(secure); } UNLOCK_ZONE(zone); } LOCK_ZONE(zone); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_DUMPING); if (compact) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDCOMPACT); } if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SHUTDOWN)) { /* * If DNS_ZONEFLG_SHUTDOWN is set, all external references to * the zone are gone, which means it is in the process of being * cleaned up, so do not reschedule dumping. * * Detach from the raw version of the zone in case this * operation has been deferred in zone_shutdown(). */ if (zone->raw != NULL) { dns_zone_detach(&zone->raw); } if (result == ISC_R_SUCCESS) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FLUSH); } } else if (result != ISC_R_SUCCESS && result != ISC_R_CANCELED) { /* * Try again in a short while. */ zone_needdump(zone, DNS_DUMP_DELAY); } else if (result == ISC_R_SUCCESS && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING); isc_time_settoepoch(&zone->dumptime); again = true; } else if (result == ISC_R_SUCCESS) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FLUSH); } if (zone->dctx != NULL) { dns_dumpctx_detach(&zone->dctx); } zonemgr_putio(&zone->writeio); UNLOCK_ZONE(zone); if (again) { (void)zone_dump(zone, false); } dns_zone_idetach(&zone); } static isc_result_t zone_dump(dns_zone_t *zone, bool compact) { const char me[] = "zone_dump"; isc_result_t result; dns_dbversion_t *version = NULL; bool again; dns_db_t *db = NULL; char *masterfile = NULL; dns_masterformat_t masterformat = dns_masterformat_none; /* * 'compact' MUST only be set if we are task locked. */ REQUIRE(DNS_ZONE_VALID(zone)); ENTER; redo: ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); LOCK_ZONE(zone); if (zone->masterfile != NULL) { masterfile = isc_mem_strdup(zone->mctx, zone->masterfile); masterformat = zone->masterformat; } UNLOCK_ZONE(zone); if (db == NULL) { result = DNS_R_NOTLOADED; goto fail; } if (masterfile == NULL) { result = DNS_R_NOMASTERFILE; goto fail; } if (compact && zone->type != dns_zone_stub) { dns_zone_t *dummy = NULL; LOCK_ZONE(zone); zone_iattach(zone, &dummy); result = zonemgr_getio(zone->zmgr, false, zone->task, zone_gotwritehandle, zone, &zone->writeio); if (result != ISC_R_SUCCESS) { zone_idetach(&dummy); } else { result = DNS_R_CONTINUE; } UNLOCK_ZONE(zone); } else { const dns_master_style_t *output_style; dns_masterrawheader_t rawdata; dns_db_currentversion(db, &version); dns_master_initrawheader(&rawdata); if (inline_secure(zone)) { get_raw_serial(zone->raw, &rawdata); } if (zone->type == dns_zone_key) { output_style = &dns_master_style_keyzone; } else { output_style = &dns_master_style_default; } result = dns_master_dump(zone->mctx, db, version, output_style, masterfile, masterformat, &rawdata); dns_db_closeversion(db, &version, false); } fail: if (db != NULL) { dns_db_detach(&db); } if (masterfile != NULL) { isc_mem_free(zone->mctx, masterfile); } masterfile = NULL; if (result == DNS_R_CONTINUE) { return (ISC_R_SUCCESS); /* XXXMPA */ } again = false; LOCK_ZONE(zone); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_DUMPING); if (result != ISC_R_SUCCESS) { /* * Try again in a short while. */ zone_needdump(zone, DNS_DUMP_DELAY); } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING); isc_time_settoepoch(&zone->dumptime); again = true; } else { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FLUSH); } UNLOCK_ZONE(zone); if (again) { goto redo; } return (result); } static isc_result_t dumptostream(dns_zone_t *zone, FILE *fd, const dns_master_style_t *style, dns_masterformat_t format, const uint32_t rawversion) { isc_result_t result; dns_dbversion_t *version = NULL; dns_db_t *db = NULL; dns_masterrawheader_t rawdata; REQUIRE(DNS_ZONE_VALID(zone)); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { return (DNS_R_NOTLOADED); } dns_db_currentversion(db, &version); dns_master_initrawheader(&rawdata); if (rawversion == 0) { rawdata.flags |= DNS_MASTERRAW_COMPAT; } else if (inline_secure(zone)) { get_raw_serial(zone->raw, &rawdata); } else if (zone->sourceserialset) { rawdata.flags = DNS_MASTERRAW_SOURCESERIALSET; rawdata.sourceserial = zone->sourceserial; } result = dns_master_dumptostream(zone->mctx, db, version, style, format, &rawdata, fd); dns_db_closeversion(db, &version, false); dns_db_detach(&db); return (result); } isc_result_t dns_zone_dumptostream(dns_zone_t *zone, FILE *fd, dns_masterformat_t format, const dns_master_style_t *style, const uint32_t rawversion) { return (dumptostream(zone, fd, style, format, rawversion)); } void dns_zone_unload(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone_unload(zone); UNLOCK_ZONE(zone); } static void notify_cancel(dns_zone_t *zone) { dns_notify_t *notify; /* * 'zone' locked by caller. */ REQUIRE(LOCKED_ZONE(zone)); for (notify = ISC_LIST_HEAD(zone->notifies); notify != NULL; notify = ISC_LIST_NEXT(notify, link)) { if (notify->find != NULL) { dns_adb_cancelfind(notify->find); } if (notify->request != NULL) { dns_request_cancel(notify->request); } } } static void checkds_cancel(dns_zone_t *zone) { dns_checkds_t *checkds; /* * 'zone' locked by caller. */ REQUIRE(LOCKED_ZONE(zone)); for (checkds = ISC_LIST_HEAD(zone->checkds_requests); checkds != NULL; checkds = ISC_LIST_NEXT(checkds, link)) { if (checkds->request != NULL) { dns_request_cancel(checkds->request); } } } static void forward_cancel(dns_zone_t *zone) { dns_forward_t *forward; /* * 'zone' locked by caller. */ REQUIRE(LOCKED_ZONE(zone)); for (forward = ISC_LIST_HEAD(zone->forwards); forward != NULL; forward = ISC_LIST_NEXT(forward, link)) { if (forward->request != NULL) { dns_request_cancel(forward->request); } } } static void zone_unload(dns_zone_t *zone) { /* * 'zone' locked by caller. */ REQUIRE(LOCKED_ZONE(zone)); if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) || !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { if (zone->writeio != NULL) { zonemgr_cancelio(zone->writeio); } if (zone->dctx != NULL) { dns_dumpctx_cancel(zone->dctx); } } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); zone_detachdb(zone); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADED); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP); if (zone->type == dns_zone_mirror) { dns_zone_log(zone, ISC_LOG_INFO, "mirror zone is no longer in use; " "reverting to normal recursion"); } } void dns_zone_setminrefreshtime(dns_zone_t *zone, uint32_t val) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(val > 0); zone->minrefresh = val; } void dns_zone_setmaxrefreshtime(dns_zone_t *zone, uint32_t val) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(val > 0); zone->maxrefresh = val; } void dns_zone_setminretrytime(dns_zone_t *zone, uint32_t val) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(val > 0); zone->minretry = val; } void dns_zone_setmaxretrytime(dns_zone_t *zone, uint32_t val) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(val > 0); zone->maxretry = val; } uint32_t dns_zone_getmaxrecords(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->maxrecords); } void dns_zone_setmaxrecords(dns_zone_t *zone, uint32_t val) { REQUIRE(DNS_ZONE_VALID(zone)); zone->maxrecords = val; } static bool notify_isqueued(dns_zone_t *zone, unsigned int flags, dns_name_t *name, isc_sockaddr_t *addr, dns_tsigkey_t *key, dns_transport_t *transport) { dns_notify_t *notify; dns_zonemgr_t *zmgr; isc_result_t result; for (notify = ISC_LIST_HEAD(zone->notifies); notify != NULL; notify = ISC_LIST_NEXT(notify, link)) { if (notify->request != NULL) { continue; } if (name != NULL && dns_name_dynamic(¬ify->ns) && dns_name_equal(name, ¬ify->ns)) { goto requeue; } if (addr != NULL && isc_sockaddr_equal(addr, ¬ify->dst) && notify->key == key && notify->transport == transport) { goto requeue; } } return (false); requeue: /* * If we are enqueued on the startup ratelimiter and this is * not a startup notify, re-enqueue on the normal notify * ratelimiter. */ if (notify->event != NULL && (flags & DNS_NOTIFY_STARTUP) == 0 && (notify->flags & DNS_NOTIFY_STARTUP) != 0) { zmgr = notify->zone->zmgr; result = isc_ratelimiter_dequeue(zmgr->startupnotifyrl, notify->event); if (result != ISC_R_SUCCESS) { return (true); } notify->flags &= ~DNS_NOTIFY_STARTUP; result = isc_ratelimiter_enqueue(notify->zone->zmgr->notifyrl, notify->zone->task, ¬ify->event); if (result != ISC_R_SUCCESS) { isc_event_free(¬ify->event); return (false); } } return (true); } static bool notify_isself(dns_zone_t *zone, isc_sockaddr_t *dst) { dns_tsigkey_t *key = NULL; isc_sockaddr_t src; isc_sockaddr_t any; bool isself; isc_netaddr_t dstaddr; isc_result_t result; if (zone->view == NULL || zone->isself == NULL) { return (false); } switch (isc_sockaddr_pf(dst)) { case PF_INET: src = zone->notifysrc4; isc_sockaddr_any(&any); break; case PF_INET6: src = zone->notifysrc6; isc_sockaddr_any6(&any); break; default: return (false); } /* * When sending from any the kernel will assign a source address * that matches the destination address. */ if (isc_sockaddr_eqaddr(&any, &src)) { src = *dst; } isc_netaddr_fromsockaddr(&dstaddr, dst); result = dns_view_getpeertsig(zone->view, &dstaddr, &key); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { return (false); } isself = (zone->isself)(zone->view, key, &src, dst, zone->rdclass, zone->isselfarg); if (key != NULL) { dns_tsigkey_detach(&key); } return (isself); } static void notify_destroy(dns_notify_t *notify, bool locked) { isc_mem_t *mctx; REQUIRE(DNS_NOTIFY_VALID(notify)); if (notify->zone != NULL) { if (!locked) { LOCK_ZONE(notify->zone); } REQUIRE(LOCKED_ZONE(notify->zone)); if (ISC_LINK_LINKED(notify, link)) { ISC_LIST_UNLINK(notify->zone->notifies, notify, link); } if (!locked) { UNLOCK_ZONE(notify->zone); } if (locked) { zone_idetach(¬ify->zone); } else { dns_zone_idetach(¬ify->zone); } } if (notify->find != NULL) { dns_adb_destroyfind(¬ify->find); } if (notify->request != NULL) { dns_request_destroy(¬ify->request); } if (dns_name_dynamic(¬ify->ns)) { dns_name_free(¬ify->ns, notify->mctx); } if (notify->key != NULL) { dns_tsigkey_detach(¬ify->key); } if (notify->transport != NULL) { dns_transport_detach(¬ify->transport); } mctx = notify->mctx; isc_mem_put(notify->mctx, notify, sizeof(*notify)); isc_mem_detach(&mctx); } static isc_result_t notify_create(isc_mem_t *mctx, unsigned int flags, dns_notify_t **notifyp) { dns_notify_t *notify; REQUIRE(notifyp != NULL && *notifyp == NULL); notify = isc_mem_get(mctx, sizeof(*notify)); *notify = (dns_notify_t){ .flags = flags, }; isc_mem_attach(mctx, ¬ify->mctx); isc_sockaddr_any(¬ify->dst); dns_name_init(¬ify->ns, NULL); ISC_LINK_INIT(notify, link); notify->magic = NOTIFY_MAGIC; *notifyp = notify; return (ISC_R_SUCCESS); } /* * XXXAG should check for DNS_ZONEFLG_EXITING */ static void process_adb_event(isc_task_t *task, isc_event_t *ev) { dns_notify_t *notify; isc_eventtype_t result; UNUSED(task); notify = ev->ev_arg; REQUIRE(DNS_NOTIFY_VALID(notify)); INSIST(task == notify->zone->task); result = ev->ev_type; isc_event_free(&ev); if (result == DNS_EVENT_ADBMOREADDRESSES) { dns_adb_destroyfind(¬ify->find); notify_find_address(notify); return; } if (result == DNS_EVENT_ADBNOMOREADDRESSES) { LOCK_ZONE(notify->zone); notify_send(notify); UNLOCK_ZONE(notify->zone); } notify_destroy(notify, false); } static void notify_find_address(dns_notify_t *notify) { isc_result_t result; unsigned int options; REQUIRE(DNS_NOTIFY_VALID(notify)); options = DNS_ADBFIND_WANTEVENT | DNS_ADBFIND_INET | DNS_ADBFIND_INET6 | DNS_ADBFIND_RETURNLAME; if (notify->zone->view->adb == NULL) { goto destroy; } result = dns_adb_createfind( notify->zone->view->adb, notify->zone->task, process_adb_event, notify, ¬ify->ns, dns_rootname, 0, options, 0, NULL, notify->zone->view->dstport, 0, NULL, ¬ify->find); /* Something failed? */ if (result != ISC_R_SUCCESS) { goto destroy; } /* More addresses pending? */ if ((notify->find->options & DNS_ADBFIND_WANTEVENT) != 0) { return; } /* We have as many addresses as we can get. */ LOCK_ZONE(notify->zone); notify_send(notify); UNLOCK_ZONE(notify->zone); destroy: notify_destroy(notify, false); } static isc_result_t notify_send_queue(dns_notify_t *notify, bool startup) { isc_event_t *e; isc_result_t result; INSIST(notify->event == NULL); e = isc_event_allocate(notify->mctx, NULL, DNS_EVENT_NOTIFYSENDTOADDR, notify_send_toaddr, notify, sizeof(isc_event_t)); if (startup) { notify->event = e; } e->ev_arg = notify; e->ev_sender = NULL; result = isc_ratelimiter_enqueue( startup ? notify->zone->zmgr->startupnotifyrl : notify->zone->zmgr->notifyrl, notify->zone->task, &e); if (result != ISC_R_SUCCESS) { isc_event_free(&e); notify->event = NULL; } return (result); } static void notify_send_toaddr(isc_task_t *task, isc_event_t *event) { dns_notify_t *notify; isc_result_t result; dns_message_t *message = NULL; isc_netaddr_t dstip; dns_tsigkey_t *key = NULL; char addrbuf[ISC_SOCKADDR_FORMATSIZE]; isc_sockaddr_t src; unsigned int options, timeout; bool have_notifysource = false; notify = event->ev_arg; REQUIRE(DNS_NOTIFY_VALID(notify)); UNUSED(task); LOCK_ZONE(notify->zone); notify->event = NULL; if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_LOADED) == 0) { result = ISC_R_CANCELED; goto cleanup; } if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0 || DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_EXITING) || notify->zone->view->requestmgr == NULL || notify->zone->db == NULL) { result = ISC_R_CANCELED; goto cleanup; } /* * The raw IPv4 address should also exist. Don't send to the * mapped form. */ if (isc_sockaddr_pf(¬ify->dst) == PF_INET6 && IN6_IS_ADDR_V4MAPPED(¬ify->dst.type.sin6.sin6_addr)) { isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf)); notify_log(notify->zone, ISC_LOG_DEBUG(3), "notify: ignoring IPv6 mapped IPV4 address: %s", addrbuf); result = ISC_R_CANCELED; goto cleanup; } result = notify_createmessage(notify->zone, notify->flags, &message); if (result != ISC_R_SUCCESS) { goto cleanup; } isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf)); if (notify->key != NULL) { /* Transfer ownership of key */ key = notify->key; notify->key = NULL; } else { isc_netaddr_fromsockaddr(&dstip, ¬ify->dst); result = dns_view_getpeertsig(notify->zone->view, &dstip, &key); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { notify_log(notify->zone, ISC_LOG_ERROR, "NOTIFY to %s not sent. " "Peer TSIG key lookup failure.", addrbuf); goto cleanup_message; } } if (key != NULL) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(&key->name, namebuf, sizeof(namebuf)); notify_log(notify->zone, ISC_LOG_DEBUG(3), "sending notify to %s : TSIG (%s)", addrbuf, namebuf); } else { notify_log(notify->zone, ISC_LOG_DEBUG(3), "sending notify to %s", addrbuf); } options = 0; if (notify->zone->view->peers != NULL) { dns_peer_t *peer = NULL; bool usetcp = false; result = dns_peerlist_peerbyaddr(notify->zone->view->peers, &dstip, &peer); if (result == ISC_R_SUCCESS) { result = dns_peer_getnotifysource(peer, &src); if (result == ISC_R_SUCCESS) { have_notifysource = true; } result = dns_peer_getforcetcp(peer, &usetcp); if (result == ISC_R_SUCCESS && usetcp) { options |= DNS_FETCHOPT_TCP; } } } switch (isc_sockaddr_pf(¬ify->dst)) { case PF_INET: if (!have_notifysource) { src = notify->zone->notifysrc4; } break; case PF_INET6: if (!have_notifysource) { src = notify->zone->notifysrc6; } break; default: result = ISC_R_NOTIMPLEMENTED; goto cleanup_key; } timeout = 5; if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_DIALNOTIFY)) { timeout = 30; } result = dns_request_create( notify->zone->view->requestmgr, message, &src, ¬ify->dst, options, key, timeout * 3 + 1, timeout, 2, notify->zone->task, notify_done, notify, ¬ify->request); if (result == ISC_R_SUCCESS) { if (isc_sockaddr_pf(¬ify->dst) == AF_INET) { inc_stats(notify->zone, dns_zonestatscounter_notifyoutv4); } else { inc_stats(notify->zone, dns_zonestatscounter_notifyoutv6); } } cleanup_key: if (key != NULL) { dns_tsigkey_detach(&key); } cleanup_message: dns_message_detach(&message); cleanup: UNLOCK_ZONE(notify->zone); isc_event_free(&event); if (result != ISC_R_SUCCESS) { notify_destroy(notify, false); } } static void notify_send(dns_notify_t *notify) { dns_adbaddrinfo_t *ai; isc_sockaddr_t dst; isc_result_t result; dns_notify_t *newnotify = NULL; unsigned int flags; bool startup; /* * Zone lock held by caller. */ REQUIRE(DNS_NOTIFY_VALID(notify)); REQUIRE(LOCKED_ZONE(notify->zone)); if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_EXITING)) { return; } for (ai = ISC_LIST_HEAD(notify->find->list); ai != NULL; ai = ISC_LIST_NEXT(ai, publink)) { dst = ai->sockaddr; if (notify_isqueued(notify->zone, notify->flags, NULL, &dst, NULL, NULL)) { continue; } if (notify_isself(notify->zone, &dst)) { continue; } newnotify = NULL; flags = notify->flags & DNS_NOTIFY_NOSOA; result = notify_create(notify->mctx, flags, &newnotify); if (result != ISC_R_SUCCESS) { goto cleanup; } zone_iattach(notify->zone, &newnotify->zone); ISC_LIST_APPEND(newnotify->zone->notifies, newnotify, link); newnotify->dst = dst; startup = ((notify->flags & DNS_NOTIFY_STARTUP) != 0); result = notify_send_queue(newnotify, startup); if (result != ISC_R_SUCCESS) { goto cleanup; } newnotify = NULL; } cleanup: if (newnotify != NULL) { notify_destroy(newnotify, true); } } void dns_zone_notify(dns_zone_t *zone) { isc_time_t now; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); TIME_NOW(&now); zone_settimer(zone, &now); UNLOCK_ZONE(zone); } static void zone_notify(dns_zone_t *zone, isc_time_t *now) { dns_dbnode_t *node = NULL; dns_db_t *zonedb = NULL; dns_dbversion_t *version = NULL; dns_name_t *origin = NULL; dns_name_t primary; dns_rdata_ns_t ns; dns_rdata_soa_t soa; uint32_t serial; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t nsrdset; dns_rdataset_t soardset; isc_result_t result; unsigned int i; isc_sockaddr_t dst; bool isqueued; dns_notifytype_t notifytype; unsigned int flags = 0; bool loggednotify = false; bool startup; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); startup = !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY); notifytype = zone->notifytype; DNS_ZONE_TIME_ADD(now, zone->notifydelay, &zone->notifytime); UNLOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) { return; } if (notifytype == dns_notifytype_no) { return; } if (notifytype == dns_notifytype_masteronly && zone->type != dns_zone_primary) { return; } origin = &zone->origin; /* * If the zone is dialup we are done as we don't want to send * the current soa so as to force a refresh query. */ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALNOTIFY)) { flags |= DNS_NOTIFY_NOSOA; } /* * Record that this was a notify due to starting up. */ if (startup) { flags |= DNS_NOTIFY_STARTUP; } /* * Get SOA RRset. */ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &zonedb); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (zonedb == NULL) { return; } dns_db_currentversion(zonedb, &version); result = dns_db_findnode(zonedb, origin, false, &node); if (result != ISC_R_SUCCESS) { goto cleanup1; } dns_rdataset_init(&soardset); result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_soa, dns_rdatatype_none, 0, &soardset, NULL); if (result != ISC_R_SUCCESS) { goto cleanup2; } /* * Find serial and primary server's name. */ dns_name_init(&primary, NULL); result = dns_rdataset_first(&soardset); if (result != ISC_R_SUCCESS) { goto cleanup3; } dns_rdataset_current(&soardset, &rdata); result = dns_rdata_tostruct(&rdata, &soa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); dns_name_dup(&soa.origin, zone->mctx, &primary); serial = soa.serial; dns_rdataset_disassociate(&soardset); /* * Enqueue notify requests for 'also-notify' servers. */ LOCK_ZONE(zone); for (i = 0; i < zone->notifycnt; i++) { dns_tsigkey_t *key = NULL; dns_transport_t *transport = NULL; dns_notify_t *notify = NULL; dns_view_t *view = dns_zone_getview(zone); if ((zone->notifykeynames != NULL) && (zone->notifykeynames[i] != NULL)) { dns_name_t *keyname = zone->notifykeynames[i]; (void)dns_view_gettsig(view, keyname, &key); } if ((zone->notifytlsnames != NULL) && (zone->notifytlsnames[i] != NULL)) { dns_name_t *tlsname = zone->notifytlsnames[i]; (void)dns_view_gettransport(view, DNS_TRANSPORT_TLS, tlsname, &transport); dns_zone_logc( zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, "got TLS configuration for zone transfer"); } /* TODO: glue the transport to the notify */ dst = zone->notify[i]; if (notify_isqueued(zone, flags, NULL, &dst, key, transport)) { if (key != NULL) { dns_tsigkey_detach(&key); } if (transport != NULL) { dns_transport_detach(&transport); } continue; } result = notify_create(zone->mctx, flags, ¬ify); if (result != ISC_R_SUCCESS) { if (key != NULL) { dns_tsigkey_detach(&key); } if (transport != NULL) { dns_transport_detach(&transport); } continue; } zone_iattach(zone, ¬ify->zone); notify->dst = dst; INSIST(notify->key == NULL); if (key != NULL) { notify->key = key; key = NULL; } INSIST(notify->transport == NULL); if (transport != NULL) { notify->transport = transport; transport = NULL; } ISC_LIST_APPEND(zone->notifies, notify, link); result = notify_send_queue(notify, startup); if (result != ISC_R_SUCCESS) { notify_destroy(notify, true); } if (!loggednotify) { notify_log(zone, ISC_LOG_INFO, "sending notifies (serial %u)", serial); loggednotify = true; } } UNLOCK_ZONE(zone); if (notifytype == dns_notifytype_explicit) { goto cleanup3; } /* * Process NS RRset to generate notifies. */ dns_rdataset_init(&nsrdset); result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_ns, dns_rdatatype_none, 0, &nsrdset, NULL); if (result != ISC_R_SUCCESS) { goto cleanup3; } result = dns_rdataset_first(&nsrdset); while (result == ISC_R_SUCCESS) { dns_notify_t *notify = NULL; dns_rdataset_current(&nsrdset, &rdata); result = dns_rdata_tostruct(&rdata, &ns, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); /* * Don't notify the primary server unless explicitly * configured to do so. */ if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOTIFYTOSOA) && dns_name_compare(&primary, &ns.name) == 0) { result = dns_rdataset_next(&nsrdset); continue; } if (!loggednotify) { notify_log(zone, ISC_LOG_INFO, "sending notifies (serial %u)", serial); loggednotify = true; } LOCK_ZONE(zone); isqueued = notify_isqueued(zone, flags, &ns.name, NULL, NULL, NULL); UNLOCK_ZONE(zone); if (isqueued) { result = dns_rdataset_next(&nsrdset); continue; } result = notify_create(zone->mctx, flags, ¬ify); if (result != ISC_R_SUCCESS) { continue; } dns_zone_iattach(zone, ¬ify->zone); dns_name_dup(&ns.name, zone->mctx, ¬ify->ns); LOCK_ZONE(zone); ISC_LIST_APPEND(zone->notifies, notify, link); UNLOCK_ZONE(zone); notify_find_address(notify); result = dns_rdataset_next(&nsrdset); } dns_rdataset_disassociate(&nsrdset); cleanup3: if (dns_name_dynamic(&primary)) { dns_name_free(&primary, zone->mctx); } cleanup2: dns_db_detachnode(zonedb, &node); cleanup1: dns_db_closeversion(zonedb, &version, false); dns_db_detach(&zonedb); } /*** *** Private ***/ static isc_result_t create_query(dns_zone_t *zone, dns_rdatatype_t rdtype, dns_name_t *name, dns_message_t **messagep) { dns_message_t *message = NULL; dns_name_t *qname = NULL; dns_rdataset_t *qrdataset = NULL; isc_result_t result; dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message); message->opcode = dns_opcode_query; message->rdclass = zone->rdclass; result = dns_message_gettempname(message, &qname); if (result != ISC_R_SUCCESS) { goto cleanup; } result = dns_message_gettemprdataset(message, &qrdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } /* * Make question. */ dns_name_clone(name, qname); dns_rdataset_makequestion(qrdataset, zone->rdclass, rdtype); ISC_LIST_APPEND(qname->list, qrdataset, link); dns_message_addname(message, qname, DNS_SECTION_QUESTION); *messagep = message; return (ISC_R_SUCCESS); cleanup: if (qname != NULL) { dns_message_puttempname(message, &qname); } if (qrdataset != NULL) { dns_message_puttemprdataset(message, &qrdataset); } dns_message_detach(&message); return (result); } static isc_result_t add_opt(dns_message_t *message, uint16_t udpsize, bool reqnsid, bool reqexpire) { isc_result_t result; dns_rdataset_t *rdataset = NULL; dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS]; int count = 0; /* Set EDNS options if applicable. */ if (reqnsid) { INSIST(count < DNS_EDNSOPTIONS); ednsopts[count].code = DNS_OPT_NSID; ednsopts[count].length = 0; ednsopts[count].value = NULL; count++; } if (reqexpire) { INSIST(count < DNS_EDNSOPTIONS); ednsopts[count].code = DNS_OPT_EXPIRE; ednsopts[count].length = 0; ednsopts[count].value = NULL; count++; } result = dns_message_buildopt(message, &rdataset, 0, udpsize, 0, ednsopts, count); if (result != ISC_R_SUCCESS) { return (result); } return (dns_message_setopt(message, rdataset)); } /* * Called when stub zone update is finished. * Update zone refresh, retry, expire values accordingly with * SOA received from primary, sync database to file, restart * zone management timer. */ static void stub_finish_zone_update(dns_stub_t *stub, isc_time_t now) { uint32_t refresh, retry, expire; isc_result_t result; isc_interval_t i; unsigned int soacount; dns_zone_t *zone = stub->zone; /* * Tidy up. */ dns_db_closeversion(stub->db, &stub->version, true); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); if (zone->db == NULL) { zone_attachdb(zone, stub->db); } result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, NULL, &refresh, &retry, &expire, NULL, NULL); if (result == ISC_R_SUCCESS && soacount > 0U) { zone->refresh = RANGE(refresh, zone->minrefresh, zone->maxrefresh); zone->retry = RANGE(retry, zone->minretry, zone->maxretry); zone->expire = RANGE(expire, zone->refresh + zone->retry, DNS_MAX_EXPIRE); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); dns_db_detach(&stub->db); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); isc_interval_set(&i, zone->expire, 0); DNS_ZONE_TIME_ADD(&now, zone->expire, &zone->expiretime); if (zone->masterfile != NULL) { zone_needdump(zone, 0); } zone_settimer(zone, &now); } /* * Process answers for A and AAAA queries when * resolving nameserver addresses for which glue * was missing in a previous answer for a NS query. */ static void stub_glue_response_cb(isc_task_t *task, isc_event_t *event) { const char me[] = "stub_glue_response_cb"; dns_requestevent_t *revent = (dns_requestevent_t *)event; dns_stub_t *stub = NULL; dns_message_t *msg = NULL; dns_zone_t *zone = NULL; char primary[ISC_SOCKADDR_FORMATSIZE]; char source[ISC_SOCKADDR_FORMATSIZE]; uint32_t addr_count, cnamecnt; isc_result_t result; isc_time_t now; struct stub_glue_request *request; struct stub_cb_args *cb_args; dns_rdataset_t *addr_rdataset = NULL; dns_dbnode_t *node = NULL; UNUSED(task); request = revent->ev_arg; cb_args = request->args; stub = cb_args->stub; INSIST(DNS_STUB_VALID(stub)); zone = stub->zone; ENTER; TIME_NOW(&now); LOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { zone_debuglog(zone, me, 1, "exiting"); goto cleanup; } isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary)); isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); if (revent->result != ISC_R_SUCCESS) { dns_zonemgr_unreachableadd(zone->zmgr, &zone->primaryaddr, &zone->sourceaddr, &now); dns_zone_log(zone, ISC_LOG_INFO, "could not refresh stub from primary %s" " (source %s): %s", primary, source, isc_result_totext(revent->result)); goto cleanup; } dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg); result = dns_request_getresponse(revent->request, msg, 0); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: unable to parse response (%s)", isc_result_totext(result)); goto cleanup; } /* * Unexpected opcode. */ if (msg->opcode != dns_opcode_query) { char opcode[128]; isc_buffer_t rb; isc_buffer_init(&rb, opcode, sizeof(opcode)); (void)dns_opcode_totext(msg->opcode, &rb); dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "unexpected opcode (%.*s) from %s (source %s)", (int)rb.used, opcode, primary, source); goto cleanup; } /* * Unexpected rcode. */ if (msg->rcode != dns_rcode_noerror) { char rcode[128]; isc_buffer_t rb; isc_buffer_init(&rb, rcode, sizeof(rcode)); (void)dns_rcode_totext(msg->rcode, &rb); dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "unexpected rcode (%.*s) from %s (source %s)", (int)rb.used, rcode, primary, source); goto cleanup; } /* * We need complete messages. */ if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { if (dns_request_usedtcp(revent->request)) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: truncated TCP " "response from primary %s (source %s)", primary, source); } goto cleanup; } /* * If non-auth log. */ if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "non-authoritative answer from " "primary %s (source %s)", primary, source); goto cleanup; } /* * Sanity checks. */ cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname); addr_count = message_count(msg, DNS_SECTION_ANSWER, request->ipv4 ? dns_rdatatype_a : dns_rdatatype_aaaa); if (cnamecnt != 0) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: unexpected CNAME response " "from primary %s (source %s)", primary, source); goto cleanup; } if (addr_count == 0) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: no %s records in response " "from primary %s (source %s)", request->ipv4 ? "A" : "AAAA", primary, source); goto cleanup; } /* * Extract A or AAAA RRset from message. */ result = dns_message_findname(msg, DNS_SECTION_ANSWER, &request->name, request->ipv4 ? dns_rdatatype_a : dns_rdatatype_aaaa, dns_rdatatype_none, NULL, &addr_rdataset); if (result != ISC_R_SUCCESS) { if (result != DNS_R_NXDOMAIN && result != DNS_R_NXRRSET) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(&request->name, namebuf, sizeof(namebuf)); dns_zone_log( zone, ISC_LOG_INFO, "refreshing stub: dns_message_findname(%s/%s) " "failed (%s)", namebuf, request->ipv4 ? "A" : "AAAA", isc_result_totext(result)); } goto cleanup; } result = dns_db_findnode(stub->db, &request->name, true, &node); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "dns_db_findnode() failed: %s", isc_result_totext(result)); goto cleanup; } result = dns_db_addrdataset(stub->db, node, stub->version, 0, addr_rdataset, 0, NULL); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "dns_db_addrdataset() failed: %s", isc_result_totext(result)); } dns_db_detachnode(stub->db, &node); cleanup: if (msg != NULL) { dns_message_detach(&msg); } isc_event_free(&event); dns_name_free(&request->name, zone->mctx); dns_request_destroy(&request->request); isc_mem_put(zone->mctx, request, sizeof(*request)); /* If last request, release all related resources */ if (atomic_fetch_sub_release(&stub->pending_requests, 1) == 1) { isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); stub_finish_zone_update(stub, now); UNLOCK_ZONE(zone); stub->magic = 0; dns_zone_idetach(&stub->zone); INSIST(stub->db == NULL); INSIST(stub->version == NULL); isc_mem_put(stub->mctx, stub, sizeof(*stub)); } else { UNLOCK_ZONE(zone); } } /* * Create and send an A or AAAA query to the primary * server of the stub zone given. */ static isc_result_t stub_request_nameserver_address(struct stub_cb_args *args, bool ipv4, const dns_name_t *name) { dns_message_t *message = NULL; dns_zone_t *zone; isc_result_t result; struct stub_glue_request *request; zone = args->stub->zone; request = isc_mem_get(zone->mctx, sizeof(*request)); request->request = NULL; request->args = args; request->name = (dns_name_t)DNS_NAME_INITEMPTY; request->ipv4 = ipv4; dns_name_dup(name, zone->mctx, &request->name); result = create_query(zone, ipv4 ? dns_rdatatype_a : dns_rdatatype_aaaa, &request->name, &message); INSIST(result == ISC_R_SUCCESS); if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { result = add_opt(message, args->udpsize, args->reqnsid, false); if (result != ISC_R_SUCCESS) { zone_debuglog(zone, "stub_send_query", 1, "unable to add opt record: %s", isc_result_totext(result)); goto fail; } } atomic_fetch_add_release(&args->stub->pending_requests, 1); result = dns_request_create( zone->view->requestmgr, message, &zone->sourceaddr, &zone->primaryaddr, DNS_REQUESTOPT_TCP, args->tsig_key, args->timeout * 3, args->timeout, 2, zone->task, stub_glue_response_cb, request, &request->request); if (result != ISC_R_SUCCESS) { uint_fast32_t pr; pr = atomic_fetch_sub_release(&args->stub->pending_requests, 1); INSIST(pr > 1); zone_debuglog(zone, "stub_send_query", 1, "dns_request_create() failed: %s", isc_result_totext(result)); goto fail; } dns_message_detach(&message); return (ISC_R_SUCCESS); fail: dns_name_free(&request->name, zone->mctx); isc_mem_put(zone->mctx, request, sizeof(*request)); if (message != NULL) { dns_message_detach(&message); } return (result); } static isc_result_t save_nsrrset(dns_message_t *message, dns_name_t *name, struct stub_cb_args *cb_args, dns_db_t *db, dns_dbversion_t *version) { dns_rdataset_t *nsrdataset = NULL; dns_rdataset_t *rdataset = NULL; dns_dbnode_t *node = NULL; dns_rdata_ns_t ns; isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; bool has_glue = false; dns_name_t *ns_name; /* * List of NS entries in answer, keep names that will be used * to resolve missing A/AAAA glue for each entry. */ dns_namelist_t ns_list; ISC_LIST_INIT(ns_list); /* * Extract NS RRset from message. */ result = dns_message_findname(message, DNS_SECTION_ANSWER, name, dns_rdatatype_ns, dns_rdatatype_none, NULL, &nsrdataset); if (result != ISC_R_SUCCESS) { goto done; } /* * Add NS rdataset. */ result = dns_db_findnode(db, name, true, &node); if (result != ISC_R_SUCCESS) { goto done; } result = dns_db_addrdataset(db, node, version, 0, nsrdataset, 0, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { goto done; } /* * Add glue rdatasets. */ for (result = dns_rdataset_first(nsrdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(nsrdataset)) { dns_rdataset_current(nsrdataset, &rdata); result = dns_rdata_tostruct(&rdata, &ns, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); if (!dns_name_issubdomain(&ns.name, name)) { continue; } rdataset = NULL; result = dns_message_findname(message, DNS_SECTION_ADDITIONAL, &ns.name, dns_rdatatype_aaaa, dns_rdatatype_none, NULL, &rdataset); if (result == ISC_R_SUCCESS) { has_glue = true; result = dns_db_findnode(db, &ns.name, true, &node); if (result != ISC_R_SUCCESS) { goto done; } result = dns_db_addrdataset(db, node, version, 0, rdataset, 0, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { goto done; } } rdataset = NULL; result = dns_message_findname( message, DNS_SECTION_ADDITIONAL, &ns.name, dns_rdatatype_a, dns_rdatatype_none, NULL, &rdataset); if (result == ISC_R_SUCCESS) { has_glue = true; result = dns_db_findnode(db, &ns.name, true, &node); if (result != ISC_R_SUCCESS) { goto done; } result = dns_db_addrdataset(db, node, version, 0, rdataset, 0, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { goto done; } } /* * If no glue is found so far, we add the name to the list to * resolve the A/AAAA glue later. If any glue is found in any * iteration step, this list will be discarded and only the glue * provided in this message will be used. */ if (!has_glue && dns_name_issubdomain(&ns.name, name)) { dns_name_t *tmp_name; tmp_name = isc_mem_get(cb_args->stub->mctx, sizeof(*tmp_name)); dns_name_init(tmp_name, NULL); dns_name_dup(&ns.name, cb_args->stub->mctx, tmp_name); ISC_LIST_APPEND(ns_list, tmp_name, link); } } if (result != ISC_R_NOMORE) { goto done; } /* * If no glue records were found, we attempt to resolve A/AAAA * for each NS entry found in the answer. */ if (!has_glue) { for (ns_name = ISC_LIST_HEAD(ns_list); ns_name != NULL; ns_name = ISC_LIST_NEXT(ns_name, link)) { /* * Resolve NS IPv4 address/A. */ result = stub_request_nameserver_address(cb_args, true, ns_name); if (result != ISC_R_SUCCESS) { goto done; } /* * Resolve NS IPv6 address/AAAA. */ result = stub_request_nameserver_address(cb_args, false, ns_name); if (result != ISC_R_SUCCESS) { goto done; } } } result = ISC_R_SUCCESS; done: while ((ns_name = ISC_LIST_HEAD(ns_list)) != NULL) { ISC_LIST_UNLINK(ns_list, ns_name, link); dns_name_free(ns_name, cb_args->stub->mctx); isc_mem_put(cb_args->stub->mctx, ns_name, sizeof(*ns_name)); } return (result); } static void stub_callback(isc_task_t *task, isc_event_t *event) { const char me[] = "stub_callback"; dns_requestevent_t *revent = (dns_requestevent_t *)event; dns_stub_t *stub = NULL; dns_message_t *msg = NULL; dns_zone_t *zone = NULL; char primary[ISC_SOCKADDR_FORMATSIZE]; char source[ISC_SOCKADDR_FORMATSIZE]; uint32_t nscnt, cnamecnt; isc_result_t result; isc_time_t now; bool exiting = false; unsigned int j; struct stub_cb_args *cb_args; cb_args = revent->ev_arg; stub = cb_args->stub; INSIST(DNS_STUB_VALID(stub)); UNUSED(task); zone = stub->zone; ENTER; TIME_NOW(&now); LOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { goto exiting; } isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary)); isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); switch (revent->result) { case ISC_R_SUCCESS: break; case ISC_R_SHUTTINGDOWN: goto exiting; case ISC_R_TIMEDOUT: if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); dns_zone_log(zone, ISC_LOG_DEBUG(1), "refreshing stub: timeout retrying " "without EDNS primary %s (source %s)", primary, source); goto same_primary; } FALLTHROUGH; default: dns_zonemgr_unreachableadd(zone->zmgr, &zone->primaryaddr, &zone->sourceaddr, &now); dns_zone_log(zone, ISC_LOG_INFO, "could not refresh stub from primary " "%s (source %s): %s", primary, source, isc_result_totext(revent->result)); goto next_primary; } dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg); result = dns_request_getresponse(revent->request, msg, 0); if (result != ISC_R_SUCCESS) { goto next_primary; } /* * Unexpected opcode. */ if (msg->opcode != dns_opcode_query) { char opcode[128]; isc_buffer_t rb; isc_buffer_init(&rb, opcode, sizeof(opcode)); (void)dns_opcode_totext(msg->opcode, &rb); dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "unexpected opcode (%.*s) from %s (source %s)", (int)rb.used, opcode, primary, source); goto next_primary; } /* * Unexpected rcode. */ if (msg->rcode != dns_rcode_noerror) { char rcode[128]; isc_buffer_t rb; isc_buffer_init(&rb, rcode, sizeof(rcode)); (void)dns_rcode_totext(msg->rcode, &rb); if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS) && (msg->rcode == dns_rcode_servfail || msg->rcode == dns_rcode_notimp || (msg->rcode == dns_rcode_formerr && msg->opt == NULL))) { dns_zone_log(zone, ISC_LOG_DEBUG(1), "refreshing stub: rcode (%.*s) retrying " "without EDNS primary %s (source %s)", (int)rb.used, rcode, primary, source); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); goto same_primary; } dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "unexpected rcode (%.*s) from %s (source %s)", (int)rb.used, rcode, primary, source); goto next_primary; } /* * We need complete messages. */ if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { if (dns_request_usedtcp(revent->request)) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: truncated TCP " "response from primary %s (source %s)", primary, source); goto next_primary; } DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC); goto same_primary; } /* * If non-auth log and next primary. */ if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "non-authoritative answer from " "primary %s (source %s)", primary, source); goto next_primary; } /* * Sanity checks. */ cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname); nscnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_ns); if (cnamecnt != 0) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: unexpected CNAME response " "from primary %s (source %s)", primary, source); goto next_primary; } if (nscnt == 0) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: no NS records in response " "from primary %s (source %s)", primary, source); goto next_primary; } atomic_fetch_add(&stub->pending_requests, 1); /* * Save answer. */ result = save_nsrrset(msg, &zone->origin, cb_args, stub->db, stub->version); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: unable to save NS records " "from primary %s (source %s)", primary, source); goto next_primary; } dns_message_detach(&msg); isc_event_free(&event); dns_request_destroy(&zone->request); /* * Check to see if there are no outstanding requests and * finish off if that is so. */ if (atomic_fetch_sub(&stub->pending_requests, 1) == 1) { isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); stub_finish_zone_update(stub, now); goto free_stub; } UNLOCK_ZONE(zone); return; exiting: zone_debuglog(zone, me, 1, "exiting"); exiting = true; next_primary: isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); if (stub->version != NULL) { dns_db_closeversion(stub->db, &stub->version, false); } if (stub->db != NULL) { dns_db_detach(&stub->db); } if (msg != NULL) { dns_message_detach(&msg); } isc_event_free(&event); dns_request_destroy(&zone->request); /* * Skip to next failed / untried primary. */ do { zone->curprimary++; } while (zone->curprimary < zone->primariescnt && zone->primariesok[zone->curprimary]); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS); if (exiting || zone->curprimary >= zone->primariescnt) { bool done = true; if (!exiting && DNS_ZONE_OPTION(zone, DNS_ZONEOPT_USEALTXFRSRC) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { /* * Did we get a good answer from all the primaries? */ for (j = 0; j < zone->primariescnt; j++) { if (!zone->primariesok[j]) { { done = false; break; } } } } else { done = true; } if (!done) { zone->curprimary = 0; /* * Find the next failed primary. */ while (zone->curprimary < zone->primariescnt && zone->primariesok[zone->curprimary]) { zone->curprimary++; } DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); } else { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); zone_settimer(zone, &now); goto free_stub; } } queue_soa_query(zone); goto free_stub; same_primary: isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); if (msg != NULL) { dns_message_detach(&msg); } isc_event_free(&event); dns_request_destroy(&zone->request); ns_query(zone, NULL, stub); UNLOCK_ZONE(zone); goto done; free_stub: UNLOCK_ZONE(zone); stub->magic = 0; dns_zone_idetach(&stub->zone); INSIST(stub->db == NULL); INSIST(stub->version == NULL); isc_mem_put(stub->mctx, stub, sizeof(*stub)); done: INSIST(event == NULL); return; } /* * Get the EDNS EXPIRE option from the response and if it exists trim * expire to be not more than it. */ static void get_edns_expire(dns_zone_t *zone, dns_message_t *message, uint32_t *expirep) { isc_result_t result; uint32_t expire; dns_rdata_t rdata = DNS_RDATA_INIT; isc_buffer_t optbuf; uint16_t optcode; uint16_t optlen; REQUIRE(expirep != NULL); REQUIRE(message != NULL); if (message->opt == NULL) { return; } result = dns_rdataset_first(message->opt); if (result == ISC_R_SUCCESS) { dns_rdataset_current(message->opt, &rdata); isc_buffer_init(&optbuf, rdata.data, rdata.length); isc_buffer_add(&optbuf, rdata.length); while (isc_buffer_remaininglength(&optbuf) >= 4) { optcode = isc_buffer_getuint16(&optbuf); optlen = isc_buffer_getuint16(&optbuf); /* * A EDNS EXPIRE response has a length of 4. */ if (optcode != DNS_OPT_EXPIRE || optlen != 4) { isc_buffer_forward(&optbuf, optlen); continue; } expire = isc_buffer_getuint32(&optbuf); dns_zone_log(zone, ISC_LOG_DEBUG(1), "got EDNS EXPIRE of %u", expire); /* * Trim *expirep? */ if (expire < *expirep) { *expirep = expire; } break; } } } /* * Set the file modification time zone->expire seconds before expiretime. */ static void setmodtime(dns_zone_t *zone, isc_time_t *expiretime) { isc_result_t result; isc_time_t when; isc_interval_t i; isc_interval_set(&i, zone->expire, 0); result = isc_time_subtract(expiretime, &i, &when); if (result != ISC_R_SUCCESS) { return; } result = ISC_R_FAILURE; if (zone->journal != NULL) { result = isc_file_settime(zone->journal, &when); } if (result == ISC_R_SUCCESS && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { result = isc_file_settime(zone->masterfile, &when); } else if (result != ISC_R_SUCCESS) { result = isc_file_settime(zone->masterfile, &when); } /* * Someone removed the file from underneath us! */ if (result == ISC_R_FILENOTFOUND) { zone_needdump(zone, DNS_DUMP_DELAY); } else if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "refresh: could not set " "file modification time of '%s': %s", zone->masterfile, isc_result_totext(result)); } } /* * An SOA query has finished (successfully or not). */ static void refresh_callback(isc_task_t *task, isc_event_t *event) { const char me[] = "refresh_callback"; dns_requestevent_t *revent = (dns_requestevent_t *)event; dns_zone_t *zone; dns_message_t *msg = NULL; uint32_t soacnt, cnamecnt, soacount, nscount; isc_time_t now; char primary[ISC_SOCKADDR_FORMATSIZE]; char source[ISC_SOCKADDR_FORMATSIZE]; dns_rdataset_t *rdataset = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_soa_t soa; isc_result_t result; uint32_t serial, oldserial = 0; unsigned int j; bool do_queue_xfrin = false; zone = revent->ev_arg; INSIST(DNS_ZONE_VALID(zone)); UNUSED(task); ENTER; TIME_NOW(&now); LOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { goto exiting; } /* * If timeout, log and try the next primary */ isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary)); isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); switch (revent->result) { case ISC_R_SUCCESS: break; case ISC_R_SHUTTINGDOWN: goto exiting; case ISC_R_TIMEDOUT: if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); dns_zone_log(zone, ISC_LOG_DEBUG(1), "refresh: timeout retrying without EDNS " "primary %s (source %s)", primary, source); goto same_primary; } else if (!dns_request_usedtcp(revent->request)) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: retry limit for " "primary %s exceeded (source %s)", primary, source); /* Try with secondary with TCP. */ if ((zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_redirect) && DNS_ZONE_OPTION(zone, DNS_ZONEOPT_TRYTCPREFRESH)) { if (!dns_zonemgr_unreachable( zone->zmgr, &zone->primaryaddr, &zone->sourceaddr, &now)) { DNS_ZONE_SETFLAG( zone, DNS_ZONEFLG_SOABEFOREAXFR); goto tcp_transfer; } dns_zone_log(zone, ISC_LOG_DEBUG(1), "refresh: skipped tcp fallback " "as primary %s (source %s) is " "unreachable (cached)", primary, source); } goto next_primary; } FALLTHROUGH; default: dns_zone_log(zone, ISC_LOG_INFO, "refresh: failure trying primary " "%s (source %s): %s", primary, source, isc_result_totext(revent->result)); goto next_primary; } dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg); result = dns_request_getresponse(revent->request, msg, 0); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: failure trying primary " "%s (source %s): %s", primary, source, isc_result_totext(result)); goto next_primary; } /* * Unexpected opcode. */ if (msg->opcode != dns_opcode_query) { char opcode[128]; isc_buffer_t rb; isc_buffer_init(&rb, opcode, sizeof(opcode)); (void)dns_opcode_totext(msg->opcode, &rb); dns_zone_log(zone, ISC_LOG_INFO, "refresh: " "unexpected opcode (%.*s) from %s (source %s)", (int)rb.used, opcode, primary, source); goto next_primary; } /* * Unexpected rcode. */ if (msg->rcode != dns_rcode_noerror) { char rcode[128]; isc_buffer_t rb; isc_buffer_init(&rb, rcode, sizeof(rcode)); (void)dns_rcode_totext(msg->rcode, &rb); if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS) && (msg->rcode == dns_rcode_servfail || msg->rcode == dns_rcode_notimp || (msg->rcode == dns_rcode_formerr && msg->opt == NULL))) { dns_zone_log(zone, ISC_LOG_DEBUG(1), "refresh: rcode (%.*s) retrying without " "EDNS primary %s (source %s)", (int)rb.used, rcode, primary, source); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); goto same_primary; } if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS) && msg->rcode == dns_rcode_badvers) { dns_zone_log(zone, ISC_LOG_DEBUG(1), "refresh: rcode (%.*s) retrying without " "EDNS EXPIRE OPTION primary %s " "(source %s)", (int)rb.used, rcode, primary, source); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); goto same_primary; } dns_zone_log(zone, ISC_LOG_INFO, "refresh: unexpected rcode (%.*s) from " "primary %s (source %s)", (int)rb.used, rcode, primary, source); /* * Perhaps AXFR/IXFR is allowed even if SOA queries aren't. */ if (msg->rcode == dns_rcode_refused && (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_redirect)) { goto tcp_transfer; } goto next_primary; } /* * If truncated punt to zone transfer which will query again. */ if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_redirect) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: truncated UDP answer, " "initiating TCP zone xfer " "for primary %s (source %s)", primary, source); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR); goto tcp_transfer; } else { INSIST(zone->type == dns_zone_stub); if (dns_request_usedtcp(revent->request)) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: truncated TCP response " "from primary %s (source %s)", primary, source); goto next_primary; } DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC); goto same_primary; } } /* * If non-auth, log and try the next primary */ if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: non-authoritative answer from " "primary %s (source %s)", primary, source); goto next_primary; } cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname); soacnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_soa); nscount = message_count(msg, DNS_SECTION_AUTHORITY, dns_rdatatype_ns); soacount = message_count(msg, DNS_SECTION_AUTHORITY, dns_rdatatype_soa); /* * There should not be a CNAME record at top of zone. */ if (cnamecnt != 0) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: CNAME at top of zone " "in primary %s (source %s)", primary, source); goto next_primary; } /* * If referral, log and try the next primary; */ if (soacnt == 0 && soacount == 0 && nscount != 0) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: referral response " "from primary %s (source %s)", primary, source); goto next_primary; } /* * If nodata, log and try the next primary; */ if (soacnt == 0 && (nscount == 0 || soacount != 0)) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: NODATA response " "from primary %s (source %s)", primary, source); goto next_primary; } /* * Only one soa at top of zone. */ if (soacnt != 1) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: answer SOA count (%d) != 1 " "from primary %s (source %s)", soacnt, primary, source); goto next_primary; } /* * Extract serial */ rdataset = NULL; result = dns_message_findname(msg, DNS_SECTION_ANSWER, &zone->origin, dns_rdatatype_soa, dns_rdatatype_none, NULL, &rdataset); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: unable to get SOA record " "from primary %s (source %s)", primary, source); goto next_primary; } result = dns_rdataset_first(rdataset); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: dns_rdataset_first() failed"); goto next_primary; } dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &soa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); serial = soa.serial; if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)) { unsigned int dbsoacount; result = zone_get_from_db(zone, zone->db, NULL, &dbsoacount, NULL, &oldserial, NULL, NULL, NULL, NULL, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); RUNTIME_CHECK(dbsoacount > 0U); zone_debuglog(zone, me, 1, "serial: new %u, old %u", serial, oldserial); } else { zone_debuglog(zone, me, 1, "serial: new %u, old not loaded", serial); } if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) || DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER) || isc_serial_gt(serial, oldserial)) { if (dns_zonemgr_unreachable(zone->zmgr, &zone->primaryaddr, &zone->sourceaddr, &now)) { dns_zone_log(zone, ISC_LOG_INFO, "refresh: skipping %s as primary %s " "(source %s) is unreachable (cached)", (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_redirect) ? "zone transfer" : "NS query", primary, source); goto next_primary; } tcp_transfer: isc_event_free(&event); dns_request_destroy(&zone->request); if (zone->type == dns_zone_secondary || zone->type == dns_zone_mirror || zone->type == dns_zone_redirect) { do_queue_xfrin = true; } else { INSIST(zone->type == dns_zone_stub); ns_query(zone, rdataset, NULL); } if (msg != NULL) { dns_message_detach(&msg); } } else if (isc_serial_eq(soa.serial, oldserial)) { isc_time_t expiretime; uint32_t expire; /* * Compute the new expire time based on this response. */ expire = zone->expire; get_edns_expire(zone, msg, &expire); DNS_ZONE_TIME_ADD(&now, expire, &expiretime); /* * Has the expire time improved? */ if (isc_time_compare(&expiretime, &zone->expiretime) > 0) { zone->expiretime = expiretime; if (zone->masterfile != NULL) { setmodtime(zone, &expiretime); } } DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); zone->primariesok[zone->curprimary] = true; goto next_primary; } else { if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MULTIMASTER)) { dns_zone_log(zone, ISC_LOG_INFO, "serial number (%u) " "received from primary %s < ours (%u)", soa.serial, primary, oldserial); } else { zone_debuglog(zone, me, 1, "ahead"); } zone->primariesok[zone->curprimary] = true; goto next_primary; } if (msg != NULL) { dns_message_detach(&msg); } goto detach; next_primary: if (msg != NULL) { dns_message_detach(&msg); } isc_event_free(&event); dns_request_destroy(&zone->request); /* * Skip to next failed / untried primary. */ do { zone->curprimary++; } while (zone->curprimary < zone->primariescnt && zone->primariesok[zone->curprimary]); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS); if (zone->curprimary >= zone->primariescnt) { bool done = true; if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_USEALTXFRSRC) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { /* * Did we get a good answer from all the primaries? */ for (j = 0; j < zone->primariescnt; j++) { if (!zone->primariesok[j]) { { done = false; break; } } } } else { done = true; } if (!done) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); zone->curprimary = 0; /* * Find the next failed primary. */ while (zone->curprimary < zone->primariescnt && zone->primariesok[zone->curprimary]) { zone->curprimary++; } goto requeue; } DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDREFRESH)) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDREFRESH); zone->refreshtime = now; } DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); zone_settimer(zone, &now); goto detach; } requeue: queue_soa_query(zone); goto detach; exiting: isc_event_free(&event); dns_request_destroy(&zone->request); goto detach; same_primary: if (msg != NULL) { dns_message_detach(&msg); } isc_event_free(&event); dns_request_destroy(&zone->request); queue_soa_query(zone); detach: UNLOCK_ZONE(zone); if (do_queue_xfrin) { queue_xfrin(zone); } dns_zone_idetach(&zone); return; } static void queue_soa_query(dns_zone_t *zone) { const char me[] = "queue_soa_query"; isc_event_t *e; dns_zone_t *dummy = NULL; isc_result_t result; ENTER; /* * Locked by caller */ REQUIRE(LOCKED_ZONE(zone)); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { cancel_refresh(zone); return; } e = isc_event_allocate(zone->mctx, NULL, DNS_EVENT_ZONE, soa_query, zone, sizeof(isc_event_t)); /* * Attach so that we won't clean up * until the event is delivered. */ zone_iattach(zone, &dummy); e->ev_arg = zone; e->ev_sender = NULL; result = isc_ratelimiter_enqueue(zone->zmgr->refreshrl, zone->task, &e); if (result != ISC_R_SUCCESS) { zone_idetach(&dummy); isc_event_free(&e); cancel_refresh(zone); } } static void soa_query(isc_task_t *task, isc_event_t *event) { const char me[] = "soa_query"; isc_result_t result = ISC_R_FAILURE; dns_message_t *message = NULL; dns_zone_t *zone = event->ev_arg; dns_zone_t *dummy = NULL; isc_netaddr_t primaryip; dns_tsigkey_t *key = NULL; dns_transport_t *transport = NULL; uint32_t options; bool cancel = true; int timeout; bool have_xfrsource = false, reqnsid, reqexpire; uint16_t udpsize = SEND_BUFFER_SIZE; bool do_queue_xfrin = false; REQUIRE(DNS_ZONE_VALID(zone)); UNUSED(task); ENTER; LOCK_ZONE(zone); if (((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0) || DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || zone->view->requestmgr == NULL) { if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { cancel = false; } goto cleanup; } again: INSIST(zone->primariescnt > 0); INSIST(zone->curprimary < zone->primariescnt); zone->primaryaddr = zone->primaries[zone->curprimary]; isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr); /* * First, look for a tsig key in the primaries statement, then * try for a server key. */ if ((zone->primarykeynames != NULL) && (zone->primarykeynames[zone->curprimary] != NULL)) { dns_view_t *view = dns_zone_getview(zone); dns_name_t *keyname = zone->primarykeynames[zone->curprimary]; result = dns_view_gettsig(view, keyname, &key); if (result != ISC_R_SUCCESS) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(keyname, namebuf, sizeof(namebuf)); dns_zone_log(zone, ISC_LOG_ERROR, "unable to find key: %s", namebuf); goto skip_primary; } } if (key == NULL) { result = dns_view_getpeertsig(zone->view, &primaryip, &key); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { char addrbuf[ISC_NETADDR_FORMATSIZE]; isc_netaddr_format(&primaryip, addrbuf, sizeof(addrbuf)); dns_zone_log(zone, ISC_LOG_ERROR, "unable to find TSIG key for %s", addrbuf); goto skip_primary; } } if ((zone->primarytlsnames != NULL) && (zone->primarytlsnames[zone->curprimary] != NULL)) { dns_view_t *view = dns_zone_getview(zone); dns_name_t *tlsname = zone->primarytlsnames[zone->curprimary]; result = dns_view_gettransport(view, DNS_TRANSPORT_TLS, tlsname, &transport); if (result != ISC_R_SUCCESS) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(tlsname, namebuf, sizeof(namebuf)); dns_zone_log(zone, ISC_LOG_ERROR, "unable to find TLS configuration: %s", namebuf); goto skip_primary; } } options = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEVC) ? DNS_REQUESTOPT_TCP : 0; reqnsid = zone->view->requestnsid; reqexpire = zone->requestexpire; if (zone->view->peers != NULL) { dns_peer_t *peer = NULL; bool edns, usetcp; result = dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, &peer); if (result == ISC_R_SUCCESS) { result = dns_peer_getsupportedns(peer, &edns); if (result == ISC_R_SUCCESS && !edns) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); } result = dns_peer_gettransfersource(peer, &zone->sourceaddr); if (result == ISC_R_SUCCESS) { have_xfrsource = true; } if (zone->view->resolver != NULL) { udpsize = dns_resolver_getudpsize( zone->view->resolver); } (void)dns_peer_getudpsize(peer, &udpsize); (void)dns_peer_getrequestnsid(peer, &reqnsid); (void)dns_peer_getrequestexpire(peer, &reqexpire); result = dns_peer_getforcetcp(peer, &usetcp); if (result == ISC_R_SUCCESS && usetcp) { options |= DNS_REQUESTOPT_TCP; } } } switch (isc_sockaddr_pf(&zone->primaryaddr)) { case PF_INET: if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { if (isc_sockaddr_equal(&zone->altxfrsource4, &zone->xfrsource4)) { goto skip_primary; } zone->sourceaddr = zone->altxfrsource4; } else if (!have_xfrsource) { zone->sourceaddr = zone->xfrsource4; } break; case PF_INET6: if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { if (isc_sockaddr_equal(&zone->altxfrsource6, &zone->xfrsource6)) { goto skip_primary; } zone->sourceaddr = zone->altxfrsource6; } else if (!have_xfrsource) { zone->sourceaddr = zone->xfrsource6; } break; default: result = ISC_R_NOTIMPLEMENTED; goto cleanup; } /* * FIXME(OS): This is a bit hackish, but it enforces the SOA query to go * through the XFR channel instead of doing dns_request that doesn't * have DoT support yet. */ if (transport != NULL) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR); do_queue_xfrin = true; cancel = false; result = ISC_R_SUCCESS; goto cleanup; } result = create_query(zone, dns_rdatatype_soa, &zone->origin, &message); if (result != ISC_R_SUCCESS) { goto cleanup; } if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { result = add_opt(message, udpsize, reqnsid, reqexpire); if (result != ISC_R_SUCCESS) { zone_debuglog(zone, me, 1, "unable to add opt record: %s", isc_result_totext(result)); } } zone_iattach(zone, &dummy); timeout = 5; if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) { timeout = 30; } result = dns_request_create( zone->view->requestmgr, message, &zone->sourceaddr, &zone->primaryaddr, options, key, timeout * 3 + 1, timeout, 2, zone->task, refresh_callback, zone, &zone->request); if (result != ISC_R_SUCCESS) { zone_idetach(&dummy); zone_debuglog(zone, me, 1, "dns_request_create() failed: %s", isc_result_totext(result)); goto skip_primary; } else { if (isc_sockaddr_pf(&zone->primaryaddr) == PF_INET) { inc_stats(zone, dns_zonestatscounter_soaoutv4); } else { inc_stats(zone, dns_zonestatscounter_soaoutv6); } } cancel = false; cleanup: if (transport != NULL) { dns_transport_detach(&transport); } if (key != NULL) { dns_tsigkey_detach(&key); } if (result != ISC_R_SUCCESS) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); } if (message != NULL) { dns_message_detach(&message); } if (cancel) { cancel_refresh(zone); } isc_event_free(&event); UNLOCK_ZONE(zone); if (do_queue_xfrin) { queue_xfrin(zone); } dns_zone_idetach(&zone); return; skip_primary: if (transport != NULL) { dns_transport_detach(&transport); } if (key != NULL) { dns_tsigkey_detach(&key); } if (message != NULL) { dns_message_detach(&message); } /* * Skip to next failed / untried primary. */ do { zone->curprimary++; } while (zone->curprimary < zone->primariescnt && zone->primariesok[zone->curprimary]); if (zone->curprimary < zone->primariescnt) { goto again; } zone->curprimary = 0; goto cleanup; } static void ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) { const char me[] = "ns_query"; isc_result_t result; dns_message_t *message = NULL; isc_netaddr_t primaryip; dns_tsigkey_t *key = NULL; dns_dbnode_t *node = NULL; int timeout; bool have_xfrsource = false; bool reqnsid; uint16_t udpsize = SEND_BUFFER_SIZE; struct stub_cb_args *cb_args; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(LOCKED_ZONE(zone)); REQUIRE((soardataset != NULL && stub == NULL) || (soardataset == NULL && stub != NULL)); REQUIRE(stub == NULL || DNS_STUB_VALID(stub)); ENTER; if (stub == NULL) { stub = isc_mem_get(zone->mctx, sizeof(*stub)); stub->magic = STUB_MAGIC; stub->mctx = zone->mctx; stub->zone = NULL; stub->db = NULL; stub->version = NULL; atomic_init(&stub->pending_requests, 0); /* * Attach so that the zone won't disappear from under us. */ zone_iattach(zone, &stub->zone); /* * If a db exists we will update it, otherwise we create a * new one and attach it to the zone once we have the NS * RRset and glue. */ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &stub->db); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); } else { ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); INSIST(zone->db_argc >= 1); result = dns_db_create(zone->mctx, zone->db_argv[0], &zone->origin, dns_dbtype_stub, zone->rdclass, zone->db_argc - 1, zone->db_argv + 1, &stub->db); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "refreshing stub: " "could not create " "database: %s", isc_result_totext(result)); goto cleanup; } dns_db_settask(stub->db, zone->task); } result = dns_db_newversion(stub->db, &stub->version); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "dns_db_newversion() failed: %s", isc_result_totext(result)); goto cleanup; } /* * Update SOA record. */ result = dns_db_findnode(stub->db, &zone->origin, true, &node); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "dns_db_findnode() failed: %s", isc_result_totext(result)); goto cleanup; } result = dns_db_addrdataset(stub->db, node, stub->version, 0, soardataset, 0, NULL); dns_db_detachnode(stub->db, &node); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: " "dns_db_addrdataset() failed: %s", isc_result_totext(result)); goto cleanup; } } /* * XXX Optimisation: Create message when zone is setup and reuse. */ result = create_query(zone, dns_rdatatype_ns, &zone->origin, &message); INSIST(result == ISC_R_SUCCESS); INSIST(zone->primariescnt > 0); INSIST(zone->curprimary < zone->primariescnt); zone->primaryaddr = zone->primaries[zone->curprimary]; isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr); /* * First, look for a tsig key in the primaries statement, then * try for a server key. */ if ((zone->primarykeynames != NULL) && (zone->primarykeynames[zone->curprimary] != NULL)) { dns_view_t *view = dns_zone_getview(zone); dns_name_t *keyname = zone->primarykeynames[zone->curprimary]; result = dns_view_gettsig(view, keyname, &key); if (result != ISC_R_SUCCESS) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(keyname, namebuf, sizeof(namebuf)); dns_zone_log(zone, ISC_LOG_ERROR, "unable to find key: %s", namebuf); } } if (key == NULL) { (void)dns_view_getpeertsig(zone->view, &primaryip, &key); } /* FIXME(OS): Do we need the transport here too? Most probably yes */ reqnsid = zone->view->requestnsid; if (zone->view->peers != NULL) { dns_peer_t *peer = NULL; bool edns; result = dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, &peer); if (result == ISC_R_SUCCESS) { result = dns_peer_getsupportedns(peer, &edns); if (result == ISC_R_SUCCESS && !edns) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS); } result = dns_peer_gettransfersource(peer, &zone->sourceaddr); if (result == ISC_R_SUCCESS) { have_xfrsource = true; } if (zone->view->resolver != NULL) { udpsize = dns_resolver_getudpsize( zone->view->resolver); } (void)dns_peer_getudpsize(peer, &udpsize); (void)dns_peer_getrequestnsid(peer, &reqnsid); } } if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { result = add_opt(message, udpsize, reqnsid, false); if (result != ISC_R_SUCCESS) { zone_debuglog(zone, me, 1, "unable to add opt record: %s", isc_result_totext(result)); } } /* * Always use TCP so that we shouldn't truncate in additional section. */ switch (isc_sockaddr_pf(&zone->primaryaddr)) { case PF_INET: if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { zone->sourceaddr = zone->altxfrsource4; } else if (!have_xfrsource) { zone->sourceaddr = zone->xfrsource4; } break; case PF_INET6: if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { zone->sourceaddr = zone->altxfrsource6; } else if (!have_xfrsource) { zone->sourceaddr = zone->xfrsource6; } break; default: result = ISC_R_NOTIMPLEMENTED; POST(result); goto cleanup; } timeout = 5; if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) { timeout = 30; } /* * Save request parameters so we can reuse them later on * for resolving missing glue A/AAAA records. */ cb_args = isc_mem_get(zone->mctx, sizeof(*cb_args)); cb_args->stub = stub; cb_args->tsig_key = key; cb_args->udpsize = udpsize; cb_args->timeout = timeout; cb_args->reqnsid = reqnsid; result = dns_request_create( zone->view->requestmgr, message, &zone->sourceaddr, &zone->primaryaddr, DNS_REQUESTOPT_TCP, key, timeout * 3 + 1, timeout, 2, zone->task, stub_callback, cb_args, &zone->request); if (result != ISC_R_SUCCESS) { zone_debuglog(zone, me, 1, "dns_request_create() failed: %s", isc_result_totext(result)); goto cleanup; } dns_message_detach(&message); goto unlock; cleanup: cancel_refresh(zone); stub->magic = 0; if (stub->version != NULL) { dns_db_closeversion(stub->db, &stub->version, false); } if (stub->db != NULL) { dns_db_detach(&stub->db); } if (stub->zone != NULL) { zone_idetach(&stub->zone); } isc_mem_put(stub->mctx, stub, sizeof(*stub)); if (message != NULL) { dns_message_detach(&message); } unlock: if (key != NULL) { dns_tsigkey_detach(&key); } return; } /* * Shut the zone down. */ static void zone_shutdown(isc_task_t *task, isc_event_t *event) { dns_zone_t *zone = (dns_zone_t *)event->ev_arg; bool free_needed, linked = false; dns_zone_t *raw = NULL, *secure = NULL; dns_view_t *view = NULL, *prev_view = NULL; UNUSED(task); REQUIRE(DNS_ZONE_VALID(zone)); INSIST(event->ev_type == DNS_EVENT_ZONECONTROL); INSIST(isc_refcount_current(&zone->erefs) == 0); zone_debuglog(zone, "zone_shutdown", 3, "shutting down"); /* * If we were waiting for xfrin quota, step out of * the queue. * If there's no zone manager, we can't be waiting for the * xfrin quota */ if (zone->zmgr != NULL) { RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); if (zone->statelist == &zone->zmgr->waiting_for_xfrin) { ISC_LIST_UNLINK(zone->zmgr->waiting_for_xfrin, zone, statelink); linked = true; zone->statelist = NULL; } if (zone->statelist == &zone->zmgr->xfrin_in_progress) { ISC_LIST_UNLINK(zone->zmgr->xfrin_in_progress, zone, statelink); zone->statelist = NULL; zmgr_resume_xfrs(zone->zmgr, false); } RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); } /* * In task context, no locking required. See zone_xfrdone(). */ if (zone->xfr != NULL) { /* The final detach will happen in zone_xfrdone() */ dns_xfrin_shutdown(zone->xfr); } /* Safe to release the zone now */ if (zone->zmgr != NULL) { dns_zonemgr_releasezone(zone->zmgr, zone); } LOCK_ZONE(zone); INSIST(zone != zone->raw); /* * Detach the views early, we don't need them anymore. However, we need * to detach them outside of the zone lock to break the lock loop * between view, adb and zone locks. */ view = zone->view; zone->view = NULL; prev_view = zone->prev_view; zone->prev_view = NULL; if (linked) { isc_refcount_decrement(&zone->irefs); } if (zone->request != NULL) { dns_request_cancel(zone->request); } if (zone->readio != NULL) { zonemgr_cancelio(zone->readio); } if (zone->lctx != NULL) { dns_loadctx_cancel(zone->lctx); } if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FLUSH) || !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { if (zone->writeio != NULL) { zonemgr_cancelio(zone->writeio); } if (zone->dctx != NULL) { dns_dumpctx_cancel(zone->dctx); } } checkds_cancel(zone); notify_cancel(zone); forward_cancel(zone); if (zone->timer != NULL) { isc_timer_destroy(&zone->timer); isc_refcount_decrement(&zone->irefs); } /* * We have now canceled everything set the flag to allow exit_check() * to succeed. We must not unlock between setting this flag and * calling exit_check(). */ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_SHUTDOWN); free_needed = exit_check(zone); /* * If a dump is in progress for the secure zone, defer detaching from * the raw zone as it may prevent the unsigned serial number from being * stored in the raw-format dump of the secure zone. In this scenario, * dump_done() takes care of cleaning up the zone->raw reference. */ if (inline_secure(zone) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { raw = zone->raw; zone->raw = NULL; } if (inline_raw(zone)) { secure = zone->secure; zone->secure = NULL; } UNLOCK_ZONE(zone); if (view != NULL) { dns_view_weakdetach(&view); } if (prev_view != NULL) { dns_view_weakdetach(&prev_view); } if (raw != NULL) { dns_zone_detach(&raw); } if (secure != NULL) { dns_zone_idetach(&secure); } if (free_needed) { zone_free(zone); } } static void zone_timer(isc_task_t *task, isc_event_t *event) { const char me[] = "zone_timer"; dns_zone_t *zone = (dns_zone_t *)event->ev_arg; UNUSED(task); REQUIRE(DNS_ZONE_VALID(zone)); ENTER; zone_maintenance(zone); isc_event_free(&event); } static void zone_settimer(dns_zone_t *zone, isc_time_t *now) { const char me[] = "zone_settimer"; isc_time_t next; isc_result_t result; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(LOCKED_ZONE(zone)); ENTER; if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { return; } isc_time_settoepoch(&next); switch (zone->type) { case dns_zone_redirect: if (zone->primaries != NULL) { goto treat_as_secondary; } FALLTHROUGH; case dns_zone_primary: if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) || DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) { next = zone->notifytime; } if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { INSIST(!isc_time_isepoch(&zone->dumptime)); if (isc_time_isepoch(&next) || isc_time_compare(&zone->dumptime, &next) < 0) { next = zone->dumptime; } } if (zone->type == dns_zone_redirect) { break; } if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING) && !isc_time_isepoch(&zone->refreshkeytime)) { if (isc_time_isepoch(&next) || isc_time_compare(&zone->refreshkeytime, &next) < 0) { next = zone->refreshkeytime; } } if (!isc_time_isepoch(&zone->resigntime)) { if (isc_time_isepoch(&next) || isc_time_compare(&zone->resigntime, &next) < 0) { next = zone->resigntime; } } if (!isc_time_isepoch(&zone->keywarntime)) { if (isc_time_isepoch(&next) || isc_time_compare(&zone->keywarntime, &next) < 0) { next = zone->keywarntime; } } if (!isc_time_isepoch(&zone->signingtime)) { if (isc_time_isepoch(&next) || isc_time_compare(&zone->signingtime, &next) < 0) { next = zone->signingtime; } } if (!isc_time_isepoch(&zone->nsec3chaintime)) { if (isc_time_isepoch(&next) || isc_time_compare(&zone->nsec3chaintime, &next) < 0) { next = zone->nsec3chaintime; } } break; case dns_zone_secondary: case dns_zone_mirror: treat_as_secondary: if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) || DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY)) { next = zone->notifytime; } FALLTHROUGH; case dns_zone_stub: if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOPRIMARIES) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOREFRESH) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADING) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING) && !isc_time_isepoch(&zone->refreshtime) && (isc_time_isepoch(&next) || isc_time_compare(&zone->refreshtime, &next) < 0)) { next = zone->refreshtime; } if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && !isc_time_isepoch(&zone->expiretime)) { if (isc_time_isepoch(&next) || isc_time_compare(&zone->expiretime, &next) < 0) { next = zone->expiretime; } } if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { INSIST(!isc_time_isepoch(&zone->dumptime)); if (isc_time_isepoch(&next) || isc_time_compare(&zone->dumptime, &next) < 0) { next = zone->dumptime; } } break; case dns_zone_key: if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDDUMP) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING)) { INSIST(!isc_time_isepoch(&zone->dumptime)); if (isc_time_isepoch(&next) || isc_time_compare(&zone->dumptime, &next) < 0) { next = zone->dumptime; } } if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING)) { if (isc_time_isepoch(&next) || (!isc_time_isepoch(&zone->refreshkeytime) && isc_time_compare(&zone->refreshkeytime, &next) < 0)) { next = zone->refreshkeytime; } } break; default: break; } if (isc_time_isepoch(&next)) { zone_debuglog(zone, me, 10, "settimer inactive"); result = isc_timer_reset(zone->timer, isc_timertype_inactive, NULL, NULL, true); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "could not deactivate zone timer: %s", isc_result_totext(result)); } } else { if (isc_time_compare(&next, now) <= 0) { next = *now; } result = isc_timer_reset(zone->timer, isc_timertype_once, &next, NULL, true); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "could not reset zone timer: %s", isc_result_totext(result)); } } } static void cancel_refresh(dns_zone_t *zone) { const char me[] = "cancel_refresh"; isc_time_t now; /* * 'zone' locked by caller. */ REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(LOCKED_ZONE(zone)); ENTER; DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); TIME_NOW(&now); zone_settimer(zone, &now); } static isc_result_t notify_createmessage(dns_zone_t *zone, unsigned int flags, dns_message_t **messagep) { dns_db_t *zonedb = NULL; dns_dbnode_t *node = NULL; dns_dbversion_t *version = NULL; dns_message_t *message = NULL; dns_rdataset_t rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; dns_name_t *tempname = NULL; dns_rdata_t *temprdata = NULL; dns_rdatalist_t *temprdatalist = NULL; dns_rdataset_t *temprdataset = NULL; isc_result_t result; isc_region_t r; isc_buffer_t *b = NULL; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(messagep != NULL && *messagep == NULL); dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message); message->opcode = dns_opcode_notify; message->flags |= DNS_MESSAGEFLAG_AA; message->rdclass = zone->rdclass; result = dns_message_gettempname(message, &tempname); if (result != ISC_R_SUCCESS) { goto cleanup; } result = dns_message_gettemprdataset(message, &temprdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } /* * Make question. */ dns_name_clone(&zone->origin, tempname); dns_rdataset_makequestion(temprdataset, zone->rdclass, dns_rdatatype_soa); ISC_LIST_APPEND(tempname->list, temprdataset, link); dns_message_addname(message, tempname, DNS_SECTION_QUESTION); tempname = NULL; temprdataset = NULL; if ((flags & DNS_NOTIFY_NOSOA) != 0) { goto done; } result = dns_message_gettempname(message, &tempname); if (result != ISC_R_SUCCESS) { goto soa_cleanup; } result = dns_message_gettemprdata(message, &temprdata); if (result != ISC_R_SUCCESS) { goto soa_cleanup; } result = dns_message_gettemprdataset(message, &temprdataset); if (result != ISC_R_SUCCESS) { goto soa_cleanup; } result = dns_message_gettemprdatalist(message, &temprdatalist); if (result != ISC_R_SUCCESS) { goto soa_cleanup; } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); INSIST(zone->db != NULL); /* XXXJT: is this assumption correct? */ dns_db_attach(zone->db, &zonedb); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); dns_name_clone(&zone->origin, tempname); dns_db_currentversion(zonedb, &version); result = dns_db_findnode(zonedb, tempname, false, &node); if (result != ISC_R_SUCCESS) { goto soa_cleanup; } dns_rdataset_init(&rdataset); result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_soa, dns_rdatatype_none, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { goto soa_cleanup; } result = dns_rdataset_first(&rdataset); if (result != ISC_R_SUCCESS) { goto soa_cleanup; } dns_rdataset_current(&rdataset, &rdata); dns_rdata_toregion(&rdata, &r); isc_buffer_allocate(zone->mctx, &b, r.length); isc_buffer_putmem(b, r.base, r.length); isc_buffer_usedregion(b, &r); dns_rdata_init(temprdata); dns_rdata_fromregion(temprdata, rdata.rdclass, rdata.type, &r); dns_message_takebuffer(message, &b); result = dns_rdataset_next(&rdataset); dns_rdataset_disassociate(&rdataset); if (result != ISC_R_NOMORE) { goto soa_cleanup; } temprdatalist->rdclass = rdata.rdclass; temprdatalist->type = rdata.type; temprdatalist->ttl = rdataset.ttl; ISC_LIST_APPEND(temprdatalist->rdata, temprdata, link); result = dns_rdatalist_tordataset(temprdatalist, temprdataset); if (result != ISC_R_SUCCESS) { goto soa_cleanup; } ISC_LIST_APPEND(tempname->list, temprdataset, link); dns_message_addname(message, tempname, DNS_SECTION_ANSWER); temprdatalist = NULL; temprdataset = NULL; temprdata = NULL; tempname = NULL; soa_cleanup: if (node != NULL) { dns_db_detachnode(zonedb, &node); } if (version != NULL) { dns_db_closeversion(zonedb, &version, false); } if (zonedb != NULL) { dns_db_detach(&zonedb); } if (tempname != NULL) { dns_message_puttempname(message, &tempname); } if (temprdata != NULL) { dns_message_puttemprdata(message, &temprdata); } if (temprdataset != NULL) { dns_message_puttemprdataset(message, &temprdataset); } if (temprdatalist != NULL) { dns_message_puttemprdatalist(message, &temprdatalist); } done: *messagep = message; return (ISC_R_SUCCESS); cleanup: if (tempname != NULL) { dns_message_puttempname(message, &tempname); } if (temprdataset != NULL) { dns_message_puttemprdataset(message, &temprdataset); } dns_message_detach(&message); return (result); } isc_result_t dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from, isc_sockaddr_t *to, dns_message_t *msg) { unsigned int i; dns_rdata_soa_t soa; dns_rdataset_t *rdataset = NULL; dns_rdata_t rdata = DNS_RDATA_INIT; isc_result_t result; char fromtext[ISC_SOCKADDR_FORMATSIZE]; int match = 0; isc_netaddr_t netaddr; uint32_t serial = 0; bool have_serial = false; dns_tsigkey_t *tsigkey; const dns_name_t *tsig; REQUIRE(DNS_ZONE_VALID(zone)); /* * If type != T_SOA return DNS_R_NOTIMP. We don't yet support * ROLLOVER. * * SOA: RFC1996 * Check that 'from' is a valid notify source, (zone->primaries). * Return DNS_R_REFUSED if not. * * If the notify message contains a serial number check it * against the zones serial and return if <= current serial * * If a refresh check is progress, if so just record the * fact we received a NOTIFY and from where and return. * We will perform a new refresh check when the current one * completes. Return ISC_R_SUCCESS. * * Otherwise initiate a refresh check using 'from' as the * first address to check. Return ISC_R_SUCCESS. */ isc_sockaddr_format(from, fromtext, sizeof(fromtext)); /* * Notify messages are processed by the raw zone. */ LOCK_ZONE(zone); INSIST(zone != zone->raw); if (inline_secure(zone)) { result = dns_zone_notifyreceive(zone->raw, from, to, msg); UNLOCK_ZONE(zone); return (result); } /* * We only handle NOTIFY (SOA) at the present. */ if (isc_sockaddr_pf(from) == PF_INET) { inc_stats(zone, dns_zonestatscounter_notifyinv4); } else { inc_stats(zone, dns_zonestatscounter_notifyinv6); } if (msg->counts[DNS_SECTION_QUESTION] == 0 || dns_message_findname(msg, DNS_SECTION_QUESTION, &zone->origin, dns_rdatatype_soa, dns_rdatatype_none, NULL, NULL) != ISC_R_SUCCESS) { UNLOCK_ZONE(zone); if (msg->counts[DNS_SECTION_QUESTION] == 0) { dns_zone_log(zone, ISC_LOG_NOTICE, "NOTIFY with no " "question section from: %s", fromtext); return (DNS_R_FORMERR); } dns_zone_log(zone, ISC_LOG_NOTICE, "NOTIFY zone does not match"); return (DNS_R_NOTIMP); } /* * If we are a primary zone just succeed. */ if (zone->type == dns_zone_primary) { UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_netaddr_fromsockaddr(&netaddr, from); for (i = 0; i < zone->primariescnt; i++) { if (isc_sockaddr_eqaddr(from, &zone->primaries[i])) { break; } if (zone->view->aclenv->match_mapped && IN6_IS_ADDR_V4MAPPED(&from->type.sin6.sin6_addr) && isc_sockaddr_pf(&zone->primaries[i]) == AF_INET) { isc_netaddr_t na1, na2; isc_netaddr_fromv4mapped(&na1, &netaddr); isc_netaddr_fromsockaddr(&na2, &zone->primaries[i]); if (isc_netaddr_equal(&na1, &na2)) { break; } } } /* * Accept notify requests from non primaries if they are on * 'zone->notify_acl'. */ tsigkey = dns_message_gettsigkey(msg); tsig = dns_tsigkey_identity(tsigkey); if (i >= zone->primariescnt && zone->notify_acl != NULL && (dns_acl_match(&netaddr, tsig, zone->notify_acl, zone->view->aclenv, &match, NULL) == ISC_R_SUCCESS) && match > 0) { /* Accept notify. */ } else if (i >= zone->primariescnt) { UNLOCK_ZONE(zone); dns_zone_log(zone, ISC_LOG_INFO, "refused notify from non-primary: %s", fromtext); inc_stats(zone, dns_zonestatscounter_notifyrej); return (DNS_R_REFUSED); } /* * If the zone is loaded and there are answers check the serial * to see if we need to do a refresh. Do not worry about this * check if we are a dialup zone as we use the notify request * to trigger a refresh check. */ if (msg->counts[DNS_SECTION_ANSWER] > 0 && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOREFRESH)) { result = dns_message_findname( msg, DNS_SECTION_ANSWER, &zone->origin, dns_rdatatype_soa, dns_rdatatype_none, NULL, &rdataset); if (result == ISC_R_SUCCESS) { result = dns_rdataset_first(rdataset); } if (result == ISC_R_SUCCESS) { uint32_t oldserial; unsigned int soacount; dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &soa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); serial = soa.serial; have_serial = true; /* * The following should safely be performed without DB * lock and succeed in this context. */ result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, &oldserial, NULL, NULL, NULL, NULL, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); RUNTIME_CHECK(soacount > 0U); if (isc_serial_le(serial, oldserial)) { dns_zone_log(zone, ISC_LOG_INFO, "notify from %s: " "zone is up to date", fromtext); UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } } } /* * If we got this far and there was a refresh in progress just * let it complete. Record where we got the notify from so we * can perform a refresh check when the current one completes */ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDREFRESH); zone->notifyfrom = *from; UNLOCK_ZONE(zone); if (have_serial) { dns_zone_log(zone, ISC_LOG_INFO, "notify from %s: serial %u: refresh in " "progress, refresh check queued", fromtext, serial); } else { dns_zone_log(zone, ISC_LOG_INFO, "notify from %s: refresh in progress, " "refresh check queued", fromtext); } return (ISC_R_SUCCESS); } if (have_serial) { dns_zone_log(zone, ISC_LOG_INFO, "notify from %s: serial %u", fromtext, serial); } else { dns_zone_log(zone, ISC_LOG_INFO, "notify from %s: no serial", fromtext); } zone->notifyfrom = *from; UNLOCK_ZONE(zone); if (to != NULL) { dns_zonemgr_unreachabledel(zone->zmgr, from, to); } dns_zone_refresh(zone); return (ISC_R_SUCCESS); } void dns_zone_setnotifyacl(dns_zone_t *zone, dns_acl_t *acl) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->notify_acl != NULL) { dns_acl_detach(&zone->notify_acl); } dns_acl_attach(acl, &zone->notify_acl); UNLOCK_ZONE(zone); } void dns_zone_setqueryacl(dns_zone_t *zone, dns_acl_t *acl) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->query_acl != NULL) { dns_acl_detach(&zone->query_acl); } dns_acl_attach(acl, &zone->query_acl); UNLOCK_ZONE(zone); } void dns_zone_setqueryonacl(dns_zone_t *zone, dns_acl_t *acl) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->queryon_acl != NULL) { dns_acl_detach(&zone->queryon_acl); } dns_acl_attach(acl, &zone->queryon_acl); UNLOCK_ZONE(zone); } void dns_zone_setupdateacl(dns_zone_t *zone, dns_acl_t *acl) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->update_acl != NULL) { dns_acl_detach(&zone->update_acl); } dns_acl_attach(acl, &zone->update_acl); UNLOCK_ZONE(zone); } void dns_zone_setforwardacl(dns_zone_t *zone, dns_acl_t *acl) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->forward_acl != NULL) { dns_acl_detach(&zone->forward_acl); } dns_acl_attach(acl, &zone->forward_acl); UNLOCK_ZONE(zone); } void dns_zone_setxfracl(dns_zone_t *zone, dns_acl_t *acl) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->xfr_acl != NULL) { dns_acl_detach(&zone->xfr_acl); } dns_acl_attach(acl, &zone->xfr_acl); UNLOCK_ZONE(zone); } dns_acl_t * dns_zone_getnotifyacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->notify_acl); } dns_acl_t * dns_zone_getqueryacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->query_acl); } dns_acl_t * dns_zone_getqueryonacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->queryon_acl); } dns_acl_t * dns_zone_getupdateacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->update_acl); } dns_acl_t * dns_zone_getforwardacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->forward_acl); } dns_acl_t * dns_zone_getxfracl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->xfr_acl); } void dns_zone_clearupdateacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->update_acl != NULL) { dns_acl_detach(&zone->update_acl); } UNLOCK_ZONE(zone); } void dns_zone_clearforwardacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->forward_acl != NULL) { dns_acl_detach(&zone->forward_acl); } UNLOCK_ZONE(zone); } void dns_zone_clearnotifyacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->notify_acl != NULL) { dns_acl_detach(&zone->notify_acl); } UNLOCK_ZONE(zone); } void dns_zone_clearqueryacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->query_acl != NULL) { dns_acl_detach(&zone->query_acl); } UNLOCK_ZONE(zone); } void dns_zone_clearqueryonacl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->queryon_acl != NULL) { dns_acl_detach(&zone->queryon_acl); } UNLOCK_ZONE(zone); } void dns_zone_clearxfracl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->xfr_acl != NULL) { dns_acl_detach(&zone->xfr_acl); } UNLOCK_ZONE(zone); } bool dns_zone_getupdatedisabled(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->update_disabled); } void dns_zone_setupdatedisabled(dns_zone_t *zone, bool state) { REQUIRE(DNS_ZONE_VALID(zone)); zone->update_disabled = state; } bool dns_zone_getzeronosoattl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->zero_no_soa_ttl); } void dns_zone_setzeronosoattl(dns_zone_t *zone, bool state) { REQUIRE(DNS_ZONE_VALID(zone)); zone->zero_no_soa_ttl = state; } void dns_zone_setchecknames(dns_zone_t *zone, dns_severity_t severity) { REQUIRE(DNS_ZONE_VALID(zone)); zone->check_names = severity; } dns_severity_t dns_zone_getchecknames(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->check_names); } void dns_zone_setjournalsize(dns_zone_t *zone, int32_t size) { REQUIRE(DNS_ZONE_VALID(zone)); zone->journalsize = size; } int32_t dns_zone_getjournalsize(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->journalsize); } static void zone_namerd_tostr(dns_zone_t *zone, char *buf, size_t length) { isc_result_t result = ISC_R_FAILURE; isc_buffer_t buffer; REQUIRE(buf != NULL); REQUIRE(length > 1U); /* * Leave space for terminating '\0'. */ isc_buffer_init(&buffer, buf, (unsigned int)length - 1); if (zone->type != dns_zone_redirect && zone->type != dns_zone_key) { if (dns_name_dynamic(&zone->origin)) { result = dns_name_totext(&zone->origin, true, &buffer); } if (result != ISC_R_SUCCESS && isc_buffer_availablelength(&buffer) >= (sizeof("") - 1)) { isc_buffer_putstr(&buffer, ""); } if (isc_buffer_availablelength(&buffer) > 0) { isc_buffer_putstr(&buffer, "/"); } (void)dns_rdataclass_totext(zone->rdclass, &buffer); } if (zone->view != NULL && strcmp(zone->view->name, "_bind") != 0 && strcmp(zone->view->name, "_default") != 0 && strlen(zone->view->name) < isc_buffer_availablelength(&buffer)) { isc_buffer_putstr(&buffer, "/"); isc_buffer_putstr(&buffer, zone->view->name); } if (inline_secure(zone) && 9U < isc_buffer_availablelength(&buffer)) { isc_buffer_putstr(&buffer, " (signed)"); } if (inline_raw(zone) && 11U < isc_buffer_availablelength(&buffer)) { isc_buffer_putstr(&buffer, " (unsigned)"); } buf[isc_buffer_usedlength(&buffer)] = '\0'; } static void zone_name_tostr(dns_zone_t *zone, char *buf, size_t length) { isc_result_t result = ISC_R_FAILURE; isc_buffer_t buffer; REQUIRE(buf != NULL); REQUIRE(length > 1U); /* * Leave space for terminating '\0'. */ isc_buffer_init(&buffer, buf, (unsigned int)length - 1); if (dns_name_dynamic(&zone->origin)) { result = dns_name_totext(&zone->origin, true, &buffer); } if (result != ISC_R_SUCCESS && isc_buffer_availablelength(&buffer) >= (sizeof("") - 1)) { isc_buffer_putstr(&buffer, ""); } buf[isc_buffer_usedlength(&buffer)] = '\0'; } static void zone_rdclass_tostr(dns_zone_t *zone, char *buf, size_t length) { isc_buffer_t buffer; REQUIRE(buf != NULL); REQUIRE(length > 1U); /* * Leave space for terminating '\0'. */ isc_buffer_init(&buffer, buf, (unsigned int)length - 1); (void)dns_rdataclass_totext(zone->rdclass, &buffer); buf[isc_buffer_usedlength(&buffer)] = '\0'; } static void zone_viewname_tostr(dns_zone_t *zone, char *buf, size_t length) { isc_buffer_t buffer; REQUIRE(buf != NULL); REQUIRE(length > 1U); /* * Leave space for terminating '\0'. */ isc_buffer_init(&buffer, buf, (unsigned int)length - 1); if (zone->view == NULL) { isc_buffer_putstr(&buffer, "_none"); } else if (strlen(zone->view->name) < isc_buffer_availablelength(&buffer)) { isc_buffer_putstr(&buffer, zone->view->name); } else { isc_buffer_putstr(&buffer, "_toolong"); } buf[isc_buffer_usedlength(&buffer)] = '\0'; } void dns_zone_name(dns_zone_t *zone, char *buf, size_t length) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(buf != NULL); LOCK_ZONE(zone); zone_namerd_tostr(zone, buf, length); UNLOCK_ZONE(zone); } void dns_zone_nameonly(dns_zone_t *zone, char *buf, size_t length) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(buf != NULL); zone_name_tostr(zone, buf, length); } void dns_zone_logv(dns_zone_t *zone, isc_logcategory_t *category, int level, const char *prefix, const char *fmt, va_list ap) { char message[4096]; const char *zstr; REQUIRE(DNS_ZONE_VALID(zone)); if (!isc_log_wouldlog(dns_lctx, level)) { return; } vsnprintf(message, sizeof(message), fmt, ap); switch (zone->type) { case dns_zone_key: zstr = "managed-keys-zone"; break; case dns_zone_redirect: zstr = "redirect-zone"; break; default: zstr = "zone "; } isc_log_write(dns_lctx, category, DNS_LOGMODULE_ZONE, level, "%s%s%s%s: %s", (prefix != NULL ? prefix : ""), (prefix != NULL ? ": " : ""), zstr, zone->strnamerd, message); } static void notify_log(dns_zone_t *zone, int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); dns_zone_logv(zone, DNS_LOGCATEGORY_NOTIFY, level, NULL, fmt, ap); va_end(ap); } void dns_zone_logc(dns_zone_t *zone, isc_logcategory_t *category, int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); dns_zone_logv(zone, category, level, NULL, fmt, ap); va_end(ap); } void dns_zone_log(dns_zone_t *zone, int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); dns_zone_logv(zone, DNS_LOGCATEGORY_GENERAL, level, NULL, fmt, ap); va_end(ap); } static void zone_debuglog(dns_zone_t *zone, const char *me, int debuglevel, const char *fmt, ...) { int level = ISC_LOG_DEBUG(debuglevel); va_list ap; va_start(ap, fmt); dns_zone_logv(zone, DNS_LOGCATEGORY_GENERAL, level, me, fmt, ap); va_end(ap); } static void dnssec_log(dns_zone_t *zone, int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); dns_zone_logv(zone, DNS_LOGCATEGORY_DNSSEC, level, NULL, fmt, ap); va_end(ap); } static int message_count(dns_message_t *msg, dns_section_t section, dns_rdatatype_t type) { isc_result_t result; dns_name_t *name; dns_rdataset_t *curr; int count = 0; result = dns_message_firstname(msg, section); while (result == ISC_R_SUCCESS) { name = NULL; dns_message_currentname(msg, section, &name); for (curr = ISC_LIST_TAIL(name->list); curr != NULL; curr = ISC_LIST_PREV(curr, link)) { if (curr->type == type) { count++; } } result = dns_message_nextname(msg, section); } return (count); } void dns_zone_setmaxxfrin(dns_zone_t *zone, uint32_t maxxfrin) { REQUIRE(DNS_ZONE_VALID(zone)); zone->maxxfrin = maxxfrin; } uint32_t dns_zone_getmaxxfrin(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->maxxfrin); } void dns_zone_setmaxxfrout(dns_zone_t *zone, uint32_t maxxfrout) { REQUIRE(DNS_ZONE_VALID(zone)); zone->maxxfrout = maxxfrout; } uint32_t dns_zone_getmaxxfrout(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->maxxfrout); } dns_zonetype_t dns_zone_gettype(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->type); } const char * dns_zonetype_name(dns_zonetype_t type) { switch (type) { case dns_zone_none: return ("none"); case dns_zone_primary: return ("primary"); case dns_zone_secondary: return ("secondary"); case dns_zone_mirror: return ("mirror"); case dns_zone_stub: return ("stub"); case dns_zone_staticstub: return ("static-stub"); case dns_zone_key: return ("key"); case dns_zone_dlz: return ("dlz"); case dns_zone_redirect: return ("redirect"); default: return ("unknown"); } } dns_zonetype_t dns_zone_getredirecttype(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(zone->type == dns_zone_redirect); return (zone->primaries == NULL ? dns_zone_primary : dns_zone_secondary); } dns_name_t * dns_zone_getorigin(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (&zone->origin); } void dns_zone_settask(dns_zone_t *zone, isc_task_t *task) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->task != NULL) { isc_task_detach(&zone->task); } isc_task_attach(task, &zone->task); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_settask(zone->db, zone->task); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); UNLOCK_ZONE(zone); } void dns_zone_gettask(dns_zone_t *zone, isc_task_t **target) { REQUIRE(DNS_ZONE_VALID(zone)); isc_task_attach(zone->task, target); } void dns_zone_setidlein(dns_zone_t *zone, uint32_t idlein) { REQUIRE(DNS_ZONE_VALID(zone)); if (idlein == 0) { idlein = DNS_DEFAULT_IDLEIN; } zone->idlein = idlein; } uint32_t dns_zone_getidlein(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->idlein); } void dns_zone_setidleout(dns_zone_t *zone, uint32_t idleout) { REQUIRE(DNS_ZONE_VALID(zone)); zone->idleout = idleout; } uint32_t dns_zone_getidleout(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->idleout); } static void notify_done(isc_task_t *task, isc_event_t *event) { dns_requestevent_t *revent = (dns_requestevent_t *)event; dns_notify_t *notify; isc_result_t result; dns_message_t *message = NULL; isc_buffer_t buf; char rcode[128]; char addrbuf[ISC_SOCKADDR_FORMATSIZE]; UNUSED(task); notify = event->ev_arg; REQUIRE(DNS_NOTIFY_VALID(notify)); INSIST(task == notify->zone->task); isc_buffer_init(&buf, rcode, sizeof(rcode)); isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf)); dns_message_create(notify->zone->mctx, DNS_MESSAGE_INTENTPARSE, &message); if (revent->result != ISC_R_SUCCESS) { result = revent->result; goto fail; } result = dns_request_getresponse(revent->request, message, DNS_MESSAGEPARSE_PRESERVEORDER); if (result != ISC_R_SUCCESS) { goto fail; } result = dns_rcode_totext(message->rcode, &buf); if (result == ISC_R_SUCCESS) { notify_log(notify->zone, ISC_LOG_DEBUG(3), "notify response from %s: %.*s", addrbuf, (int)buf.used, rcode); } goto done; fail: notify_log(notify->zone, ISC_LOG_DEBUG(2), "notify to %s failed: %s", addrbuf, isc_result_totext(result)); if (result == ISC_R_TIMEDOUT) { notify_log(notify->zone, ISC_LOG_DEBUG(1), "notify to %s: retries exceeded", addrbuf); } done: notify_destroy(notify, false); isc_event_free(&event); dns_message_detach(&message); } struct secure_event { isc_event_t e; dns_db_t *db; uint32_t serial; }; static void update_log_cb(void *arg, dns_zone_t *zone, int level, const char *message) { UNUSED(arg); dns_zone_log(zone, level, "%s", message); } static isc_result_t sync_secure_journal(dns_zone_t *zone, dns_zone_t *raw, dns_journal_t *journal, uint32_t start, uint32_t end, dns_difftuple_t **soatuplep, dns_diff_t *diff) { isc_result_t result; dns_difftuple_t *tuple = NULL; dns_diffop_t op = DNS_DIFFOP_ADD; int n_soa = 0; REQUIRE(soatuplep != NULL); if (start == end) { return (DNS_R_UNCHANGED); } CHECK(dns_journal_iter_init(journal, start, end, NULL)); for (result = dns_journal_first_rr(journal); result == ISC_R_SUCCESS; result = dns_journal_next_rr(journal)) { dns_name_t *name = NULL; uint32_t ttl; dns_rdata_t *rdata = NULL; dns_journal_current_rr(journal, &name, &ttl, &rdata); if (rdata->type == dns_rdatatype_soa) { n_soa++; if (n_soa == 2) { /* * Save the latest raw SOA record. */ if (*soatuplep != NULL) { dns_difftuple_free(soatuplep); } CHECK(dns_difftuple_create( diff->mctx, DNS_DIFFOP_ADD, name, ttl, rdata, soatuplep)); } if (n_soa == 3) { n_soa = 1; } continue; } /* Sanity. */ if (n_soa == 0) { dns_zone_log(raw, ISC_LOG_ERROR, "corrupt journal file: '%s'\n", raw->journal); return (ISC_R_FAILURE); } if (zone->privatetype != 0 && rdata->type == zone->privatetype) { continue; } if (rdata->type == dns_rdatatype_nsec || rdata->type == dns_rdatatype_rrsig || rdata->type == dns_rdatatype_nsec3 || rdata->type == dns_rdatatype_dnskey || rdata->type == dns_rdatatype_nsec3param) { continue; } op = (n_soa == 1) ? DNS_DIFFOP_DEL : DNS_DIFFOP_ADD; CHECK(dns_difftuple_create(diff->mctx, op, name, ttl, rdata, &tuple)); dns_diff_appendminimal(diff, &tuple); } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } failure: return (result); } static isc_result_t sync_secure_db(dns_zone_t *seczone, dns_zone_t *raw, dns_db_t *secdb, dns_dbversion_t *secver, dns_difftuple_t **soatuple, dns_diff_t *diff) { isc_result_t result; dns_db_t *rawdb = NULL; dns_dbversion_t *rawver = NULL; dns_difftuple_t *tuple = NULL, *next; dns_difftuple_t *oldtuple = NULL, *newtuple = NULL; dns_rdata_soa_t oldsoa, newsoa; REQUIRE(DNS_ZONE_VALID(seczone)); REQUIRE(soatuple != NULL && *soatuple == NULL); if (!seczone->sourceserialset) { return (DNS_R_UNCHANGED); } dns_db_attach(raw->db, &rawdb); dns_db_currentversion(rawdb, &rawver); result = dns_db_diffx(diff, rawdb, rawver, secdb, secver, NULL); dns_db_closeversion(rawdb, &rawver, false); dns_db_detach(&rawdb); if (result != ISC_R_SUCCESS) { return (result); } for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; tuple = next) { next = ISC_LIST_NEXT(tuple, link); if (tuple->rdata.type == dns_rdatatype_nsec || tuple->rdata.type == dns_rdatatype_rrsig || tuple->rdata.type == dns_rdatatype_dnskey || tuple->rdata.type == dns_rdatatype_nsec3 || tuple->rdata.type == dns_rdatatype_nsec3param) { ISC_LIST_UNLINK(diff->tuples, tuple, link); dns_difftuple_free(&tuple); continue; } if (tuple->rdata.type == dns_rdatatype_soa) { if (tuple->op == DNS_DIFFOP_DEL) { INSIST(oldtuple == NULL); oldtuple = tuple; } if (tuple->op == DNS_DIFFOP_ADD) { INSIST(newtuple == NULL); newtuple = tuple; } } } if (oldtuple != NULL && newtuple != NULL) { result = dns_rdata_tostruct(&oldtuple->rdata, &oldsoa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); result = dns_rdata_tostruct(&newtuple->rdata, &newsoa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); /* * If the SOA records are the same except for the serial * remove them from the diff. */ if (oldtuple->ttl == newtuple->ttl && oldsoa.refresh == newsoa.refresh && oldsoa.retry == newsoa.retry && oldsoa.minimum == newsoa.minimum && oldsoa.expire == newsoa.expire && dns_name_equal(&oldsoa.origin, &newsoa.origin) && dns_name_equal(&oldsoa.contact, &newsoa.contact)) { ISC_LIST_UNLINK(diff->tuples, oldtuple, link); dns_difftuple_free(&oldtuple); ISC_LIST_UNLINK(diff->tuples, newtuple, link); dns_difftuple_free(&newtuple); } } if (ISC_LIST_EMPTY(diff->tuples)) { return (DNS_R_UNCHANGED); } /* * If there are still SOA records in the diff they can now be removed * saving the new SOA record. */ if (oldtuple != NULL) { ISC_LIST_UNLINK(diff->tuples, oldtuple, link); dns_difftuple_free(&oldtuple); } if (newtuple != NULL) { ISC_LIST_UNLINK(diff->tuples, newtuple, link); *soatuple = newtuple; } return (ISC_R_SUCCESS); } static void receive_secure_serial(isc_task_t *task, isc_event_t *event) { static char me[] = "receive_secure_serial"; isc_result_t result = ISC_R_SUCCESS; dns_journal_t *rjournal = NULL; dns_journal_t *sjournal = NULL; uint32_t start, end; dns_zone_t *zone; dns_difftuple_t *tuple = NULL, *soatuple = NULL; dns_update_log_t log = { update_log_cb, NULL }; uint32_t newserial = 0, desired = 0; isc_time_t timenow; int level = ISC_LOG_ERROR; UNUSED(task); zone = event->ev_arg; end = ((struct secure_event *)event)->serial; ENTER; LOCK_ZONE(zone); /* * If we are already processing a receive secure serial event * for the zone, just queue the new one and exit. */ if (zone->rss_event != NULL && zone->rss_event != event) { ISC_LIST_APPEND(zone->rss_events, event, ev_link); UNLOCK_ZONE(zone); return; } nextevent: if (zone->rss_event != NULL) { INSIST(zone->rss_event == event); UNLOCK_ZONE(zone); } else { zone->rss_event = event; dns_diff_init(zone->mctx, &zone->rss_diff); /* * zone->db may be NULL, if the load from disk failed. */ result = ISC_R_SUCCESS; ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &zone->rss_db); } else { result = ISC_R_FAILURE; } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (result == ISC_R_SUCCESS && zone->raw != NULL) { dns_zone_attach(zone->raw, &zone->rss_raw); } else { result = ISC_R_FAILURE; } UNLOCK_ZONE(zone); CHECK(result); /* * We first attempt to sync the raw zone to the secure zone * by using the raw zone's journal, applying all the deltas * from the latest source-serial of the secure zone up to * the current serial number of the raw zone. * * If that fails, then we'll fall back to a direct comparison * between raw and secure zones. */ CHECK(dns_journal_open(zone->rss_raw->mctx, zone->rss_raw->journal, DNS_JOURNAL_WRITE, &rjournal)); result = dns_journal_open(zone->mctx, zone->journal, DNS_JOURNAL_READ, &sjournal); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { goto failure; } if (!dns_journal_get_sourceserial(rjournal, &start)) { start = dns_journal_first_serial(rjournal); dns_journal_set_sourceserial(rjournal, start); } if (sjournal != NULL) { uint32_t serial; /* * We read the secure journal first, if that * exists use its value provided it is greater * that from the raw journal. */ if (dns_journal_get_sourceserial(sjournal, &serial)) { if (isc_serial_gt(serial, start)) { start = serial; } } dns_journal_destroy(&sjournal); } dns_db_currentversion(zone->rss_db, &zone->rss_oldver); CHECK(dns_db_newversion(zone->rss_db, &zone->rss_newver)); /* * Try to apply diffs from the raw zone's journal to the secure * zone. If that fails, we recover by syncing up the databases * directly. */ result = sync_secure_journal(zone, zone->rss_raw, rjournal, start, end, &soatuple, &zone->rss_diff); if (result == DNS_R_UNCHANGED) { goto failure; } else if (result != ISC_R_SUCCESS) { CHECK(sync_secure_db(zone, zone->rss_raw, zone->rss_db, zone->rss_oldver, &soatuple, &zone->rss_diff)); } CHECK(dns_diff_apply(&zone->rss_diff, zone->rss_db, zone->rss_newver)); if (soatuple != NULL) { uint32_t oldserial; CHECK(dns_db_createsoatuple( zone->rss_db, zone->rss_oldver, zone->rss_diff.mctx, DNS_DIFFOP_DEL, &tuple)); oldserial = dns_soa_getserial(&tuple->rdata); newserial = desired = dns_soa_getserial(&soatuple->rdata); if (!isc_serial_gt(newserial, oldserial)) { newserial = oldserial + 1; if (newserial == 0) { newserial++; } dns_soa_setserial(newserial, &soatuple->rdata); } CHECK(do_one_tuple(&tuple, zone->rss_db, zone->rss_newver, &zone->rss_diff)); CHECK(do_one_tuple(&soatuple, zone->rss_db, zone->rss_newver, &zone->rss_diff)); } else { CHECK(update_soa_serial(zone, zone->rss_db, zone->rss_newver, &zone->rss_diff, zone->mctx, zone->updatemethod)); } } result = dns_update_signaturesinc( &log, zone, zone->rss_db, zone->rss_oldver, zone->rss_newver, &zone->rss_diff, zone->sigvalidityinterval, &zone->rss_state); if (result == DNS_R_CONTINUE) { if (rjournal != NULL) { dns_journal_destroy(&rjournal); } isc_task_send(task, &event); return; } /* * If something went wrong while trying to update the secure zone and * the latter was already signed before, do not apply raw zone deltas * to it as that would break existing DNSSEC signatures. However, if * the secure zone was not yet signed (e.g. because no signing keys * were created for it), commence applying raw zone deltas to it so * that contents of the raw zone and the secure zone are kept in sync. */ if (result != ISC_R_SUCCESS && dns_db_issecure(zone->rss_db)) { goto failure; } if (rjournal == NULL) { CHECK(dns_journal_open(zone->rss_raw->mctx, zone->rss_raw->journal, DNS_JOURNAL_WRITE, &rjournal)); } CHECK(zone_journal(zone, &zone->rss_diff, &end, "receive_secure_serial")); dns_journal_set_sourceserial(rjournal, end); dns_journal_commit(rjournal); LOCK_ZONE(zone); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); zone->sourceserial = end; zone->sourceserialset = true; zone_needdump(zone, DNS_DUMP_DELAY); /* * Set resign time to make sure it is set to the earliest * signature expiration. */ set_resigntime(zone); TIME_NOW(&timenow); zone_settimer(zone, &timenow); UNLOCK_ZONE(zone); dns_db_closeversion(zone->rss_db, &zone->rss_oldver, false); dns_db_closeversion(zone->rss_db, &zone->rss_newver, true); if (newserial != 0) { dns_zone_log(zone, ISC_LOG_INFO, "serial %u (unsigned %u)", newserial, desired); } failure: isc_event_free(&zone->rss_event); event = ISC_LIST_HEAD(zone->rss_events); if (zone->rss_raw != NULL) { dns_zone_detach(&zone->rss_raw); } if (result != ISC_R_SUCCESS) { LOCK_ZONE(zone); set_resigntime(zone); TIME_NOW(&timenow); zone_settimer(zone, &timenow); UNLOCK_ZONE(zone); if (result == DNS_R_UNCHANGED) { level = ISC_LOG_INFO; } dns_zone_log(zone, level, "receive_secure_serial: %s", isc_result_totext(result)); } if (tuple != NULL) { dns_difftuple_free(&tuple); } if (soatuple != NULL) { dns_difftuple_free(&soatuple); } if (zone->rss_db != NULL) { if (zone->rss_oldver != NULL) { dns_db_closeversion(zone->rss_db, &zone->rss_oldver, false); } if (zone->rss_newver != NULL) { dns_db_closeversion(zone->rss_db, &zone->rss_newver, false); } dns_db_detach(&zone->rss_db); } INSIST(zone->rss_oldver == NULL); INSIST(zone->rss_newver == NULL); if (rjournal != NULL) { dns_journal_destroy(&rjournal); } dns_diff_clear(&zone->rss_diff); if (event != NULL) { LOCK_ZONE(zone); isc_refcount_decrement(&zone->irefs); ISC_LIST_UNLINK(zone->rss_events, event, ev_link); goto nextevent; } event = ISC_LIST_HEAD(zone->rss_post); while (event != NULL) { ISC_LIST_UNLINK(zone->rss_post, event, ev_link); rss_post(zone, event); event = ISC_LIST_HEAD(zone->rss_post); } dns_zone_idetach(&zone); } static isc_result_t zone_send_secureserial(dns_zone_t *zone, uint32_t serial) { isc_event_t *e; dns_zone_t *dummy = NULL; e = isc_event_allocate(zone->secure->mctx, zone, DNS_EVENT_ZONESECURESERIAL, receive_secure_serial, zone->secure, sizeof(struct secure_event)); ((struct secure_event *)e)->serial = serial; INSIST(LOCKED_ZONE(zone->secure)); zone_iattach(zone->secure, &dummy); isc_task_send(zone->secure->task, &e); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SENDSECURE); return (ISC_R_SUCCESS); } static isc_result_t checkandaddsoa(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, dns_rdataset_t *rdataset, uint32_t oldserial) { dns_rdata_soa_t soa; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdatalist_t temprdatalist; dns_rdataset_t temprdataset; isc_buffer_t b; isc_result_t result; unsigned char buf[DNS_SOA_BUFFERSIZE]; dns_fixedname_t fixed; dns_name_t *name; result = dns_rdataset_first(rdataset); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &soa, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (isc_serial_gt(soa.serial, oldserial)) { return (dns_db_addrdataset(db, node, version, 0, rdataset, 0, NULL)); } /* * Always bump the serial. */ oldserial++; if (oldserial == 0) { oldserial++; } soa.serial = oldserial; /* * Construct a replacement rdataset. */ dns_rdata_reset(&rdata); isc_buffer_init(&b, buf, sizeof(buf)); result = dns_rdata_fromstruct(&rdata, rdataset->rdclass, dns_rdatatype_soa, &soa, &b); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdatalist_init(&temprdatalist); temprdatalist.rdclass = rdata.rdclass; temprdatalist.type = rdata.type; temprdatalist.ttl = rdataset->ttl; ISC_LIST_APPEND(temprdatalist.rdata, &rdata, link); dns_rdataset_init(&temprdataset); result = dns_rdatalist_tordataset(&temprdatalist, &temprdataset); RUNTIME_CHECK(result == ISC_R_SUCCESS); name = dns_fixedname_initname(&fixed); result = dns_db_nodefullname(db, node, name); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdataset_getownercase(rdataset, name); dns_rdataset_setownercase(&temprdataset, name); return (dns_db_addrdataset(db, node, version, 0, &temprdataset, 0, NULL)); } /* * This function should populate an nsec3paramlist_t with the * nsecparam_t data from a zone. */ static isc_result_t save_nsec3param(dns_zone_t *zone, nsec3paramlist_t *nsec3list) { isc_result_t result; dns_dbnode_t *node = NULL; dns_rdataset_t rdataset, prdataset; dns_dbversion_t *version = NULL; nsec3param_t *nsec3param = NULL; nsec3param_t *nsec3p = NULL; nsec3param_t *next; dns_db_t *db = NULL; unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(nsec3list != NULL); REQUIRE(ISC_LIST_EMPTY(*nsec3list)); dns_rdataset_init(&rdataset); dns_rdataset_init(&prdataset); dns_db_attach(zone->db, &db); CHECK(dns_db_getoriginnode(db, &node)); dns_db_currentversion(db, &version); result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3param, dns_rdatatype_none, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { goto getprivate; } /* * Walk nsec3param rdataset making a list of parameters (note that * multiple simultaneous nsec3 chains are annoyingly legal -- this * is why we use an nsec3list, even though we will usually only * have one). */ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_t private = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(3), "looping through nsec3param data"); nsec3param = isc_mem_get(zone->mctx, sizeof(nsec3param_t)); ISC_LINK_INIT(nsec3param, link); /* * now transfer the data from the rdata to * the nsec3param */ dns_nsec3param_toprivate(&rdata, &private, zone->privatetype, nsec3param->data, sizeof(nsec3param->data)); nsec3param->length = private.length; ISC_LIST_APPEND(*nsec3list, nsec3param, link); } getprivate: result = dns_db_findrdataset(db, node, version, zone->privatetype, dns_rdatatype_none, 0, &prdataset, NULL); if (result != ISC_R_SUCCESS) { goto done; } /* * walk private type records, converting them to nsec3 parameters * using dns_nsec3param_fromprivate(), do the right thing based on * CREATE and REMOVE flags */ for (result = dns_rdataset_first(&prdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&prdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_t private = DNS_RDATA_INIT; dns_rdataset_current(&prdataset, &private); isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(3), "looping through nsec3param private data"); /* * Do we have a valid private record? */ if (!dns_nsec3param_fromprivate(&private, &rdata, buf, sizeof(buf))) { continue; } /* * Remove any NSEC3PARAM records scheduled to be removed. */ if (NSEC3REMOVE(rdata.data[1])) { /* * Zero out the flags. */ rdata.data[1] = 0; for (nsec3p = ISC_LIST_HEAD(*nsec3list); nsec3p != NULL; nsec3p = next) { next = ISC_LIST_NEXT(nsec3p, link); if (nsec3p->length == rdata.length + 1 && memcmp(rdata.data, nsec3p->data + 1, nsec3p->length - 1) == 0) { ISC_LIST_UNLINK(*nsec3list, nsec3p, link); isc_mem_put(zone->mctx, nsec3p, sizeof(nsec3param_t)); } } continue; } nsec3param = isc_mem_get(zone->mctx, sizeof(nsec3param_t)); ISC_LINK_INIT(nsec3param, link); /* * Copy the remaining private records so the nsec/nsec3 * chain gets created. */ INSIST(private.length <= sizeof(nsec3param->data)); memmove(nsec3param->data, private.data, private.length); nsec3param->length = private.length; ISC_LIST_APPEND(*nsec3list, nsec3param, link); } done: if (result == ISC_R_NOMORE || result == ISC_R_NOTFOUND) { result = ISC_R_SUCCESS; } failure: 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 (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (dns_rdataset_isassociated(&prdataset)) { dns_rdataset_disassociate(&prdataset); } return (result); } /* * Populate new zone db with private type records found by save_nsec3param(). */ static isc_result_t restore_nsec3param(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, nsec3paramlist_t *nsec3list) { isc_result_t result = ISC_R_SUCCESS; dns_diff_t diff; dns_rdata_t rdata; nsec3param_t *nsec3p = NULL; nsec3param_t *next; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(!ISC_LIST_EMPTY(*nsec3list)); dns_diff_init(zone->mctx, &diff); /* * Loop through the list of private-type records, set the INITIAL * and CREATE flags, and the add the record to the apex of the tree * in db. */ for (nsec3p = ISC_LIST_HEAD(*nsec3list); nsec3p != NULL; nsec3p = next) { next = ISC_LIST_NEXT(nsec3p, link); dns_rdata_init(&rdata); nsec3p->data[2] = DNS_NSEC3FLAG_CREATE | DNS_NSEC3FLAG_INITIAL; rdata.length = nsec3p->length; rdata.data = nsec3p->data; rdata.type = zone->privatetype; rdata.rdclass = zone->rdclass; result = update_one_rr(db, version, &diff, DNS_DIFFOP_ADD, &zone->origin, 0, &rdata); if (result != ISC_R_SUCCESS) { break; } } dns_diff_clear(&diff); return (result); } static isc_result_t copy_non_dnssec_records(dns_db_t *db, dns_db_t *version, dns_db_t *rawdb, dns_dbiterator_t *dbiterator, unsigned int *oldserial) { dns_dbnode_t *rawnode = NULL, *node = NULL; dns_fixedname_t fixed; dns_name_t *name = dns_fixedname_initname(&fixed); dns_rdataset_t rdataset; dns_rdatasetiter_t *rdsit = NULL; isc_result_t result; result = dns_dbiterator_current(dbiterator, &rawnode, name); if (result != ISC_R_SUCCESS) { return (ISC_R_SUCCESS); } dns_dbiterator_pause(dbiterator); result = dns_db_findnode(db, name, true, &node); if (result != ISC_R_SUCCESS) { goto cleanup; } result = dns_db_allrdatasets(rawdb, rawnode, NULL, 0, 0, &rdsit); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_rdataset_init(&rdataset); for (result = dns_rdatasetiter_first(rdsit); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(rdsit)) { dns_rdatasetiter_current(rdsit, &rdataset); if (rdataset.type == dns_rdatatype_nsec || rdataset.type == dns_rdatatype_rrsig || rdataset.type == dns_rdatatype_nsec3 || rdataset.type == dns_rdatatype_dnskey || rdataset.type == dns_rdatatype_nsec3param) { dns_rdataset_disassociate(&rdataset); continue; } if (rdataset.type == dns_rdatatype_soa && oldserial != NULL) { result = checkandaddsoa(db, node, version, &rdataset, *oldserial); } else { result = dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL); } dns_rdataset_disassociate(&rdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } cleanup: if (rdsit != NULL) { dns_rdatasetiter_destroy(&rdsit); } if (rawnode) { dns_db_detachnode(rawdb, &rawnode); } if (node) { dns_db_detachnode(db, &node); } return (result); } static void receive_secure_db(isc_task_t *task, isc_event_t *event) { isc_result_t result; dns_zone_t *zone; dns_db_t *rawdb, *db = NULL; dns_dbiterator_t *dbiterator = NULL; dns_dbversion_t *version = NULL; isc_time_t loadtime; unsigned int oldserial = 0, *oldserialp = NULL; nsec3paramlist_t nsec3list; isc_event_t *setnsec3param_event; dns_zone_t *dummy; UNUSED(task); ISC_LIST_INIT(nsec3list); zone = event->ev_arg; rawdb = ((struct secure_event *)event)->db; isc_event_free(&event); LOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING) || !inline_secure(zone)) { result = ISC_R_SHUTTINGDOWN; goto failure; } TIME_NOW(&loadtime); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { result = dns_db_getsoaserial(zone->db, NULL, &oldserial); if (result == ISC_R_SUCCESS) { oldserialp = &oldserial; } /* * assemble nsec3parameters from the old zone, and set a flag * if any are found */ result = save_nsec3param(zone, &nsec3list); if (result != ISC_R_SUCCESS) { ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); goto failure; } } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); result = dns_db_create(zone->mctx, zone->db_argv[0], &zone->origin, dns_dbtype_zone, zone->rdclass, zone->db_argc - 1, zone->db_argv + 1, &db); if (result != ISC_R_SUCCESS) { goto failure; } result = dns_db_setgluecachestats(db, zone->gluecachestats); if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) { goto failure; } result = dns_db_newversion(db, &version); if (result != ISC_R_SUCCESS) { goto failure; } result = dns_db_createiterator(rawdb, 0, &dbiterator); if (result != ISC_R_SUCCESS) { goto failure; } for (result = dns_dbiterator_first(dbiterator); result == ISC_R_SUCCESS; result = dns_dbiterator_next(dbiterator)) { result = copy_non_dnssec_records(db, version, rawdb, dbiterator, oldserialp); if (result != ISC_R_SUCCESS) { goto failure; } } dns_dbiterator_destroy(&dbiterator); if (result != ISC_R_NOMORE) { goto failure; } /* * Call restore_nsec3param() to create private-type records from * the old nsec3 parameters and insert them into db */ if (!ISC_LIST_EMPTY(nsec3list)) { result = restore_nsec3param(zone, db, version, &nsec3list); if (result != ISC_R_SUCCESS) { goto failure; } } dns_db_closeversion(db, &version, true); /* * Lock hierarchy: zmgr, zone, raw. */ INSIST(zone != zone->raw); LOCK_ZONE(zone->raw); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); result = zone_postload(zone, db, loadtime, ISC_R_SUCCESS); zone_needdump(zone, 0); /* XXXMPA */ UNLOCK_ZONE(zone->raw); /* * Process any queued NSEC3PARAM change requests. */ while (!ISC_LIST_EMPTY(zone->setnsec3param_queue)) { setnsec3param_event = ISC_LIST_HEAD(zone->setnsec3param_queue); ISC_LIST_UNLINK(zone->setnsec3param_queue, setnsec3param_event, ev_link); dummy = NULL; zone_iattach(zone, &dummy); isc_task_send(zone->task, &setnsec3param_event); } failure: UNLOCK_ZONE(zone); if (dbiterator != NULL) { dns_dbiterator_destroy(&dbiterator); } if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "receive_secure_db: %s", isc_result_totext(result)); } while (!ISC_LIST_EMPTY(nsec3list)) { nsec3param_t *nsec3p; nsec3p = ISC_LIST_HEAD(nsec3list); ISC_LIST_UNLINK(nsec3list, nsec3p, link); isc_mem_put(zone->mctx, nsec3p, sizeof(nsec3param_t)); } if (db != NULL) { if (version != NULL) { dns_db_closeversion(db, &version, false); } dns_db_detach(&db); } dns_db_detach(&rawdb); dns_zone_idetach(&zone); INSIST(version == NULL); } static isc_result_t zone_send_securedb(dns_zone_t *zone, dns_db_t *db) { isc_event_t *e; dns_db_t *dummy = NULL; dns_zone_t *secure = NULL; e = isc_event_allocate(zone->secure->mctx, zone, DNS_EVENT_ZONESECUREDB, receive_secure_db, zone->secure, sizeof(struct secure_event)); dns_db_attach(db, &dummy); ((struct secure_event *)e)->db = dummy; INSIST(LOCKED_ZONE(zone->secure)); zone_iattach(zone->secure, &secure); isc_task_send(zone->secure->task, &e); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SENDSECURE); return (ISC_R_SUCCESS); } isc_result_t dns_zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump) { isc_result_t result; dns_zone_t *secure = NULL; REQUIRE(DNS_ZONE_VALID(zone)); again: LOCK_ZONE(zone); if (inline_raw(zone)) { secure = zone->secure; INSIST(secure != zone); TRYLOCK_ZONE(result, secure); if (result != ISC_R_SUCCESS) { UNLOCK_ZONE(zone); secure = NULL; isc_thread_yield(); goto again; } } ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); result = zone_replacedb(zone, db, dump); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); if (secure != NULL) { UNLOCK_ZONE(secure); } UNLOCK_ZONE(zone); return (result); } static isc_result_t zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump) { dns_dbversion_t *ver; isc_result_t result; unsigned int soacount = 0; unsigned int nscount = 0; /* * 'zone' and 'zone->db' locked by caller. */ REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(LOCKED_ZONE(zone)); if (inline_raw(zone)) { REQUIRE(LOCKED_ZONE(zone->secure)); } result = zone_get_from_db(zone, db, &nscount, &soacount, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (result == ISC_R_SUCCESS) { if (soacount != 1) { dns_zone_log(zone, ISC_LOG_ERROR, "has %d SOA records", soacount); result = DNS_R_BADZONE; } if (nscount == 0 && zone->type != dns_zone_key) { dns_zone_log(zone, ISC_LOG_ERROR, "has no NS records"); result = DNS_R_BADZONE; } if (result != ISC_R_SUCCESS) { return (result); } } else { dns_zone_log(zone, ISC_LOG_ERROR, "retrieving SOA and NS records failed: %s", isc_result_totext(result)); return (result); } result = check_nsec3param(zone, db); if (result != ISC_R_SUCCESS) { return (result); } ver = NULL; dns_db_currentversion(db, &ver); /* * The initial version of a secondary zone is always dumped; * subsequent versions may be journaled instead if this * is enabled in the configuration. */ if (zone->db != NULL && zone->journal != NULL && DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER)) { uint32_t serial, oldserial; dns_zone_log(zone, ISC_LOG_DEBUG(3), "generating diffs"); result = dns_db_getsoaserial(db, ver, &serial); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "ixfr-from-differences: unable to get " "new serial"); goto fail; } /* * This is checked in zone_postload() for primary zones. */ result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, &oldserial, NULL, NULL, NULL, NULL, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); RUNTIME_CHECK(soacount > 0U); if ((zone->type == dns_zone_secondary || (zone->type == dns_zone_redirect && zone->primaries != NULL)) && !isc_serial_gt(serial, oldserial)) { uint32_t serialmin, serialmax; serialmin = (oldserial + 1) & 0xffffffffU; serialmax = (oldserial + 0x7fffffffU) & 0xffffffffU; dns_zone_log(zone, ISC_LOG_ERROR, "ixfr-from-differences: failed: " "new serial (%u) out of range [%u - %u]", serial, serialmin, serialmax); result = ISC_R_RANGE; goto fail; } result = dns_db_diff(zone->mctx, db, ver, zone->db, NULL, zone->journal); if (result != ISC_R_SUCCESS) { char strbuf[ISC_STRERRORSIZE]; strerror_r(errno, strbuf, sizeof(strbuf)); dns_zone_log(zone, ISC_LOG_ERROR, "ixfr-from-differences: failed: " "%s", strbuf); goto fallback; } if (dump) { zone_needdump(zone, DNS_DUMP_DELAY); } else { zone_journal_compact(zone, zone->db, serial); } if (zone->type == dns_zone_primary && inline_raw(zone)) { zone_send_secureserial(zone, serial); } } else { fallback: if (dump && zone->masterfile != NULL) { /* * If DNS_ZONEFLG_FORCEXFER was set we don't want * to keep the old masterfile. */ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER) && remove(zone->masterfile) < 0 && errno != ENOENT) { char strbuf[ISC_STRERRORSIZE]; strerror_r(errno, strbuf, sizeof(strbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, ISC_LOG_WARNING, "unable to remove masterfile " "'%s': '%s'", zone->masterfile, strbuf); } if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) == 0) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NODELAY); } else { zone_needdump(zone, 0); } } if (dump && zone->journal != NULL) { /* * The in-memory database just changed, and * because 'dump' is set, it didn't change by * being loaded from disk. Also, we have not * journaled diffs for this change. * Therefore, the on-disk journal is missing * the deltas for this change. Since it can * no longer be used to bring the zone * up-to-date, it is useless and should be * removed. */ isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, ISC_LOG_DEBUG(3), "removing journal file"); if (remove(zone->journal) < 0 && errno != ENOENT) { char strbuf[ISC_STRERRORSIZE]; strerror_r(errno, strbuf, sizeof(strbuf)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, ISC_LOG_WARNING, "unable to remove journal " "'%s': '%s'", zone->journal, strbuf); } } if (inline_raw(zone)) { zone_send_securedb(zone, db); } } dns_db_closeversion(db, &ver, false); dns_zone_log(zone, ISC_LOG_DEBUG(3), "replacing zone database"); if (zone->db != NULL) { zone_detachdb(zone); } zone_attachdb(zone, db); dns_db_settask(zone->db, zone->task); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY); return (ISC_R_SUCCESS); fail: dns_db_closeversion(db, &ver, false); return (result); } /* The caller must hold the dblock as a writer. */ static void zone_attachdb(dns_zone_t *zone, dns_db_t *db) { REQUIRE(zone->db == NULL && db != NULL); dns_db_attach(db, &zone->db); } /* The caller must hold the dblock as a writer. */ static void zone_detachdb(dns_zone_t *zone) { REQUIRE(zone->db != NULL); dns_zone_rpz_disable_db(zone, zone->db); dns_zone_catz_disable_db(zone, zone->db); dns_db_detach(&zone->db); } static void zone_xfrdone(dns_zone_t *zone, isc_result_t result) { isc_time_t now; bool again = false; unsigned int soacount; unsigned int nscount; uint32_t serial, refresh, retry, expire, minimum, soattl; isc_result_t xfrresult = result; bool free_needed; dns_zone_t *secure = NULL; REQUIRE(DNS_ZONE_VALID(zone)); dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), "zone transfer finished: %s", isc_result_totext(result)); /* * Obtaining a lock on the zone->secure (see zone_send_secureserial) * could result in a deadlock due to a LOR so we will spin if we * can't obtain both locks. */ again: LOCK_ZONE(zone); if (inline_raw(zone)) { secure = zone->secure; INSIST(secure != zone); TRYLOCK_ZONE(result, secure); if (result != ISC_R_SUCCESS) { UNLOCK_ZONE(zone); secure = NULL; isc_thread_yield(); goto again; } } INSIST(DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR); TIME_NOW(&now); switch (xfrresult) { case ISC_R_SUCCESS: DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); FALLTHROUGH; case DNS_R_UPTODATE: DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_FORCEXFER); /* * Has the zone expired underneath us? */ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db == NULL) { ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); goto same_primary; } /* * Update the zone structure's data from the actual * SOA received. */ nscount = 0; soacount = 0; INSIST(zone->db != NULL); result = zone_get_from_db(zone, zone->db, &nscount, &soacount, &soattl, &serial, &refresh, &retry, &expire, &minimum, NULL); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (result == ISC_R_SUCCESS) { if (soacount != 1) { dns_zone_log(zone, ISC_LOG_ERROR, "transferred zone " "has %d SOA records", soacount); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS)) { zone->refresh = DNS_ZONE_DEFAULTREFRESH; zone->retry = DNS_ZONE_DEFAULTRETRY; } DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS); zone_unload(zone); goto next_primary; } if (nscount == 0) { dns_zone_log(zone, ISC_LOG_ERROR, "transferred zone " "has no NS records"); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HAVETIMERS)) { zone->refresh = DNS_ZONE_DEFAULTREFRESH; zone->retry = DNS_ZONE_DEFAULTRETRY; } DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS); zone_unload(zone); goto next_primary; } zone->refresh = RANGE(refresh, zone->minrefresh, zone->maxrefresh); zone->retry = RANGE(retry, zone->minretry, zone->maxretry); zone->expire = RANGE(expire, zone->refresh + zone->retry, DNS_MAX_EXPIRE); zone->soattl = soattl; zone->minimum = minimum; DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS); } /* * Set our next update/expire times. */ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDREFRESH)) { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDREFRESH); zone->refreshtime = now; DNS_ZONE_TIME_ADD(&now, zone->expire, &zone->expiretime); } else { DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); DNS_ZONE_TIME_ADD(&now, zone->expire, &zone->expiretime); } /* * Set loadtime. */ zone->loadtime = now; if (result == ISC_R_SUCCESS && xfrresult == ISC_R_SUCCESS) { char buf[DNS_NAME_FORMATSIZE + sizeof(": TSIG ''")]; if (zone->tsigkey != NULL) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(&zone->tsigkey->name, namebuf, sizeof(namebuf)); snprintf(buf, sizeof(buf), ": TSIG '%s'", namebuf); } else { buf[0] = '\0'; } dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, "transferred serial %u%s", serial, buf); if (inline_raw(zone)) { zone_send_secureserial(zone, serial); } } /* * This is not necessary if we just performed a AXFR * however it is necessary for an IXFR / UPTODATE and * won't hurt with an AXFR. */ if (zone->masterfile != NULL || zone->journal != NULL) { unsigned int delay = DNS_DUMP_DELAY; result = ISC_R_FAILURE; if (zone->journal != NULL) { result = isc_file_settime(zone->journal, &now); } if (result != ISC_R_SUCCESS && zone->masterfile != NULL) { result = isc_file_settime(zone->masterfile, &now); } if ((DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NODELAY) != 0) || result == ISC_R_FILENOTFOUND) { delay = 0; } if ((result == ISC_R_SUCCESS || result == ISC_R_FILENOTFOUND) && zone->masterfile != NULL) { zone_needdump(zone, delay); } else if (result != ISC_R_SUCCESS) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_ERROR, "transfer: could not set file " "modification time of '%s': %s", zone->masterfile, isc_result_totext(result)); } } DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NODELAY); inc_stats(zone, dns_zonestatscounter_xfrsuccess); break; case DNS_R_BADIXFR: /* Force retry with AXFR. */ DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOIXFR); goto same_primary; case DNS_R_TOOMANYRECORDS: case DNS_R_VERIFYFAILURE: DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); inc_stats(zone, dns_zonestatscounter_xfrfail); break; default: next_primary: /* * Skip to next failed / untried primary. */ do { zone->curprimary++; } while (zone->curprimary < zone->primariescnt && zone->primariesok[zone->curprimary]); same_primary: if (zone->curprimary >= zone->primariescnt) { zone->curprimary = 0; if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_USEALTXFRSRC) && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); while (zone->curprimary < zone->primariescnt && zone->primariesok[zone->curprimary]) { zone->curprimary++; } again = true; } else { DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC); } } else { DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_REFRESH); again = true; } inc_stats(zone, dns_zonestatscounter_xfrfail); break; } zone_settimer(zone, &now); /* * If creating the transfer object failed, zone->xfr is NULL. * Otherwise, we are called as the done callback of a zone * transfer object that just entered its shutting-down * state. Since we are no longer responsible for shutting * it down, we can detach our reference. */ if (zone->xfr != NULL) { dns_xfrin_detach(&zone->xfr); } if (zone->tsigkey != NULL) { dns_tsigkey_detach(&zone->tsigkey); } if (zone->transport != NULL) { dns_transport_detach(&zone->transport); } /* * Handle any deferred journal compaction. */ if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDCOMPACT)) { dns_db_t *db = NULL; if (dns_zone_getdb(zone, &db) == ISC_R_SUCCESS) { zone_journal_compact(zone, db, zone->compact_serial); dns_db_detach(&db); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDCOMPACT); } } if (secure != NULL) { UNLOCK_ZONE(secure); } /* * This transfer finishing freed up a transfer quota slot. * Let any other zones waiting for quota have it. */ if (zone->zmgr != NULL && zone->statelist == &zone->zmgr->xfrin_in_progress) { UNLOCK_ZONE(zone); RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); ISC_LIST_UNLINK(zone->zmgr->xfrin_in_progress, zone, statelink); zone->statelist = NULL; zmgr_resume_xfrs(zone->zmgr, false); RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); LOCK_ZONE(zone); } /* * Retry with a different server if necessary. */ if (again && !DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { queue_soa_query(zone); } isc_refcount_decrement(&zone->irefs); free_needed = exit_check(zone); UNLOCK_ZONE(zone); if (free_needed) { zone_free(zone); } } static void zone_loaddone(void *arg, isc_result_t result) { static char me[] = "zone_loaddone"; dns_load_t *load = arg; dns_zone_t *zone; isc_result_t tresult; dns_zone_t *secure = NULL; REQUIRE(DNS_LOAD_VALID(load)); zone = load->zone; ENTER; /* * If zone loading failed, remove the update db callbacks prior * to calling the list of callbacks in the zone load structure. */ if (result != ISC_R_SUCCESS) { dns_zone_rpz_disable_db(zone, load->db); dns_zone_catz_disable_db(zone, load->db); } tresult = dns_db_endload(load->db, &load->callbacks); if (tresult != ISC_R_SUCCESS && (result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE)) { result = tresult; } /* * Lock hierarchy: zmgr, zone, raw. */ again: LOCK_ZONE(zone); INSIST(zone != zone->raw); if (inline_secure(zone)) { LOCK_ZONE(zone->raw); } else if (inline_raw(zone)) { secure = zone->secure; TRYLOCK_ZONE(tresult, secure); if (tresult != ISC_R_SUCCESS) { UNLOCK_ZONE(zone); secure = NULL; isc_thread_yield(); goto again; } } (void)zone_postload(zone, load->db, load->loadtime, result); zonemgr_putio(&zone->readio); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_LOADING); zone_idetach(&load->callbacks.zone); /* * Leave the zone frozen if the reload fails. */ if ((result == ISC_R_SUCCESS || result == DNS_R_SEENINCLUDE) && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_THAW)) { zone->update_disabled = false; } DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_THAW); if (inline_secure(zone)) { UNLOCK_ZONE(zone->raw); } else if (secure != NULL) { UNLOCK_ZONE(secure); } UNLOCK_ZONE(zone); load->magic = 0; dns_db_detach(&load->db); if (load->zone->lctx != NULL) { dns_loadctx_detach(&load->zone->lctx); } dns_zone_idetach(&load->zone); isc_mem_putanddetach(&load->mctx, load, sizeof(*load)); } void dns_zone_getssutable(dns_zone_t *zone, dns_ssutable_t **table) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(table != NULL); REQUIRE(*table == NULL); LOCK_ZONE(zone); if (zone->ssutable != NULL) { dns_ssutable_attach(zone->ssutable, table); } UNLOCK_ZONE(zone); } void dns_zone_setssutable(dns_zone_t *zone, dns_ssutable_t *table) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->ssutable != NULL) { dns_ssutable_detach(&zone->ssutable); } if (table != NULL) { dns_ssutable_attach(table, &zone->ssutable); } UNLOCK_ZONE(zone); } void dns_zone_setsigvalidityinterval(dns_zone_t *zone, uint32_t interval) { REQUIRE(DNS_ZONE_VALID(zone)); zone->sigvalidityinterval = interval; } uint32_t dns_zone_getsigvalidityinterval(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->sigvalidityinterval); } void dns_zone_setkeyvalidityinterval(dns_zone_t *zone, uint32_t interval) { REQUIRE(DNS_ZONE_VALID(zone)); zone->keyvalidityinterval = interval; } uint32_t dns_zone_getkeyvalidityinterval(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->keyvalidityinterval); } void dns_zone_setsigresigninginterval(dns_zone_t *zone, uint32_t interval) { isc_time_t now; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->sigresigninginterval = interval; set_resigntime(zone); if (zone->task != NULL) { TIME_NOW(&now); zone_settimer(zone, &now); } UNLOCK_ZONE(zone); } uint32_t dns_zone_getsigresigninginterval(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->sigresigninginterval); } static void queue_xfrin(dns_zone_t *zone) { const char me[] = "queue_xfrin"; isc_result_t result; dns_zonemgr_t *zmgr = zone->zmgr; ENTER; INSIST(zone->statelist == NULL); RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); ISC_LIST_APPEND(zmgr->waiting_for_xfrin, zone, statelink); isc_refcount_increment0(&zone->irefs); zone->statelist = &zmgr->waiting_for_xfrin; result = zmgr_start_xfrin_ifquota(zmgr, zone); RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); if (result == ISC_R_QUOTA) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, "zone transfer deferred due to quota"); } else if (result != ISC_R_SUCCESS) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_ERROR, "starting zone transfer: %s", isc_result_totext(result)); } } /* * This event callback is called when a zone has received * any necessary zone transfer quota. This is the time * to go ahead and start the transfer. */ static void got_transfer_quota(isc_task_t *task, isc_event_t *event) { isc_result_t result = ISC_R_SUCCESS; dns_peer_t *peer = NULL; char primary[ISC_SOCKADDR_FORMATSIZE]; char source[ISC_SOCKADDR_FORMATSIZE]; dns_rdatatype_t xfrtype; dns_zone_t *zone = event->ev_arg; isc_netaddr_t primaryip; isc_sockaddr_t sourceaddr; isc_sockaddr_t primaryaddr; isc_time_t now; const char *soa_before = ""; bool loaded; isc_tlsctx_cache_t *zmgr_tlsctx_cache = NULL; UNUSED(task); INSIST(task == zone->task); isc_event_free(&event); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { zone_xfrdone(zone, ISC_R_CANCELED); return; } TIME_NOW(&now); isc_sockaddr_format(&zone->primaryaddr, primary, sizeof(primary)); if (dns_zonemgr_unreachable(zone->zmgr, &zone->primaryaddr, &zone->sourceaddr, &now)) { isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, "got_transfer_quota: skipping zone transfer as " "primary %s (source %s) is unreachable (cached)", primary, source); zone_xfrdone(zone, ISC_R_CANCELED); return; } isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr); (void)dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, &peer); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR)) { soa_before = "SOA before "; } /* * Decide whether we should request IXFR or AXFR. */ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); loaded = (zone->db != NULL); ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (!loaded) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), "no database exists yet, requesting AXFR of " "initial version from %s", primary); xfrtype = dns_rdatatype_axfr; } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER)) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), "forced reload, requesting AXFR of " "initial version from %s", primary); xfrtype = dns_rdatatype_axfr; } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOIXFR)) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), "retrying with AXFR from %s due to " "previous IXFR failure", primary); xfrtype = dns_rdatatype_axfr; LOCK_ZONE(zone); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOIXFR); UNLOCK_ZONE(zone); } else { bool use_ixfr = true; if (peer != NULL) { result = dns_peer_getrequestixfr(peer, &use_ixfr); } if (peer == NULL || result != ISC_R_SUCCESS) { use_ixfr = zone->requestixfr; } if (!use_ixfr) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), "IXFR disabled, " "requesting %sAXFR from %s", soa_before, primary); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR)) { xfrtype = dns_rdatatype_soa; } else { xfrtype = dns_rdatatype_axfr; } } else { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), "requesting IXFR from %s", primary); xfrtype = dns_rdatatype_ixfr; } } /* * Determine if we should attempt to sign the request with TSIG. */ result = ISC_R_NOTFOUND; /* * First, look for a tsig key in the primaries statement, then * try for a server key. */ if ((zone->primarykeynames != NULL) && (zone->primarykeynames[zone->curprimary] != NULL)) { dns_view_t *view = dns_zone_getview(zone); dns_name_t *keyname = zone->primarykeynames[zone->curprimary]; result = dns_view_gettsig(view, keyname, &zone->tsigkey); } if (result != ISC_R_SUCCESS) { INSIST(zone->tsigkey == NULL); result = dns_view_getpeertsig(zone->view, &primaryip, &zone->tsigkey); } if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_ERROR, "could not get TSIG key for zone transfer: %s", isc_result_totext(result)); } /* * Get the TLS transport for the primary, if configured. */ if ((zone->primarytlsnames != NULL) && (zone->primarytlsnames[zone->curprimary] != NULL)) { dns_view_t *view = dns_zone_getview(zone); dns_name_t *tlsname = zone->primarytlsnames[zone->curprimary]; result = dns_view_gettransport(view, DNS_TRANSPORT_TLS, tlsname, &zone->transport); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_ERROR, "could not get TLS configuration for " "zone transfer: %s", isc_result_totext(result)); } } LOCK_ZONE(zone); primaryaddr = zone->primaryaddr; sourceaddr = zone->sourceaddr; UNLOCK_ZONE(zone); INSIST(isc_sockaddr_pf(&primaryaddr) == isc_sockaddr_pf(&sourceaddr)); if (zone->xfr != NULL) { dns_xfrin_detach(&zone->xfr); } zmgr_tlsctx_attach(zone->zmgr, &zmgr_tlsctx_cache); result = dns_xfrin_create(zone, xfrtype, &primaryaddr, &sourceaddr, zone->tsigkey, zone->transport, zmgr_tlsctx_cache, zone->mctx, zone->zmgr->netmgr, zone_xfrdone, &zone->xfr); isc_tlsctx_cache_detach(&zmgr_tlsctx_cache); /* * Any failure in this function is handled like a failed * zone transfer. This ensures that we get removed from * zmgr->xfrin_in_progress. */ if (result != ISC_R_SUCCESS) { zone_xfrdone(zone, result); return; } LOCK_ZONE(zone); if (xfrtype == dns_rdatatype_axfr) { if (isc_sockaddr_pf(&primaryaddr) == PF_INET) { inc_stats(zone, dns_zonestatscounter_axfrreqv4); } else { inc_stats(zone, dns_zonestatscounter_axfrreqv6); } } else if (xfrtype == dns_rdatatype_ixfr) { if (isc_sockaddr_pf(&primaryaddr) == PF_INET) { inc_stats(zone, dns_zonestatscounter_ixfrreqv4); } else { inc_stats(zone, dns_zonestatscounter_ixfrreqv6); } } UNLOCK_ZONE(zone); } /* * Update forwarding support. */ static void forward_destroy(dns_forward_t *forward) { forward->magic = 0; if (forward->request != NULL) { dns_request_destroy(&forward->request); } if (forward->msgbuf != NULL) { isc_buffer_free(&forward->msgbuf); } if (forward->zone != NULL) { LOCK(&forward->zone->lock); if (ISC_LINK_LINKED(forward, link)) { ISC_LIST_UNLINK(forward->zone->forwards, forward, link); } UNLOCK(&forward->zone->lock); dns_zone_idetach(&forward->zone); } isc_mem_putanddetach(&forward->mctx, forward, sizeof(*forward)); } static isc_result_t sendtoprimary(dns_forward_t *forward) { isc_result_t result; isc_sockaddr_t src; LOCK_ZONE(forward->zone); if (DNS_ZONE_FLAG(forward->zone, DNS_ZONEFLG_EXITING)) { UNLOCK_ZONE(forward->zone); return (ISC_R_CANCELED); } if (forward->which >= forward->zone->primariescnt) { UNLOCK_ZONE(forward->zone); return (ISC_R_NOMORE); } forward->addr = forward->zone->primaries[forward->which]; /* * Always use TCP regardless of whether the original update * used TCP. * XXX The timeout may but a bit small if we are far down a * transfer graph and have to try several primaries. */ switch (isc_sockaddr_pf(&forward->addr)) { case PF_INET: src = forward->zone->xfrsource4; break; case PF_INET6: src = forward->zone->xfrsource6; break; default: result = ISC_R_NOTIMPLEMENTED; goto unlock; } result = dns_request_createraw(forward->zone->view->requestmgr, forward->msgbuf, &src, &forward->addr, forward->options, 15 /* XXX */, 0, 0, forward->zone->task, forward_callback, forward, &forward->request); if (result == ISC_R_SUCCESS) { if (!ISC_LINK_LINKED(forward, link)) { ISC_LIST_APPEND(forward->zone->forwards, forward, link); } } unlock: UNLOCK_ZONE(forward->zone); return (result); } static void forward_callback(isc_task_t *task, isc_event_t *event) { const char me[] = "forward_callback"; dns_requestevent_t *revent = (dns_requestevent_t *)event; dns_message_t *msg = NULL; char primary[ISC_SOCKADDR_FORMATSIZE]; isc_result_t result; dns_forward_t *forward; dns_zone_t *zone; UNUSED(task); forward = revent->ev_arg; INSIST(DNS_FORWARD_VALID(forward)); zone = forward->zone; INSIST(DNS_ZONE_VALID(zone)); ENTER; isc_sockaddr_format(&forward->addr, primary, sizeof(primary)); if (revent->result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "could not forward dynamic update to %s: %s", primary, isc_result_totext(revent->result)); goto next_primary; } dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg); result = dns_request_getresponse(revent->request, msg, DNS_MESSAGEPARSE_PRESERVEORDER | DNS_MESSAGEPARSE_CLONEBUFFER); if (result != ISC_R_SUCCESS) { goto next_primary; } /* * Unexpected opcode. */ if (msg->opcode != dns_opcode_update) { char opcode[128]; isc_buffer_t rb; isc_buffer_init(&rb, opcode, sizeof(opcode)); (void)dns_opcode_totext(msg->opcode, &rb); dns_zone_log(zone, ISC_LOG_INFO, "forwarding dynamic update: " "unexpected opcode (%.*s) from %s", (int)rb.used, opcode, primary); goto next_primary; } switch (msg->rcode) { /* * Pass these rcodes back to client. */ case dns_rcode_noerror: case dns_rcode_yxdomain: case dns_rcode_yxrrset: case dns_rcode_nxrrset: case dns_rcode_refused: case dns_rcode_nxdomain: { char rcode[128]; isc_buffer_t rb; isc_buffer_init(&rb, rcode, sizeof(rcode)); (void)dns_rcode_totext(msg->rcode, &rb); dns_zone_log(zone, ISC_LOG_INFO, "forwarded dynamic update: " "primary %s returned: %.*s", primary, (int)rb.used, rcode); break; } /* These should not occur if the primaries/zone are valid. */ case dns_rcode_notzone: case dns_rcode_notauth: { char rcode[128]; isc_buffer_t rb; isc_buffer_init(&rb, rcode, sizeof(rcode)); (void)dns_rcode_totext(msg->rcode, &rb); dns_zone_log(zone, ISC_LOG_WARNING, "forwarding dynamic update: " "unexpected response: primary %s returned: %.*s", primary, (int)rb.used, rcode); goto next_primary; } /* Try another server for these rcodes. */ case dns_rcode_formerr: case dns_rcode_servfail: case dns_rcode_notimp: case dns_rcode_badvers: default: goto next_primary; } /* call callback */ (forward->callback)(forward->callback_arg, ISC_R_SUCCESS, msg); msg = NULL; dns_request_destroy(&forward->request); forward_destroy(forward); isc_event_free(&event); return; next_primary: if (msg != NULL) { dns_message_detach(&msg); } isc_event_free(&event); forward->which++; dns_request_destroy(&forward->request); result = sendtoprimary(forward); if (result != ISC_R_SUCCESS) { /* call callback */ dns_zone_log(zone, ISC_LOG_DEBUG(3), "exhausted dynamic update forwarder list"); (forward->callback)(forward->callback_arg, result, NULL); forward_destroy(forward); } } isc_result_t dns_zone_forwardupdate(dns_zone_t *zone, dns_message_t *msg, dns_updatecallback_t callback, void *callback_arg) { dns_forward_t *forward; isc_result_t result; isc_region_t *mr; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(msg != NULL); REQUIRE(callback != NULL); forward = isc_mem_get(zone->mctx, sizeof(*forward)); forward->request = NULL; forward->zone = NULL; forward->msgbuf = NULL; forward->which = 0; forward->mctx = 0; forward->callback = callback; forward->callback_arg = callback_arg; ISC_LINK_INIT(forward, link); forward->magic = FORWARD_MAGIC; forward->options = DNS_REQUESTOPT_TCP; /* * If we have a SIG(0) signed message we need to preserve the * query id as that is included in the SIG(0) computation. */ if (msg->sig0 != NULL) { forward->options |= DNS_REQUESTOPT_FIXEDID; } mr = dns_message_getrawmessage(msg); if (mr == NULL) { result = ISC_R_UNEXPECTEDEND; goto cleanup; } isc_buffer_allocate(zone->mctx, &forward->msgbuf, mr->length); result = isc_buffer_copyregion(forward->msgbuf, mr); if (result != ISC_R_SUCCESS) { goto cleanup; } isc_mem_attach(zone->mctx, &forward->mctx); dns_zone_iattach(zone, &forward->zone); result = sendtoprimary(forward); cleanup: if (result != ISC_R_SUCCESS) { forward_destroy(forward); } return (result); } isc_result_t dns_zone_next(dns_zone_t *zone, dns_zone_t **next) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(next != NULL && *next == NULL); *next = ISC_LIST_NEXT(zone, link); if (*next == NULL) { return (ISC_R_NOMORE); } else { return (ISC_R_SUCCESS); } } isc_result_t dns_zone_first(dns_zonemgr_t *zmgr, dns_zone_t **first) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); REQUIRE(first != NULL && *first == NULL); *first = ISC_LIST_HEAD(zmgr->zones); if (*first == NULL) { return (ISC_R_NOMORE); } else { return (ISC_R_SUCCESS); } } /*** *** Zone manager. ***/ #define KEYMGMT_OVERCOMMIT 3 #define KEYMGMT_BITS_MIN 2U #define KEYMGMT_BITS_MAX 32U /* * WMM: Static hash functions copied from lib/dns/rbtdb.c. Should be moved to * lib/isc/hash.c when we refactor the hash table code. */ #define GOLDEN_RATIO_32 0x61C88647 #define HASHSIZE(bits) (UINT64_C(1) << (bits)) static uint32_t hash_index(uint32_t val, uint32_t bits) { return (val * GOLDEN_RATIO_32 >> (32 - bits)); } static uint32_t hash_bits_grow(uint32_t bits, uint32_t count) { uint32_t newbits = bits; while (count >= HASHSIZE(newbits) && newbits < KEYMGMT_BITS_MAX) { newbits++; } return (newbits); } static uint32_t hash_bits_shrink(uint32_t bits, uint32_t count) { uint32_t newbits = bits; while (count <= HASHSIZE(newbits) && newbits > KEYMGMT_BITS_MIN) { newbits--; } return (newbits); } static void zonemgr_keymgmt_init(dns_zonemgr_t *zmgr) { dns_keymgmt_t *mgmt = isc_mem_get(zmgr->mctx, sizeof(*mgmt)); uint32_t size; *mgmt = (dns_keymgmt_t){ .bits = KEYMGMT_BITS_MIN, }; isc_mem_attach(zmgr->mctx, &mgmt->mctx); isc_rwlock_init(&mgmt->lock, 0, 0); size = HASHSIZE(mgmt->bits); mgmt->table = isc_mem_get(mgmt->mctx, sizeof(*mgmt->table) * size); memset(mgmt->table, 0, size * sizeof(mgmt->table[0])); atomic_init(&mgmt->count, 0); mgmt->magic = KEYMGMT_MAGIC; zmgr->keymgmt = mgmt; } static void zonemgr_keymgmt_destroy(dns_zonemgr_t *zmgr) { dns_keymgmt_t *mgmt = zmgr->keymgmt; uint32_t size; REQUIRE(DNS_KEYMGMT_VALID(mgmt)); size = HASHSIZE(mgmt->bits); RWLOCK(&mgmt->lock, isc_rwlocktype_write); INSIST(mgmt->count == 0); RWUNLOCK(&mgmt->lock, isc_rwlocktype_write); mgmt->magic = 0; isc_rwlock_destroy(&mgmt->lock); isc_mem_put(mgmt->mctx, mgmt->table, size * sizeof(mgmt->table[0])); isc_mem_putanddetach(&mgmt->mctx, mgmt, sizeof(dns_keymgmt_t)); } static void zonemgr_keymgmt_resize(dns_zonemgr_t *zmgr) { dns_keyfileio_t **newtable; dns_keymgmt_t *mgmt = zmgr->keymgmt; uint32_t bits, newbits, count, size, newsize; bool grow; REQUIRE(DNS_KEYMGMT_VALID(mgmt)); RWLOCK(&mgmt->lock, isc_rwlocktype_read); count = atomic_load_relaxed(&mgmt->count); bits = mgmt->bits; RWUNLOCK(&mgmt->lock, isc_rwlocktype_read); size = HASHSIZE(bits); INSIST(size > 0); if (count >= (size * KEYMGMT_OVERCOMMIT)) { grow = true; } else if (count < (size / 2)) { grow = false; } else { /* No need to resize. */ return; } if (grow) { newbits = hash_bits_grow(bits, count); } else { newbits = hash_bits_shrink(bits, count); } if (newbits == bits) { /* * Bit values may stay the same if maximum or minimum is * reached. */ return; } newsize = HASHSIZE(newbits); INSIST(newsize > 0); RWLOCK(&mgmt->lock, isc_rwlocktype_write); newtable = isc_mem_get(mgmt->mctx, sizeof(dns_keyfileio_t *) * newsize); memset(newtable, 0, sizeof(dns_keyfileio_t *) * newsize); for (unsigned int i = 0; i < size; i++) { dns_keyfileio_t *kfio, *next; for (kfio = mgmt->table[i]; kfio != NULL; kfio = next) { uint32_t hash = hash_index(kfio->hashval, newbits); next = kfio->next; kfio->next = newtable[hash]; newtable[hash] = kfio; } mgmt->table[i] = NULL; } isc_mem_put(mgmt->mctx, mgmt->table, sizeof(*mgmt->table) * size); mgmt->bits = newbits; mgmt->table = newtable; RWUNLOCK(&mgmt->lock, isc_rwlocktype_write); } static void zonemgr_keymgmt_add(dns_zonemgr_t *zmgr, dns_zone_t *zone, dns_keyfileio_t **added) { dns_keymgmt_t *mgmt = zmgr->keymgmt; uint32_t hashval, hash; dns_keyfileio_t *kfio, *next; REQUIRE(DNS_KEYMGMT_VALID(mgmt)); REQUIRE(added != NULL && *added == NULL); RWLOCK(&mgmt->lock, isc_rwlocktype_write); hashval = dns_name_hash(&zone->origin, false); hash = hash_index(hashval, mgmt->bits); for (kfio = mgmt->table[hash]; kfio != NULL; kfio = next) { next = kfio->next; if (dns_name_equal(kfio->name, &zone->origin)) { /* Already in table, increment the counter. */ isc_refcount_increment(&kfio->references); break; } } if (kfio == NULL) { /* No entry found, add it. */ kfio = isc_mem_get(mgmt->mctx, sizeof(*kfio)); *kfio = (dns_keyfileio_t){ .hashval = hashval, .next = mgmt->table[hash], .magic = KEYFILEIO_MAGIC, }; isc_refcount_init(&kfio->references, 1); kfio->name = dns_fixedname_initname(&kfio->fname); dns_name_copy(&zone->origin, kfio->name); isc_mutex_init(&kfio->lock); mgmt->table[hash] = kfio; atomic_fetch_add_relaxed(&mgmt->count, 1); } RWUNLOCK(&mgmt->lock, isc_rwlocktype_write); *added = kfio; /* * Call resize, that function will also check if resize is necessary. */ zonemgr_keymgmt_resize(zmgr); } static void zonemgr_keymgmt_delete(dns_zonemgr_t *zmgr, dns_zone_t *zone, dns_keyfileio_t **deleted) { dns_keymgmt_t *mgmt = zmgr->keymgmt; uint32_t hashval, hash; dns_keyfileio_t *kfio, *prev, *next; REQUIRE(DNS_KEYMGMT_VALID(mgmt)); REQUIRE(deleted != NULL && DNS_KEYFILEIO_VALID(*deleted)); RWLOCK(&mgmt->lock, isc_rwlocktype_write); hashval = dns_name_hash(&zone->origin, false); hash = hash_index(hashval, mgmt->bits); prev = NULL; for (kfio = mgmt->table[hash]; kfio != NULL; kfio = next) { next = kfio->next; if (dns_name_equal(kfio->name, &zone->origin)) { INSIST(kfio == *deleted); *deleted = NULL; if (isc_refcount_decrement(&kfio->references) == 1) { if (prev == NULL) { mgmt->table[hash] = kfio->next; } else { prev->next = kfio->next; } isc_refcount_destroy(&kfio->references); isc_mutex_destroy(&kfio->lock); isc_mem_put(mgmt->mctx, kfio, sizeof(*kfio)); atomic_fetch_sub_relaxed(&mgmt->count, 1); } break; } prev = kfio; } RWUNLOCK(&mgmt->lock, isc_rwlocktype_write); /* * Call resize, that function will also check if resize is necessary. */ zonemgr_keymgmt_resize(zmgr); } isc_result_t dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, isc_nm_t *netmgr, dns_zonemgr_t **zmgrp) { dns_zonemgr_t *zmgr; isc_result_t result; zmgr = isc_mem_get(mctx, sizeof(*zmgr)); zmgr->mctx = NULL; isc_refcount_init(&zmgr->refs, 1); isc_mem_attach(mctx, &zmgr->mctx); zmgr->taskmgr = taskmgr; zmgr->timermgr = timermgr; zmgr->netmgr = netmgr; zmgr->zonetasks = NULL; zmgr->loadtasks = NULL; zmgr->mctxpool = NULL; zmgr->task = NULL; zmgr->checkdsrl = NULL; zmgr->notifyrl = NULL; zmgr->refreshrl = NULL; zmgr->startupnotifyrl = NULL; zmgr->startuprefreshrl = NULL; ISC_LIST_INIT(zmgr->zones); ISC_LIST_INIT(zmgr->waiting_for_xfrin); ISC_LIST_INIT(zmgr->xfrin_in_progress); memset(zmgr->unreachable, 0, sizeof(zmgr->unreachable)); for (size_t i = 0; i < UNREACH_CACHE_SIZE; i++) { atomic_init(&zmgr->unreachable[i].expire, 0); } isc_rwlock_init(&zmgr->rwlock, 0, 0); zmgr->transfersin = 10; zmgr->transfersperns = 2; /* Unreachable lock. */ isc_rwlock_init(&zmgr->urlock, 0, 0); /* Create a single task for queueing of SOA queries. */ result = isc_task_create(taskmgr, 1, &zmgr->task); if (result != ISC_R_SUCCESS) { goto free_urlock; } isc_task_setname(zmgr->task, "zmgr", zmgr); result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, &zmgr->checkdsrl); if (result != ISC_R_SUCCESS) { goto free_task; } result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, &zmgr->notifyrl); if (result != ISC_R_SUCCESS) { goto free_checkdsrl; } result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, &zmgr->refreshrl); if (result != ISC_R_SUCCESS) { goto free_notifyrl; } result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, &zmgr->startupnotifyrl); if (result != ISC_R_SUCCESS) { goto free_refreshrl; } result = isc_ratelimiter_create(mctx, timermgr, zmgr->task, &zmgr->startuprefreshrl); if (result != ISC_R_SUCCESS) { goto free_startupnotifyrl; } /* Key file I/O locks. */ zonemgr_keymgmt_init(zmgr); /* Default to 20 refresh queries / notifies / checkds per second. */ setrl(zmgr->checkdsrl, &zmgr->checkdsrate, 20); setrl(zmgr->notifyrl, &zmgr->notifyrate, 20); setrl(zmgr->startupnotifyrl, &zmgr->startupnotifyrate, 20); setrl(zmgr->refreshrl, &zmgr->serialqueryrate, 20); setrl(zmgr->startuprefreshrl, &zmgr->startupserialqueryrate, 20); isc_ratelimiter_setpushpop(zmgr->startupnotifyrl, true); isc_ratelimiter_setpushpop(zmgr->startuprefreshrl, true); zmgr->iolimit = 1; zmgr->ioactive = 0; ISC_LIST_INIT(zmgr->high); ISC_LIST_INIT(zmgr->low); isc_mutex_init(&zmgr->iolock); zmgr->tlsctx_cache = NULL; isc_rwlock_init(&zmgr->tlsctx_cache_rwlock, 0, 0); zmgr->magic = ZONEMGR_MAGIC; *zmgrp = zmgr; return (ISC_R_SUCCESS); #if 0 free_iolock: isc_mutex_destroy(&zmgr->iolock); #endif /* if 0 */ free_startupnotifyrl: isc_ratelimiter_detach(&zmgr->startupnotifyrl); free_refreshrl: isc_ratelimiter_detach(&zmgr->refreshrl); free_notifyrl: isc_ratelimiter_detach(&zmgr->notifyrl); free_checkdsrl: isc_ratelimiter_detach(&zmgr->checkdsrl); free_task: isc_task_detach(&zmgr->task); free_urlock: isc_rwlock_destroy(&zmgr->urlock); isc_rwlock_destroy(&zmgr->rwlock); isc_mem_put(zmgr->mctx, zmgr, sizeof(*zmgr)); isc_mem_detach(&mctx); return (result); } isc_result_t dns_zonemgr_createzone(dns_zonemgr_t *zmgr, dns_zone_t **zonep) { isc_result_t result; isc_mem_t *mctx = NULL; dns_zone_t *zone = NULL; void *item; REQUIRE(DNS_ZONEMGR_VALID(zmgr)); REQUIRE(zonep != NULL && *zonep == NULL); if (zmgr->mctxpool == NULL) { return (ISC_R_FAILURE); } item = isc_pool_get(zmgr->mctxpool); if (item == NULL) { return (ISC_R_FAILURE); } isc_mem_attach((isc_mem_t *)item, &mctx); result = dns_zone_create(&zone, mctx); isc_mem_detach(&mctx); if (result == ISC_R_SUCCESS) { *zonep = zone; } return (result); } isc_result_t dns_zonemgr_managezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) { isc_result_t result; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(DNS_ZONEMGR_VALID(zmgr)); if (zmgr->zonetasks == NULL) { return (ISC_R_FAILURE); } RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); LOCK_ZONE(zone); REQUIRE(zone->task == NULL); REQUIRE(zone->timer == NULL); REQUIRE(zone->zmgr == NULL); isc_taskpool_gettask(zmgr->zonetasks, &zone->task); isc_taskpool_gettask(zmgr->loadtasks, &zone->loadtask); /* * Set the task name. The tag will arbitrarily point to one * of the zones sharing the task (in practice, the one * to be managed last). */ isc_task_setname(zone->task, "zone", zone); isc_task_setname(zone->loadtask, "loadzone", zone); result = isc_timer_create(zmgr->timermgr, isc_timertype_inactive, NULL, NULL, zone->task, zone_timer, zone, &zone->timer); if (result != ISC_R_SUCCESS) { goto cleanup_tasks; } /* * The timer "holds" a iref. */ isc_refcount_increment0(&zone->irefs); zonemgr_keymgmt_add(zmgr, zone, &zone->kfio); INSIST(zone->kfio != NULL); ISC_LIST_APPEND(zmgr->zones, zone, link); zone->zmgr = zmgr; isc_refcount_increment(&zmgr->refs); goto unlock; cleanup_tasks: isc_task_detach(&zone->loadtask); isc_task_detach(&zone->task); unlock: UNLOCK_ZONE(zone); RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); return (result); } void dns_zonemgr_releasezone(dns_zonemgr_t *zmgr, dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(DNS_ZONEMGR_VALID(zmgr)); REQUIRE(zone->zmgr == zmgr); RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); LOCK_ZONE(zone); ISC_LIST_UNLINK(zmgr->zones, zone, link); if (zone->kfio != NULL) { zonemgr_keymgmt_delete(zmgr, zone, &zone->kfio); ENSURE(zone->kfio == NULL); } /* Detach below, outside of the write lock. */ zone->zmgr = NULL; UNLOCK_ZONE(zone); RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); dns_zonemgr_detach(&zmgr); } void dns_zonemgr_attach(dns_zonemgr_t *source, dns_zonemgr_t **target) { REQUIRE(DNS_ZONEMGR_VALID(source)); REQUIRE(target != NULL && *target == NULL); isc_refcount_increment(&source->refs); *target = source; } void dns_zonemgr_detach(dns_zonemgr_t **zmgrp) { dns_zonemgr_t *zmgr; REQUIRE(zmgrp != NULL); zmgr = *zmgrp; *zmgrp = NULL; REQUIRE(DNS_ZONEMGR_VALID(zmgr)); if (isc_refcount_decrement(&zmgr->refs) == 1) { zonemgr_free(zmgr); } } isc_result_t dns_zonemgr_forcemaint(dns_zonemgr_t *zmgr) { dns_zone_t *p; REQUIRE(DNS_ZONEMGR_VALID(zmgr)); RWLOCK(&zmgr->rwlock, isc_rwlocktype_read); for (p = ISC_LIST_HEAD(zmgr->zones); p != NULL; p = ISC_LIST_NEXT(p, link)) { dns_zone_maintenance(p); } RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read); /* * Recent configuration changes may have increased the * amount of available transfers quota. Make sure any * transfers currently blocked on quota get started if * possible. */ RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); zmgr_resume_xfrs(zmgr, true); RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); return (ISC_R_SUCCESS); } void dns_zonemgr_resumexfrs(dns_zonemgr_t *zmgr) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); zmgr_resume_xfrs(zmgr, true); RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); } void dns_zonemgr_shutdown(dns_zonemgr_t *zmgr) { dns_zone_t *zone; REQUIRE(DNS_ZONEMGR_VALID(zmgr)); isc_ratelimiter_shutdown(zmgr->checkdsrl); isc_ratelimiter_shutdown(zmgr->notifyrl); isc_ratelimiter_shutdown(zmgr->refreshrl); isc_ratelimiter_shutdown(zmgr->startupnotifyrl); isc_ratelimiter_shutdown(zmgr->startuprefreshrl); if (zmgr->task != NULL) { isc_task_destroy(&zmgr->task); } if (zmgr->zonetasks != NULL) { isc_taskpool_destroy(&zmgr->zonetasks); } if (zmgr->loadtasks != NULL) { isc_taskpool_destroy(&zmgr->loadtasks); } if (zmgr->mctxpool != NULL) { isc_pool_destroy(&zmgr->mctxpool); } RWLOCK(&zmgr->rwlock, isc_rwlocktype_read); for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL; zone = ISC_LIST_NEXT(zone, link)) { LOCK_ZONE(zone); forward_cancel(zone); UNLOCK_ZONE(zone); } RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read); } static isc_result_t mctxinit(void **target, void *arg) { isc_mem_t *mctx = NULL; UNUSED(arg); REQUIRE(target != NULL && *target == NULL); isc_mem_create(&mctx); isc_mem_setname(mctx, "zonemgr-pool"); *target = mctx; return (ISC_R_SUCCESS); } static void mctxfree(void **target) { isc_mem_t *mctx = *(isc_mem_t **)target; isc_mem_detach(&mctx); *target = NULL; } #define ZONES_PER_TASK 100 #define ZONES_PER_MCTX 1000 isc_result_t dns_zonemgr_setsize(dns_zonemgr_t *zmgr, int num_zones) { isc_result_t result; int ntasks = num_zones / ZONES_PER_TASK; int nmctx = num_zones / ZONES_PER_MCTX; isc_taskpool_t *pool = NULL; isc_pool_t *mctxpool = NULL; REQUIRE(DNS_ZONEMGR_VALID(zmgr)); /* * For anything fewer than 1000 zones we use 10 tasks in * the task pools. More than that, and we'll scale at one * task per 100 zones. Similarly, for anything smaller than * 2000 zones we use 2 memory contexts, then scale at 1:1000. */ if (ntasks < 10) { ntasks = 10; } if (nmctx < 2) { nmctx = 2; } /* Create or resize the zone task pools. */ if (zmgr->zonetasks == NULL) { result = isc_taskpool_create(zmgr->taskmgr, zmgr->mctx, ntasks, 2, false, &pool); } else { result = isc_taskpool_expand(&zmgr->zonetasks, ntasks, false, &pool); } if (result == ISC_R_SUCCESS) { zmgr->zonetasks = pool; } /* * We always set all tasks in the zone-load task pool to * privileged. This prevents other tasks in the system from * running while the server task manager is in privileged * mode. */ pool = NULL; if (zmgr->loadtasks == NULL) { result = isc_taskpool_create(zmgr->taskmgr, zmgr->mctx, ntasks, UINT_MAX, true, &pool); } else { result = isc_taskpool_expand(&zmgr->loadtasks, ntasks, true, &pool); } if (result == ISC_R_SUCCESS) { zmgr->loadtasks = pool; } /* Create or resize the zone memory context pool. */ if (zmgr->mctxpool == NULL) { result = isc_pool_create(zmgr->mctx, nmctx, mctxfree, mctxinit, NULL, &mctxpool); } else { result = isc_pool_expand(&zmgr->mctxpool, nmctx, &mctxpool); } if (result == ISC_R_SUCCESS) { zmgr->mctxpool = mctxpool; } return (result); } static void zonemgr_free(dns_zonemgr_t *zmgr) { isc_mem_t *mctx; INSIST(ISC_LIST_EMPTY(zmgr->zones)); zmgr->magic = 0; isc_refcount_destroy(&zmgr->refs); isc_mutex_destroy(&zmgr->iolock); isc_ratelimiter_detach(&zmgr->checkdsrl); isc_ratelimiter_detach(&zmgr->notifyrl); isc_ratelimiter_detach(&zmgr->refreshrl); isc_ratelimiter_detach(&zmgr->startupnotifyrl); isc_ratelimiter_detach(&zmgr->startuprefreshrl); isc_rwlock_destroy(&zmgr->urlock); isc_rwlock_destroy(&zmgr->rwlock); isc_rwlock_destroy(&zmgr->tlsctx_cache_rwlock); zonemgr_keymgmt_destroy(zmgr); mctx = zmgr->mctx; if (zmgr->tlsctx_cache != NULL) { isc_tlsctx_cache_detach(&zmgr->tlsctx_cache); } isc_mem_put(zmgr->mctx, zmgr, sizeof(*zmgr)); isc_mem_detach(&mctx); } void dns_zonemgr_settransfersin(dns_zonemgr_t *zmgr, uint32_t value) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); zmgr->transfersin = value; } uint32_t dns_zonemgr_gettransfersin(dns_zonemgr_t *zmgr) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); return (zmgr->transfersin); } void dns_zonemgr_settransfersperns(dns_zonemgr_t *zmgr, uint32_t value) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); zmgr->transfersperns = value; } uint32_t dns_zonemgr_gettransfersperns(dns_zonemgr_t *zmgr) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); return (zmgr->transfersperns); } isc_taskmgr_t * dns_zonemgr_gettaskmgr(dns_zonemgr_t *zmgr) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); return (zmgr->taskmgr); } isc_timermgr_t * dns_zonemgr_gettimermgr(dns_zonemgr_t *zmgr) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); return (zmgr->timermgr); } /* * Try to start a new incoming zone transfer to fill a quota * slot that was just vacated. * * Requires: * The zone manager is locked by the caller. */ static void zmgr_resume_xfrs(dns_zonemgr_t *zmgr, bool multi) { dns_zone_t *zone; dns_zone_t *next; for (zone = ISC_LIST_HEAD(zmgr->waiting_for_xfrin); zone != NULL; zone = next) { isc_result_t result; next = ISC_LIST_NEXT(zone, statelink); result = zmgr_start_xfrin_ifquota(zmgr, zone); if (result == ISC_R_SUCCESS) { if (multi) { continue; } /* * We successfully filled the slot. We're done. */ break; } else if (result == ISC_R_QUOTA) { /* * Not enough quota. This is probably the per-server * quota, because we usually get called when a unit of * global quota has just been freed. Try the next * zone, it may succeed if it uses another primary. */ continue; } else { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_DEBUG(1), "starting zone transfer: %s", isc_result_totext(result)); break; } } } /* * Try to start an incoming zone transfer for 'zone', quota permitting. * * Requires: * The zone manager is locked by the caller. * * Returns: * ISC_R_SUCCESS There was enough quota and we attempted to * start a transfer. zone_xfrdone() has been or will * be called. * ISC_R_QUOTA Not enough quota. * Others Failure. */ static isc_result_t zmgr_start_xfrin_ifquota(dns_zonemgr_t *zmgr, dns_zone_t *zone) { dns_peer_t *peer = NULL; isc_netaddr_t primaryip; uint32_t nxfrsin, nxfrsperns; dns_zone_t *x; uint32_t maxtransfersin, maxtransfersperns; isc_event_t *e; /* * If we are exiting just pretend we got quota so the zone will * be cleaned up in the zone's task context. */ LOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { UNLOCK_ZONE(zone); goto gotquota; } /* * Find any configured information about the server we'd * like to transfer this zone from. */ isc_netaddr_fromsockaddr(&primaryip, &zone->primaryaddr); (void)dns_peerlist_peerbyaddr(zone->view->peers, &primaryip, &peer); UNLOCK_ZONE(zone); /* * Determine the total maximum number of simultaneous * transfers allowed, and the maximum for this specific * primary. */ maxtransfersin = zmgr->transfersin; maxtransfersperns = zmgr->transfersperns; if (peer != NULL) { (void)dns_peer_gettransfers(peer, &maxtransfersperns); } /* * Count the total number of transfers that are in progress, * and the number of transfers in progress from this primary. * We linearly scan a list of all transfers; if this turns * out to be too slow, we could hash on the primary address. */ nxfrsin = nxfrsperns = 0; for (x = ISC_LIST_HEAD(zmgr->xfrin_in_progress); x != NULL; x = ISC_LIST_NEXT(x, statelink)) { isc_netaddr_t xip; LOCK_ZONE(x); isc_netaddr_fromsockaddr(&xip, &x->primaryaddr); UNLOCK_ZONE(x); nxfrsin++; if (isc_netaddr_equal(&xip, &primaryip)) { nxfrsperns++; } } /* Enforce quota. */ if (nxfrsin >= maxtransfersin) { return (ISC_R_QUOTA); } if (nxfrsperns >= maxtransfersperns) { return (ISC_R_QUOTA); } gotquota: /* * We have sufficient quota. Move the zone to the "xfrin_in_progress" * list and send it an event to let it start the actual transfer in the * context of its own task. */ e = isc_event_allocate(zmgr->mctx, zmgr, DNS_EVENT_ZONESTARTXFRIN, got_transfer_quota, zone, sizeof(isc_event_t)); LOCK_ZONE(zone); INSIST(zone->statelist == &zmgr->waiting_for_xfrin); ISC_LIST_UNLINK(zmgr->waiting_for_xfrin, zone, statelink); ISC_LIST_APPEND(zmgr->xfrin_in_progress, zone, statelink); zone->statelist = &zmgr->xfrin_in_progress; isc_task_send(zone->task, &e); dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, "Transfer started."); UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } void dns_zonemgr_setiolimit(dns_zonemgr_t *zmgr, uint32_t iolimit) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); REQUIRE(iolimit > 0); zmgr->iolimit = iolimit; } uint32_t dns_zonemgr_getiolimit(dns_zonemgr_t *zmgr) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); return (zmgr->iolimit); } /* * Get permission to request a file handle from the OS. * An event will be sent to action when one is available. * There are two queues available (high and low), the high * queue will be serviced before the low one. * * zonemgr_putio() must be called after the event is delivered to * 'action'. */ static isc_result_t zonemgr_getio(dns_zonemgr_t *zmgr, bool high, isc_task_t *task, isc_taskaction_t action, void *arg, dns_io_t **iop) { dns_io_t *io; bool queue; REQUIRE(DNS_ZONEMGR_VALID(zmgr)); REQUIRE(iop != NULL && *iop == NULL); io = isc_mem_get(zmgr->mctx, sizeof(*io)); io->event = isc_event_allocate(zmgr->mctx, task, DNS_EVENT_IOREADY, action, arg, sizeof(*io->event)); io->zmgr = zmgr; io->high = high; io->task = NULL; isc_task_attach(task, &io->task); ISC_LINK_INIT(io, link); io->magic = IO_MAGIC; LOCK(&zmgr->iolock); zmgr->ioactive++; queue = (zmgr->ioactive > zmgr->iolimit); if (queue) { if (io->high) { ISC_LIST_APPEND(zmgr->high, io, link); } else { ISC_LIST_APPEND(zmgr->low, io, link); } } UNLOCK(&zmgr->iolock); *iop = io; if (!queue) { isc_task_send(io->task, &io->event); } return (ISC_R_SUCCESS); } static void zonemgr_putio(dns_io_t **iop) { dns_io_t *io; dns_io_t *next; dns_zonemgr_t *zmgr; REQUIRE(iop != NULL); io = *iop; *iop = NULL; REQUIRE(DNS_IO_VALID(io)); INSIST(!ISC_LINK_LINKED(io, link)); INSIST(io->event == NULL); zmgr = io->zmgr; isc_task_detach(&io->task); io->magic = 0; isc_mem_put(zmgr->mctx, io, sizeof(*io)); LOCK(&zmgr->iolock); INSIST(zmgr->ioactive > 0); zmgr->ioactive--; next = HEAD(zmgr->high); if (next == NULL) { next = HEAD(zmgr->low); } if (next != NULL) { if (next->high) { ISC_LIST_UNLINK(zmgr->high, next, link); } else { ISC_LIST_UNLINK(zmgr->low, next, link); } INSIST(next->event != NULL); } UNLOCK(&zmgr->iolock); if (next != NULL) { isc_task_send(next->task, &next->event); } } static void zonemgr_cancelio(dns_io_t *io) { bool send_event = false; REQUIRE(DNS_IO_VALID(io)); /* * If we are queued to be run then dequeue. */ LOCK(&io->zmgr->iolock); if (ISC_LINK_LINKED(io, link)) { if (io->high) { ISC_LIST_UNLINK(io->zmgr->high, io, link); } else { ISC_LIST_UNLINK(io->zmgr->low, io, link); } send_event = true; INSIST(io->event != NULL); } UNLOCK(&io->zmgr->iolock); if (send_event) { io->event->ev_attributes |= ISC_EVENTATTR_CANCELED; isc_task_send(io->task, &io->event); } } static void zone_saveunique(dns_zone_t *zone, const char *path, const char *templat) { char *buf; int buflen; isc_result_t result; buflen = strlen(path) + strlen(templat) + 2; buf = isc_mem_get(zone->mctx, buflen); result = isc_file_template(path, templat, buf, buflen); if (result != ISC_R_SUCCESS) { goto cleanup; } result = isc_file_renameunique(path, buf); if (result != ISC_R_SUCCESS) { goto cleanup; } dns_zone_log(zone, ISC_LOG_WARNING, "unable to load from '%s'; " "renaming file to '%s' for failure analysis and " "retransferring.", path, buf); cleanup: isc_mem_put(zone->mctx, buf, buflen); } static void setrl(isc_ratelimiter_t *rl, unsigned int *rate, unsigned int value) { isc_interval_t interval; uint32_t s, ns; uint32_t pertic; isc_result_t result; if (value == 0) { value = 1; } if (value == 1) { s = 1; ns = 0; pertic = 1; } else if (value <= 10) { s = 0; ns = 1000000000 / value; pertic = 1; } else { s = 0; ns = (1000000000 / value) * 10; pertic = 10; } isc_interval_set(&interval, s, ns); result = isc_ratelimiter_setinterval(rl, &interval); RUNTIME_CHECK(result == ISC_R_SUCCESS); isc_ratelimiter_setpertic(rl, pertic); *rate = value; } void dns_zonemgr_setcheckdsrate(dns_zonemgr_t *zmgr, unsigned int value) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); setrl(zmgr->checkdsrl, &zmgr->checkdsrate, value); } void dns_zonemgr_setnotifyrate(dns_zonemgr_t *zmgr, unsigned int value) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); setrl(zmgr->notifyrl, &zmgr->notifyrate, value); } void dns_zonemgr_setstartupnotifyrate(dns_zonemgr_t *zmgr, unsigned int value) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); setrl(zmgr->startupnotifyrl, &zmgr->startupnotifyrate, value); } void dns_zonemgr_setserialqueryrate(dns_zonemgr_t *zmgr, unsigned int value) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); setrl(zmgr->refreshrl, &zmgr->serialqueryrate, value); /* XXXMPA separate out once we have the code to support this. */ setrl(zmgr->startuprefreshrl, &zmgr->startupserialqueryrate, value); } unsigned int dns_zonemgr_getnotifyrate(dns_zonemgr_t *zmgr) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); return (zmgr->notifyrate); } unsigned int dns_zonemgr_getstartupnotifyrate(dns_zonemgr_t *zmgr) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); return (zmgr->startupnotifyrate); } unsigned int dns_zonemgr_getserialqueryrate(dns_zonemgr_t *zmgr) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); return (zmgr->serialqueryrate); } bool dns_zonemgr_unreachable(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, isc_sockaddr_t *local, isc_time_t *now) { unsigned int i; uint32_t seconds = isc_time_seconds(now); uint32_t count = 0; REQUIRE(DNS_ZONEMGR_VALID(zmgr)); RWLOCK(&zmgr->urlock, isc_rwlocktype_read); for (i = 0; i < UNREACH_CACHE_SIZE; i++) { if (atomic_load(&zmgr->unreachable[i].expire) >= seconds && isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) && isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) { atomic_store_relaxed(&zmgr->unreachable[i].last, seconds); count = zmgr->unreachable[i].count; break; } } RWUNLOCK(&zmgr->urlock, isc_rwlocktype_read); return (i < UNREACH_CACHE_SIZE && count > 1U); } void dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, isc_sockaddr_t *local) { unsigned int i; char primary[ISC_SOCKADDR_FORMATSIZE]; char source[ISC_SOCKADDR_FORMATSIZE]; isc_sockaddr_format(remote, primary, sizeof(primary)); isc_sockaddr_format(local, source, sizeof(source)); REQUIRE(DNS_ZONEMGR_VALID(zmgr)); RWLOCK(&zmgr->urlock, isc_rwlocktype_read); for (i = 0; i < UNREACH_CACHE_SIZE; i++) { if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) && isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) { atomic_store_relaxed(&zmgr->unreachable[i].expire, 0); break; } } RWUNLOCK(&zmgr->urlock, isc_rwlocktype_read); } void dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, isc_sockaddr_t *local, isc_time_t *now) { uint32_t seconds = isc_time_seconds(now); uint32_t expire = 0, last = seconds; unsigned int slot = UNREACH_CACHE_SIZE, oldest = 0; bool update_entry = true; REQUIRE(DNS_ZONEMGR_VALID(zmgr)); RWLOCK(&zmgr->urlock, isc_rwlocktype_write); for (unsigned int i = 0; i < UNREACH_CACHE_SIZE; i++) { /* Existing entry? */ if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) && isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) { update_entry = false; slot = i; expire = atomic_load_relaxed( &zmgr->unreachable[i].expire); break; } /* Pick first empty slot? */ if (atomic_load_relaxed(&zmgr->unreachable[i].expire) < seconds) { slot = i; break; } /* The worst case, least recently used slot? */ if (atomic_load_relaxed(&zmgr->unreachable[i].last) < last) { last = atomic_load_relaxed(&zmgr->unreachable[i].last); oldest = i; } } /* We haven't found any existing or free slots, use the oldest */ if (slot == UNREACH_CACHE_SIZE) { slot = oldest; } if (expire < seconds) { /* Expired or new entry, reset count to 1 */ zmgr->unreachable[slot].count = 1; } else { zmgr->unreachable[slot].count++; } atomic_store_relaxed(&zmgr->unreachable[slot].expire, seconds + UNREACH_HOLD_TIME); atomic_store_relaxed(&zmgr->unreachable[slot].last, seconds); if (update_entry) { zmgr->unreachable[slot].remote = *remote; zmgr->unreachable[slot].local = *local; } RWUNLOCK(&zmgr->urlock, isc_rwlocktype_write); } void dns_zone_forcereload(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); if (zone->type == dns_zone_primary || (zone->type == dns_zone_redirect && zone->primaries == NULL)) { return; } LOCK_ZONE(zone); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_FORCEXFER); UNLOCK_ZONE(zone); dns_zone_refresh(zone); } bool dns_zone_isforced(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER)); } isc_result_t dns_zone_setstatistics(dns_zone_t *zone, bool on) { /* * This function is obsoleted. */ UNUSED(zone); UNUSED(on); return (ISC_R_NOTIMPLEMENTED); } uint64_t * dns_zone_getstatscounters(dns_zone_t *zone) { /* * This function is obsoleted. */ UNUSED(zone); return (NULL); } void dns_zone_setstats(dns_zone_t *zone, isc_stats_t *stats) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(zone->stats == NULL); LOCK_ZONE(zone); zone->stats = NULL; isc_stats_attach(stats, &zone->stats); UNLOCK_ZONE(zone); } void dns_zone_setrequeststats(dns_zone_t *zone, isc_stats_t *stats) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->requeststats_on && stats == NULL) { zone->requeststats_on = false; } else if (!zone->requeststats_on && stats != NULL) { if (zone->requeststats == NULL) { isc_stats_attach(stats, &zone->requeststats); } zone->requeststats_on = true; } UNLOCK_ZONE(zone); } void dns_zone_setrcvquerystats(dns_zone_t *zone, dns_stats_t *stats) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (zone->requeststats_on && stats != NULL) { if (zone->rcvquerystats == NULL) { dns_stats_attach(stats, &zone->rcvquerystats); zone->requeststats_on = true; } } UNLOCK_ZONE(zone); } void dns_zone_setdnssecsignstats(dns_zone_t *zone, dns_stats_t *stats) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (stats != NULL && zone->dnssecsignstats == NULL) { dns_stats_attach(stats, &zone->dnssecsignstats); } UNLOCK_ZONE(zone); } dns_stats_t * dns_zone_getdnssecsignstats(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->dnssecsignstats); } isc_stats_t * dns_zone_getrequeststats(dns_zone_t *zone) { /* * We don't lock zone for efficiency reason. This is not catastrophic * because requeststats must always be valid when requeststats_on is * true. * Some counters may be incremented while requeststats_on is becoming * false, or some cannot be incremented just after the statistics are * installed, but it shouldn't matter much in practice. */ if (zone->requeststats_on) { return (zone->requeststats); } else { return (NULL); } } /* * Return the received query stats bucket * see note from dns_zone_getrequeststats() */ dns_stats_t * dns_zone_getrcvquerystats(dns_zone_t *zone) { if (zone->requeststats_on) { return (zone->rcvquerystats); } else { return (NULL); } } void dns_zone_dialup(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); zone_debuglog(zone, "dns_zone_dialup", 3, "notify = %d, refresh = %d", DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALNOTIFY), DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALNOTIFY)) { dns_zone_notify(zone); } if (zone->type != dns_zone_primary && zone->primaries != NULL && DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) { dns_zone_refresh(zone); } } void dns_zone_setdialup(dns_zone_t *zone, dns_dialuptype_t dialup) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_DIALNOTIFY | DNS_ZONEFLG_DIALREFRESH | DNS_ZONEFLG_NOREFRESH); switch (dialup) { case dns_dialuptype_no: break; case dns_dialuptype_yes: DNS_ZONE_SETFLAG(zone, (DNS_ZONEFLG_DIALNOTIFY | DNS_ZONEFLG_DIALREFRESH | DNS_ZONEFLG_NOREFRESH)); break; case dns_dialuptype_notify: DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DIALNOTIFY); break; case dns_dialuptype_notifypassive: DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DIALNOTIFY); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOREFRESH); break; case dns_dialuptype_refresh: DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DIALREFRESH); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOREFRESH); break; case dns_dialuptype_passive: DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOREFRESH); break; default: UNREACHABLE(); } UNLOCK_ZONE(zone); } isc_result_t dns_zone_setkeydirectory(dns_zone_t *zone, const char *directory) { isc_result_t result = ISC_R_SUCCESS; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); result = dns_zone_setstring(zone, &zone->keydirectory, directory); UNLOCK_ZONE(zone); return (result); } const char * dns_zone_getkeydirectory(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->keydirectory); } unsigned int dns_zonemgr_getcount(dns_zonemgr_t *zmgr, int state) { dns_zone_t *zone; unsigned int count = 0; REQUIRE(DNS_ZONEMGR_VALID(zmgr)); RWLOCK(&zmgr->rwlock, isc_rwlocktype_read); switch (state) { case DNS_ZONESTATE_XFERRUNNING: for (zone = ISC_LIST_HEAD(zmgr->xfrin_in_progress); zone != NULL; zone = ISC_LIST_NEXT(zone, statelink)) { count++; } break; case DNS_ZONESTATE_XFERDEFERRED: for (zone = ISC_LIST_HEAD(zmgr->waiting_for_xfrin); zone != NULL; zone = ISC_LIST_NEXT(zone, statelink)) { count++; } break; case DNS_ZONESTATE_SOAQUERY: for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL; zone = ISC_LIST_NEXT(zone, link)) { if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)) { count++; } } break; case DNS_ZONESTATE_ANY: for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL; zone = ISC_LIST_NEXT(zone, link)) { dns_view_t *view = zone->view; if (view != NULL && strcmp(view->name, "_bind") == 0) { continue; } count++; } break; case DNS_ZONESTATE_AUTOMATIC: for (zone = ISC_LIST_HEAD(zmgr->zones); zone != NULL; zone = ISC_LIST_NEXT(zone, link)) { dns_view_t *view = zone->view; if (view != NULL && strcmp(view->name, "_bind") == 0) { continue; } if (zone->automatic) { count++; } } break; default: UNREACHABLE(); } RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read); return (count); } void dns_zone_lock_keyfiles(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); if (zone->kasp == NULL) { /* No need to lock, nothing is writing key files. */ return; } REQUIRE(DNS_KEYFILEIO_VALID(zone->kfio)); isc_mutex_lock(&zone->kfio->lock); } void dns_zone_unlock_keyfiles(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); if (zone->kasp == NULL) { /* No need to lock, nothing is writing key files. */ return; } REQUIRE(DNS_KEYFILEIO_VALID(zone->kfio)); isc_mutex_unlock(&zone->kfio->lock); } isc_result_t dns_zone_checknames(dns_zone_t *zone, const dns_name_t *name, dns_rdata_t *rdata) { bool ok = true; bool fail = false; char namebuf[DNS_NAME_FORMATSIZE]; char namebuf2[DNS_NAME_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; int level = ISC_LOG_WARNING; dns_name_t bad; REQUIRE(DNS_ZONE_VALID(zone)); if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMES) && rdata->type != dns_rdatatype_nsec3) { return (ISC_R_SUCCESS); } if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKNAMESFAIL) || rdata->type == dns_rdatatype_nsec3) { level = ISC_LOG_ERROR; fail = true; } ok = dns_rdata_checkowner(name, rdata->rdclass, rdata->type, true); if (!ok) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(rdata->type, typebuf, sizeof(typebuf)); dns_zone_log(zone, level, "%s/%s: %s", namebuf, typebuf, isc_result_totext(DNS_R_BADOWNERNAME)); if (fail) { return (DNS_R_BADOWNERNAME); } } dns_name_init(&bad, NULL); ok = dns_rdata_checknames(rdata, name, &bad); if (!ok) { dns_name_format(name, namebuf, sizeof(namebuf)); dns_name_format(&bad, namebuf2, sizeof(namebuf2)); dns_rdatatype_format(rdata->type, typebuf, sizeof(typebuf)); dns_zone_log(zone, level, "%s/%s: %s: %s ", namebuf, typebuf, namebuf2, isc_result_totext(DNS_R_BADNAME)); if (fail) { return (DNS_R_BADNAME); } } return (ISC_R_SUCCESS); } void dns_zone_setcheckmx(dns_zone_t *zone, dns_checkmxfunc_t checkmx) { REQUIRE(DNS_ZONE_VALID(zone)); zone->checkmx = checkmx; } void dns_zone_setchecksrv(dns_zone_t *zone, dns_checksrvfunc_t checksrv) { REQUIRE(DNS_ZONE_VALID(zone)); zone->checksrv = checksrv; } void dns_zone_setcheckns(dns_zone_t *zone, dns_checknsfunc_t checkns) { REQUIRE(DNS_ZONE_VALID(zone)); zone->checkns = checkns; } void dns_zone_setisself(dns_zone_t *zone, dns_isselffunc_t isself, void *arg) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->isself = isself; zone->isselfarg = arg; UNLOCK_ZONE(zone); } void dns_zone_setnotifydelay(dns_zone_t *zone, uint32_t delay) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->notifydelay = delay; UNLOCK_ZONE(zone); } uint32_t dns_zone_getnotifydelay(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->notifydelay); } isc_result_t dns_zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, bool deleteit) { isc_result_t result; REQUIRE(DNS_ZONE_VALID(zone)); dnssec_log(zone, ISC_LOG_NOTICE, "dns_zone_signwithkey(algorithm=%u, keyid=%u)", algorithm, keyid); LOCK_ZONE(zone); result = zone_signwithkey(zone, algorithm, keyid, deleteit); UNLOCK_ZONE(zone); return (result); } /* * Called when a dynamic update for an NSEC3PARAM record is received. * * If set, transform the NSEC3 salt into human-readable form so that it can be * logged. Then call zone_addnsec3chain(), passing NSEC3PARAM RDATA to it. */ isc_result_t dns_zone_addnsec3chain(dns_zone_t *zone, dns_rdata_nsec3param_t *nsec3param) { isc_result_t result; char salt[255 * 2 + 1]; REQUIRE(DNS_ZONE_VALID(zone)); result = dns_nsec3param_salttotext(nsec3param, salt, sizeof(salt)); RUNTIME_CHECK(result == ISC_R_SUCCESS); dnssec_log(zone, ISC_LOG_NOTICE, "dns_zone_addnsec3chain(hash=%u, iterations=%u, salt=%s)", nsec3param->hash, nsec3param->iterations, salt); LOCK_ZONE(zone); result = zone_addnsec3chain(zone, nsec3param); UNLOCK_ZONE(zone); return (result); } void dns_zone_setnodes(dns_zone_t *zone, uint32_t nodes) { REQUIRE(DNS_ZONE_VALID(zone)); if (nodes == 0) { nodes = 1; } zone->nodes = nodes; } void dns_zone_setsignatures(dns_zone_t *zone, uint32_t signatures) { REQUIRE(DNS_ZONE_VALID(zone)); /* * We treat signatures as a signed value so explicitly * limit its range here. */ if (signatures > INT32_MAX) { signatures = INT32_MAX; } else if (signatures == 0) { signatures = 1; } zone->signatures = signatures; } uint32_t dns_zone_getsignatures(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->signatures); } void dns_zone_setprivatetype(dns_zone_t *zone, dns_rdatatype_t type) { REQUIRE(DNS_ZONE_VALID(zone)); zone->privatetype = type; } dns_rdatatype_t dns_zone_getprivatetype(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->privatetype); } static isc_result_t zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, bool deleteit) { dns_signing_t *signing; dns_signing_t *current; isc_result_t result = ISC_R_SUCCESS; isc_time_t now; dns_db_t *db = NULL; signing = isc_mem_get(zone->mctx, sizeof *signing); signing->magic = 0; signing->db = NULL; signing->dbiterator = NULL; signing->algorithm = algorithm; signing->keyid = keyid; signing->deleteit = deleteit; signing->done = false; TIME_NOW(&now); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { result = ISC_R_NOTFOUND; goto cleanup; } dns_db_attach(db, &signing->db); for (current = ISC_LIST_HEAD(zone->signing); current != NULL; current = ISC_LIST_NEXT(current, link)) { if (current->db == signing->db && current->algorithm == signing->algorithm && current->keyid == signing->keyid) { if (current->deleteit != signing->deleteit) { current->done = true; } else { goto cleanup; } } } result = dns_db_createiterator(signing->db, 0, &signing->dbiterator); if (result == ISC_R_SUCCESS) { result = dns_dbiterator_first(signing->dbiterator); } if (result == ISC_R_SUCCESS) { dns_dbiterator_pause(signing->dbiterator); ISC_LIST_INITANDAPPEND(zone->signing, signing, link); signing = NULL; if (isc_time_isepoch(&zone->signingtime)) { zone->signingtime = now; if (zone->task != NULL) { zone_settimer(zone, &now); } } } cleanup: if (signing != NULL) { if (signing->db != NULL) { dns_db_detach(&signing->db); } if (signing->dbiterator != NULL) { dns_dbiterator_destroy(&signing->dbiterator); } isc_mem_put(zone->mctx, signing, sizeof *signing); } if (db != NULL) { dns_db_detach(&db); } return (result); } /* Called once; *timep should be set to the current time. */ static isc_result_t next_keyevent(dst_key_t *key, isc_stdtime_t *timep) { isc_result_t result; isc_stdtime_t now, then = 0, event; int i; now = *timep; for (i = 0; i <= DST_MAX_TIMES; i++) { result = dst_key_gettime(key, i, &event); if (result == ISC_R_SUCCESS && event > now && (then == 0 || event < then)) { then = event; } } if (then != 0) { *timep = then; return (ISC_R_SUCCESS); } return (ISC_R_NOTFOUND); } static isc_result_t rr_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, const dns_rdata_t *rdata, bool *flag) { dns_rdataset_t rdataset; dns_dbnode_t *node = NULL; isc_result_t result; dns_rdataset_init(&rdataset); if (rdata->type == dns_rdatatype_nsec3) { CHECK(dns_db_findnsec3node(db, name, false, &node)); } else { CHECK(dns_db_findnode(db, name, false, &node)); } result = dns_db_findrdataset(db, node, ver, rdata->type, 0, (isc_stdtime_t)0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { *flag = false; result = ISC_R_SUCCESS; goto failure; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t myrdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &myrdata); if (!dns_rdata_compare(&myrdata, rdata)) { break; } } dns_rdataset_disassociate(&rdataset); if (result == ISC_R_SUCCESS) { *flag = true; } else if (result == ISC_R_NOMORE) { *flag = false; result = ISC_R_SUCCESS; } failure: if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } /* * Add records to signal the state of signing or of key removal. */ static isc_result_t add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype, dns_dbversion_t *ver, dns_diff_t *diff, bool sign_all) { dns_difftuple_t *tuple, *newtuple = NULL; dns_rdata_dnskey_t dnskey; dns_rdata_t rdata = DNS_RDATA_INIT; bool flag; isc_region_t r; isc_result_t result = ISC_R_SUCCESS; uint16_t keyid; unsigned char buf[5]; dns_name_t *name = dns_db_origin(db); for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link)) { if (tuple->rdata.type != dns_rdatatype_dnskey) { continue; } result = dns_rdata_tostruct(&tuple->rdata, &dnskey, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if ((dnskey.flags & (DNS_KEYFLAG_OWNERMASK | DNS_KEYTYPE_NOAUTH)) != DNS_KEYOWNER_ZONE) { continue; } dns_rdata_toregion(&tuple->rdata, &r); keyid = dst_region_computeid(&r); buf[0] = dnskey.algorithm; buf[1] = (keyid & 0xff00) >> 8; buf[2] = (keyid & 0xff); buf[3] = (tuple->op == DNS_DIFFOP_ADD) ? 0 : 1; buf[4] = 0; rdata.data = buf; rdata.length = sizeof(buf); rdata.type = privatetype; rdata.rdclass = tuple->rdata.rdclass; if (sign_all || tuple->op == DNS_DIFFOP_DEL) { CHECK(rr_exists(db, ver, name, &rdata, &flag)); if (flag) { continue; } CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, name, 0, &rdata, &newtuple)); CHECK(do_one_tuple(&newtuple, db, ver, diff)); INSIST(newtuple == NULL); } /* * Remove any record which says this operation has already * completed. */ buf[4] = 1; CHECK(rr_exists(db, ver, name, &rdata, &flag)); if (flag) { CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, name, 0, &rdata, &newtuple)); CHECK(do_one_tuple(&newtuple, db, ver, diff)); INSIST(newtuple == NULL); } } failure: return (result); } /* * See if dns__zone_updatesigs() will update signature for RRset 'rrtype' at * the apex, and if not tickle them and cause to sign so that newly activated * keys are used. */ static isc_result_t tickle_apex_rrset(dns_rdatatype_t rrtype, dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, dns_diff_t *diff, dns__zonediff_t *zonediff, dst_key_t **keys, unsigned int nkeys, isc_stdtime_t inception, isc_stdtime_t keyexpire, bool check_ksk, bool keyset_kskonly) { dns_difftuple_t *tuple; isc_result_t result; for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link)) { if (tuple->rdata.type == rrtype && dns_name_equal(&tuple->name, &zone->origin)) { break; } } if (tuple == NULL) { result = del_sigs(zone, db, ver, &zone->origin, rrtype, zonediff, keys, nkeys, now, false); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "sign_apex:del_sigs -> %s", isc_result_totext(result)); return (result); } result = add_sigs(db, ver, &zone->origin, zone, rrtype, zonediff->diff, keys, nkeys, zone->mctx, now, inception, keyexpire, check_ksk, keyset_kskonly); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "sign_apex:add_sigs -> %s", isc_result_totext(result)); return (result); } } return (ISC_R_SUCCESS); } static isc_result_t sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, dns_diff_t *diff, dns__zonediff_t *zonediff) { isc_result_t result; isc_stdtime_t inception, soaexpire, keyexpire; bool check_ksk, keyset_kskonly; dst_key_t *zone_keys[DNS_MAXZONEKEYS]; unsigned int nkeys = 0, i; result = dns__zone_findkeys(zone, db, ver, now, zone->mctx, DNS_MAXZONEKEYS, zone_keys, &nkeys); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "sign_apex:dns__zone_findkeys -> %s", isc_result_totext(result)); return (result); } inception = now - 3600; /* Allow for clock skew. */ soaexpire = now + dns_zone_getsigvalidityinterval(zone); keyexpire = dns_zone_getkeyvalidityinterval(zone); if (keyexpire == 0) { keyexpire = soaexpire - 1; } else { keyexpire += now; } check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK); keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY); /* * See if dns__zone_updatesigs() will update DNSKEY/CDS/CDNSKEY * signature and if not cause them to sign so that newly activated * keys are used. */ result = tickle_apex_rrset(dns_rdatatype_dnskey, zone, db, ver, now, diff, zonediff, zone_keys, nkeys, inception, keyexpire, check_ksk, keyset_kskonly); if (result != ISC_R_SUCCESS) { goto failure; } result = tickle_apex_rrset(dns_rdatatype_cds, zone, db, ver, now, diff, zonediff, zone_keys, nkeys, inception, keyexpire, check_ksk, keyset_kskonly); if (result != ISC_R_SUCCESS) { goto failure; } result = tickle_apex_rrset(dns_rdatatype_cdnskey, zone, db, ver, now, diff, zonediff, zone_keys, nkeys, inception, keyexpire, check_ksk, keyset_kskonly); if (result != ISC_R_SUCCESS) { goto failure; } result = dns__zone_updatesigs(diff, db, ver, zone_keys, nkeys, zone, inception, soaexpire, keyexpire, now, check_ksk, keyset_kskonly, zonediff); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "sign_apex:dns__zone_updatesigs -> %s", isc_result_totext(result)); goto failure; } failure: for (i = 0; i < nkeys; i++) { dst_key_free(&zone_keys[i]); } return (result); } static isc_result_t clean_nsec3param(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff) { isc_result_t result; dns_dbnode_t *node = NULL; dns_rdataset_t rdataset; dns_rdataset_init(&rdataset); CHECK(dns_db_getoriginnode(db, &node)); result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, dns_rdatatype_none, 0, &rdataset, NULL); if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (result != ISC_R_NOTFOUND) { goto failure; } result = dns_nsec3param_deletechains(db, ver, zone, true, diff); failure: if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } /* * Given an RRSIG rdataset and an algorithm, determine whether there * are any signatures using that algorithm. */ static bool signed_with_alg(dns_rdataset_t *rdataset, dns_secalg_t alg) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_rrsig_t rrsig; isc_result_t result; REQUIRE(rdataset == NULL || rdataset->type == dns_rdatatype_rrsig); if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) { return (false); } for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdataset_current(rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); if (rrsig.algorithm == alg) { return (true); } } return (false); } static isc_result_t add_chains(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff) { dns_name_t *origin; bool build_nsec3; isc_result_t result; origin = dns_db_origin(db); CHECK(dns_private_chains(db, ver, zone->privatetype, NULL, &build_nsec3)); if (build_nsec3) { CHECK(dns_nsec3_addnsec3sx(db, ver, origin, zone_nsecttl(zone), false, zone->privatetype, diff)); } CHECK(updatesecure(db, ver, origin, zone_nsecttl(zone), true, diff)); failure: return (result); } static void dnssec_report(const char *format, ...) { va_list args; va_start(args, format); isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_ZONE, ISC_LOG_INFO, format, args); va_end(args); } static void checkds_destroy(dns_checkds_t *checkds, bool locked) { isc_mem_t *mctx; REQUIRE(DNS_CHECKDS_VALID(checkds)); dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), "checkds: destroy DS query"); if (checkds->zone != NULL) { if (!locked) { LOCK_ZONE(checkds->zone); } REQUIRE(LOCKED_ZONE(checkds->zone)); if (ISC_LINK_LINKED(checkds, link)) { ISC_LIST_UNLINK(checkds->zone->checkds_requests, checkds, link); } if (!locked) { UNLOCK_ZONE(checkds->zone); } if (locked) { zone_idetach(&checkds->zone); } else { dns_zone_idetach(&checkds->zone); } } if (checkds->request != NULL) { dns_request_destroy(&checkds->request); } if (checkds->key != NULL) { dns_tsigkey_detach(&checkds->key); } if (checkds->transport != NULL) { dns_transport_detach(&checkds->transport); } mctx = checkds->mctx; isc_mem_put(checkds->mctx, checkds, sizeof(*checkds)); isc_mem_detach(&mctx); } static isc_result_t make_dnskey(dst_key_t *key, unsigned char *buf, int bufsize, dns_rdata_t *target) { isc_result_t result; isc_buffer_t b; isc_region_t r; isc_buffer_init(&b, buf, bufsize); result = dst_key_todns(key, &b); if (result != ISC_R_SUCCESS) { return (result); } dns_rdata_reset(target); isc_buffer_usedregion(&b, &r); dns_rdata_fromregion(target, dst_key_class(key), dns_rdatatype_dnskey, &r); return (ISC_R_SUCCESS); } static bool do_checkds(dns_zone_t *zone, dst_key_t *key, isc_stdtime_t now, bool dspublish) { dns_kasp_t *kasp = dns_zone_getkasp(zone); const char *dir = dns_zone_getkeydirectory(zone); isc_result_t result; uint32_t count = 0; if (dspublish) { (void)dst_key_getnum(key, DST_NUM_DSPUBCOUNT, &count); count += 1; dst_key_setnum(key, DST_NUM_DSPUBCOUNT, count); dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: %u DS published " "for key %u", count, dst_key_id(key)); if (count != zone->parentalscnt) { return false; } } else { (void)dst_key_getnum(key, DST_NUM_DSDELCOUNT, &count); count += 1; dst_key_setnum(key, DST_NUM_DSDELCOUNT, count); dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: %u DS withdrawn " "for key %u", count, dst_key_id(key)); if (count != zone->parentalscnt) { return false; } } dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: checkds %s for key " "%u", dspublish ? "published" : "withdrawn", dst_key_id(key)); dns_zone_lock_keyfiles(zone); result = dns_keymgr_checkds_id(kasp, &zone->checkds_ok, dir, now, now, dspublish, dst_key_id(key), dst_key_alg(key)); dns_zone_unlock_keyfiles(zone); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_WARNING, "checkds: checkds for key %u failed: %s", dst_key_id(key), isc_result_totext(result)); return false; } return true; } static isc_result_t validate_ds(dns_zone_t *zone, dns_message_t *message) { UNUSED(zone); UNUSED(message); /* Get closest trust anchor */ /* Check that trust anchor is (grand)parent of zone. */ /* Find the DNSKEY signing the message. */ /* Check that DNSKEY is in chain of trust. */ /* Validate DS RRset. */ return (ISC_R_SUCCESS); } static void checkds_done(isc_task_t *task, isc_event_t *event) { char addrbuf[ISC_SOCKADDR_FORMATSIZE]; char rcode[128]; dns_checkds_t *checkds; dns_zone_t *zone; dns_db_t *db = NULL; dns_dbversion_t *version = NULL; dns_dnsseckey_t *key; dns_dnsseckeylist_t keys; dns_kasp_t *kasp = NULL; dns_message_t *message = NULL; dns_rdataset_t *ds_rrset = NULL; dns_requestevent_t *revent = (dns_requestevent_t *)event; isc_buffer_t buf; isc_result_t result; isc_stdtime_t now; isc_time_t timenow; bool rekey = false; bool empty = false; UNUSED(task); checkds = event->ev_arg; REQUIRE(DNS_CHECKDS_VALID(checkds)); zone = checkds->zone; INSIST(task == zone->task); ISC_LIST_INIT(keys); kasp = zone->kasp; INSIST(kasp != NULL); isc_buffer_init(&buf, rcode, sizeof(rcode)); isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf)); dns_zone_log(zone, ISC_LOG_DEBUG(1), "checkds: DS query to %s: done", addrbuf); dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &message); INSIST(message != NULL); CHECK(revent->result); CHECK(dns_request_getresponse(revent->request, message, DNS_MESSAGEPARSE_PRESERVEORDER)); CHECK(dns_rcode_totext(message->rcode, &buf)); dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: DS response from %s: %.*s", addrbuf, (int)buf.used, rcode); /* Validate response. */ CHECK(validate_ds(zone, message)); /* Check RCODE. */ if (message->rcode != dns_rcode_noerror) { dns_zone_log(zone, ISC_LOG_NOTICE, "checkds: bad DS response from %s: %.*s", addrbuf, (int)buf.used, rcode); goto failure; } /* Make sure that either AA or RA bit is set. */ if ((message->flags & DNS_MESSAGEFLAG_AA) == 0 && (message->flags & DNS_MESSAGEFLAG_RA) == 0) { dns_zone_log(zone, ISC_LOG_NOTICE, "checkds: bad DS response from %s: expected AA or " "RA bit set", addrbuf); goto failure; } /* Lookup DS RRset. */ result = dns_message_firstname(message, DNS_SECTION_ANSWER); while (result == ISC_R_SUCCESS) { dns_name_t *name = NULL; dns_rdataset_t *rdataset; dns_message_currentname(message, DNS_SECTION_ANSWER, &name); if (dns_name_compare(&zone->origin, name) != 0) { goto next; } for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { if (rdataset->type != dns_rdatatype_ds) { goto next; } ds_rrset = rdataset; break; } if (ds_rrset != NULL) { break; } next: result = dns_message_nextname(message, DNS_SECTION_ANSWER); } if (ds_rrset == NULL) { empty = true; dns_zone_log(zone, ISC_LOG_NOTICE, "checkds: empty DS response from %s", addrbuf); } TIME_NOW(&timenow); now = isc_time_seconds(&timenow); CHECK(dns_zone_getdb(zone, &db)); dns_db_currentversion(db, &version); KASP_LOCK(kasp); LOCK_ZONE(zone); for (key = ISC_LIST_HEAD(zone->checkds_ok); key != NULL; key = ISC_LIST_NEXT(key, link)) { bool alldone = false, found = false; bool checkdspub = false, checkdsdel = false, ksk = false; dst_key_state_t ds_state = DST_KEY_STATE_NA; isc_stdtime_t published = 0, withdrawn = 0; isc_result_t ret = ISC_R_SUCCESS; /* Is this key have the KSK role? */ (void)dst_key_role(key->key, &ksk, NULL); if (!ksk) { continue; } /* Do we need to check the DS RRset for this key? */ (void)dst_key_getstate(key->key, DST_KEY_DS, &ds_state); (void)dst_key_gettime(key->key, DST_TIME_DSPUBLISH, &published); (void)dst_key_gettime(key->key, DST_TIME_DSDELETE, &withdrawn); if (ds_state == DST_KEY_STATE_RUMOURED && published == 0) { checkdspub = true; } else if (ds_state == DST_KEY_STATE_UNRETENTIVE && withdrawn == 0) { checkdsdel = true; } if (!checkdspub && !checkdsdel) { continue; } if (empty) { goto dswithdrawn; } /* Find the appropriate DS record. */ ret = dns_rdataset_first(ds_rrset); while (ret == ISC_R_SUCCESS) { dns_rdata_ds_t ds; dns_rdata_t dnskey = DNS_RDATA_INIT; dns_rdata_t dsrdata = DNS_RDATA_INIT; dns_rdata_t rdata = DNS_RDATA_INIT; isc_result_t r; unsigned char dsbuf[DNS_DS_BUFFERSIZE]; unsigned char keybuf[DST_KEY_MAXSIZE]; dns_rdataset_current(ds_rrset, &rdata); r = dns_rdata_tostruct(&rdata, &ds, NULL); if (r != ISC_R_SUCCESS) { goto nextds; } /* Check key tag and algorithm. */ if (dst_key_id(key->key) != ds.key_tag) { goto nextds; } if (dst_key_alg(key->key) != ds.algorithm) { goto nextds; } /* Derive DS from DNSKEY, see if the rdata is equal. */ make_dnskey(key->key, keybuf, sizeof(keybuf), &dnskey); r = dns_ds_buildrdata(&zone->origin, &dnskey, ds.digest_type, dsbuf, &dsrdata); if (r != ISC_R_SUCCESS) { goto nextds; } if (dns_rdata_compare(&rdata, &dsrdata) == 0) { found = true; if (checkdspub) { /* DS Published. */ alldone = do_checkds(zone, key->key, now, true); if (alldone) { rekey = true; } } } nextds: ret = dns_rdataset_next(ds_rrset); } dswithdrawn: /* DS withdrawn. */ if (checkdsdel && !found) { alldone = do_checkds(zone, key->key, now, false); if (alldone) { rekey = true; } } } UNLOCK_ZONE(zone); KASP_UNLOCK(kasp); /* Rekey after checkds. */ if (rekey) { dns_zone_rekey(zone, false); } failure: if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: DS request failed: %s", isc_result_totext(result)); } if (version != NULL) { dns_db_closeversion(db, &version, false); } if (db != NULL) { dns_db_detach(&db); } while (!ISC_LIST_EMPTY(keys)) { key = ISC_LIST_HEAD(keys); ISC_LIST_UNLINK(keys, key, link); dns_dnsseckey_destroy(dns_zone_getmctx(zone), &key); } isc_event_free(&event); checkds_destroy(checkds, false); dns_message_detach(&message); } static bool checkds_isqueued(dns_zone_t *zone, isc_sockaddr_t *addr, dns_tsigkey_t *key, dns_transport_t *transport) { dns_checkds_t *checkds; for (checkds = ISC_LIST_HEAD(zone->checkds_requests); checkds != NULL; checkds = ISC_LIST_NEXT(checkds, link)) { if (checkds->request != NULL) { continue; } if (addr != NULL && isc_sockaddr_equal(addr, &checkds->dst) && checkds->key == key && checkds->transport == transport) { return (true); } } return (false); } static isc_result_t checkds_create(isc_mem_t *mctx, unsigned int flags, dns_checkds_t **checkdsp) { dns_checkds_t *checkds; REQUIRE(checkdsp != NULL && *checkdsp == NULL); checkds = isc_mem_get(mctx, sizeof(*checkds)); *checkds = (dns_checkds_t){ .flags = flags, }; isc_mem_attach(mctx, &checkds->mctx); isc_sockaddr_any(&checkds->dst); ISC_LINK_INIT(checkds, link); checkds->magic = CHECKDS_MAGIC; *checkdsp = checkds; return (ISC_R_SUCCESS); } static isc_result_t checkds_createmessage(dns_zone_t *zone, dns_message_t **messagep) { dns_message_t *message = NULL; dns_name_t *tempname = NULL; dns_rdataset_t *temprdataset = NULL; isc_result_t result; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(messagep != NULL && *messagep == NULL); dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message); message->opcode = dns_opcode_query; message->rdclass = zone->rdclass; message->flags |= DNS_MESSAGEFLAG_RD; result = dns_message_gettempname(message, &tempname); if (result != ISC_R_SUCCESS) { goto cleanup; } result = dns_message_gettemprdataset(message, &temprdataset); if (result != ISC_R_SUCCESS) { goto cleanup; } /* * Make question. */ dns_name_init(tempname, NULL); dns_name_clone(&zone->origin, tempname); dns_rdataset_makequestion(temprdataset, zone->rdclass, dns_rdatatype_ds); ISC_LIST_APPEND(tempname->list, temprdataset, link); dns_message_addname(message, tempname, DNS_SECTION_QUESTION); tempname = NULL; temprdataset = NULL; *messagep = message; return (ISC_R_SUCCESS); cleanup: if (tempname != NULL) { dns_message_puttempname(message, &tempname); } if (temprdataset != NULL) { dns_message_puttemprdataset(message, &temprdataset); } dns_message_detach(&message); return (result); } static void checkds_send_toaddr(isc_task_t *task, isc_event_t *event) { dns_checkds_t *checkds; isc_result_t result; dns_message_t *message = NULL; isc_netaddr_t dstip; dns_tsigkey_t *key = NULL; char addrbuf[ISC_SOCKADDR_FORMATSIZE]; isc_sockaddr_t src; unsigned int options, timeout; bool have_checkdssource = false; checkds = event->ev_arg; REQUIRE(DNS_CHECKDS_VALID(checkds)); UNUSED(task); LOCK_ZONE(checkds->zone); checkds->event = NULL; if (DNS_ZONE_FLAG(checkds->zone, DNS_ZONEFLG_LOADED) == 0) { result = ISC_R_CANCELED; goto cleanup; } if ((event->ev_attributes & ISC_EVENTATTR_CANCELED) != 0 || DNS_ZONE_FLAG(checkds->zone, DNS_ZONEFLG_EXITING) || checkds->zone->view->requestmgr == NULL || checkds->zone->db == NULL) { result = ISC_R_CANCELED; goto cleanup; } /* * The raw IPv4 address should also exist. Don't send to the * mapped form. */ if (isc_sockaddr_pf(&checkds->dst) == PF_INET6 && IN6_IS_ADDR_V4MAPPED(&checkds->dst.type.sin6.sin6_addr)) { isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf)); dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), "checkds: ignoring IPv6 mapped IPV4 address: %s", addrbuf); result = ISC_R_CANCELED; goto cleanup; } result = checkds_createmessage(checkds->zone, &message); if (result != ISC_R_SUCCESS) { goto cleanup; } isc_sockaddr_format(&checkds->dst, addrbuf, sizeof(addrbuf)); if (checkds->key != NULL) { /* Transfer ownership of key */ key = checkds->key; checkds->key = NULL; } else { isc_netaddr_fromsockaddr(&dstip, &checkds->dst); result = dns_view_getpeertsig(checkds->zone->view, &dstip, &key); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { dns_zone_log(checkds->zone, ISC_LOG_ERROR, "checkds: DS query to %s not sent. " "Peer TSIG key lookup failure.", addrbuf); goto cleanup_message; } } if (key != NULL) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(&key->name, namebuf, sizeof(namebuf)); dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), "checkds: sending DS query to %s : TSIG (%s)", addrbuf, namebuf); } else { dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), "checkds: sending DS query to %s", addrbuf); } options = 0; if (checkds->zone->view->peers != NULL) { dns_peer_t *peer = NULL; bool usetcp = false; result = dns_peerlist_peerbyaddr(checkds->zone->view->peers, &dstip, &peer); if (result == ISC_R_SUCCESS) { result = dns_peer_getquerysource(peer, &src); if (result == ISC_R_SUCCESS) { have_checkdssource = true; } result = dns_peer_getforcetcp(peer, &usetcp); if (result == ISC_R_SUCCESS && usetcp) { options |= DNS_FETCHOPT_TCP; } } } switch (isc_sockaddr_pf(&checkds->dst)) { case PF_INET: if (!have_checkdssource) { src = checkds->zone->parentalsrc4; } break; case PF_INET6: if (!have_checkdssource) { src = checkds->zone->parentalsrc6; } break; default: result = ISC_R_NOTIMPLEMENTED; goto cleanup_key; } dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), "checkds: create request for DS query to %s", addrbuf); timeout = 5; options |= DNS_REQUESTOPT_TCP; result = dns_request_create( checkds->zone->view->requestmgr, message, &src, &checkds->dst, options, key, timeout * 3 + 1, timeout, 2, checkds->zone->task, checkds_done, checkds, &checkds->request); if (result != ISC_R_SUCCESS) { dns_zone_log(checkds->zone, ISC_LOG_DEBUG(3), "checkds: dns_request_create() to %s failed: %s", addrbuf, isc_result_totext(result)); } cleanup_key: if (key != NULL) { dns_tsigkey_detach(&key); } cleanup_message: dns_message_detach(&message); cleanup: UNLOCK_ZONE(checkds->zone); isc_event_free(&event); if (result != ISC_R_SUCCESS) { checkds_destroy(checkds, false); } } static isc_result_t checkds_send_queue(dns_checkds_t *checkds) { isc_event_t *e; isc_result_t result; INSIST(checkds->event == NULL); e = isc_event_allocate(checkds->mctx, NULL, DNS_EVENT_CHECKDSSENDTOADDR, checkds_send_toaddr, checkds, sizeof(isc_event_t)); e->ev_arg = checkds; e->ev_sender = NULL; result = isc_ratelimiter_enqueue(checkds->zone->zmgr->checkdsrl, checkds->zone->task, &e); if (result != ISC_R_SUCCESS) { isc_event_free(&e); checkds->event = NULL; } return (result); } static void checkds_send(dns_zone_t *zone) { dns_view_t *view = dns_zone_getview(zone); isc_result_t result; unsigned int flags = 0; /* * Zone lock held by caller. */ REQUIRE(LOCKED_ZONE(zone)); dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: start sending DS queries to %u parentals", zone->parentalscnt); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: abort, named exiting"); return; } for (unsigned int i = 0; i < zone->parentalscnt; i++) { dns_tsigkey_t *key = NULL; dns_transport_t *transport = NULL; isc_sockaddr_t dst; dns_checkds_t *checkds = NULL; if ((zone->parentalkeynames != NULL) && (zone->parentalkeynames[i] != NULL)) { dns_name_t *keyname = zone->parentalkeynames[i]; (void)dns_view_gettsig(view, keyname, &key); } if ((zone->parentaltlsnames != NULL) && (zone->parentaltlsnames[i] != NULL)) { dns_name_t *tlsname = zone->parentaltlsnames[i]; (void)dns_view_gettransport(view, DNS_TRANSPORT_TLS, tlsname, &transport); dns_zone_logc( zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, "got TLS configuration for zone transfer"); } dst = zone->parentals[i]; /* TODO: glue the transport to the checkds request */ if (checkds_isqueued(zone, &dst, key, transport)) { dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: DS query to parent " "%d is queued", i); if (key != NULL) { dns_tsigkey_detach(&key); } if (transport != NULL) { dns_transport_detach(&transport); } continue; } dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: create DS query for " "parent %d", i); result = checkds_create(zone->mctx, flags, &checkds); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: create DS query for " "parent %d failed", i); continue; } zone_iattach(zone, &checkds->zone); checkds->dst = dst; INSIST(checkds->key == NULL); if (key != NULL) { checkds->key = key; key = NULL; } INSIST(checkds->transport == NULL); if (transport != NULL) { checkds->transport = transport; transport = NULL; } ISC_LIST_APPEND(zone->checkds_requests, checkds, link); result = checkds_send_queue(checkds); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_DEBUG(3), "checkds: send DS query to " "parent %d failed", i); checkds_destroy(checkds, true); } } } static void zone_checkds(dns_zone_t *zone) { bool cdscheck = false; for (dns_dnsseckey_t *key = ISC_LIST_HEAD(zone->checkds_ok); key != NULL; key = ISC_LIST_NEXT(key, link)) { dst_key_state_t ds_state = DST_KEY_STATE_NA; bool ksk = false; isc_stdtime_t published = 0, withdrawn = 0; /* Is this key have the KSK role? */ (void)dst_key_role(key->key, &ksk, NULL); if (!ksk) { continue; } /* Do we need to check the DS RRset? */ (void)dst_key_getstate(key->key, DST_KEY_DS, &ds_state); (void)dst_key_gettime(key->key, DST_TIME_DSPUBLISH, &published); (void)dst_key_gettime(key->key, DST_TIME_DSDELETE, &withdrawn); if (ds_state == DST_KEY_STATE_RUMOURED && published == 0) { dst_key_setnum(key->key, DST_NUM_DSPUBCOUNT, 0); cdscheck = true; } else if (ds_state == DST_KEY_STATE_UNRETENTIVE && withdrawn == 0) { dst_key_setnum(key->key, DST_NUM_DSDELCOUNT, 0); cdscheck = true; } } if (cdscheck) { /* Request the DS RRset. */ LOCK_ZONE(zone); checkds_send(zone); UNLOCK_ZONE(zone); } } static void zone_rekey(dns_zone_t *zone) { isc_result_t result; dns_db_t *db = NULL; dns_dbnode_t *node = NULL; dns_dbversion_t *ver = NULL; dns_rdataset_t cdsset, soaset, soasigs, keyset, keysigs, cdnskeyset; dns_dnsseckeylist_t dnskeys, keys, rmkeys; dns_dnsseckey_t *key = NULL; dns_diff_t diff, _sig_diff; dns_kasp_t *kasp; dns__zonediff_t zonediff; bool commit = false, newactive = false; bool newalg = false; bool fullsign; dns_ttl_t ttl = 3600; const char *dir = NULL; isc_mem_t *mctx = NULL; isc_stdtime_t now, nexttime = 0; isc_time_t timenow; isc_interval_t ival; char timebuf[80]; REQUIRE(DNS_ZONE_VALID(zone)); ISC_LIST_INIT(dnskeys); ISC_LIST_INIT(keys); ISC_LIST_INIT(rmkeys); dns_rdataset_init(&soaset); dns_rdataset_init(&soasigs); dns_rdataset_init(&keyset); dns_rdataset_init(&keysigs); dns_rdataset_init(&cdsset); dns_rdataset_init(&cdnskeyset); dir = dns_zone_getkeydirectory(zone); mctx = zone->mctx; dns_diff_init(mctx, &diff); dns_diff_init(mctx, &_sig_diff); zonediff_init(&zonediff, &_sig_diff); CHECK(dns_zone_getdb(zone, &db)); CHECK(dns_db_newversion(db, &ver)); CHECK(dns_db_getoriginnode(db, &node)); TIME_NOW(&timenow); now = isc_time_seconds(&timenow); kasp = dns_zone_getkasp(zone); dnssec_log(zone, ISC_LOG_INFO, "reconfiguring zone keys"); /* Get the SOA record's TTL */ CHECK(dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, dns_rdatatype_none, 0, &soaset, &soasigs)); ttl = soaset.ttl; dns_rdataset_disassociate(&soaset); /* Get the DNSKEY rdataset */ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, dns_rdatatype_none, 0, &keyset, &keysigs); if (result == ISC_R_SUCCESS) { ttl = keyset.ttl; dns_zone_lock_keyfiles(zone); result = dns_dnssec_keylistfromrdataset( &zone->origin, dir, mctx, &keyset, &keysigs, &soasigs, false, false, &dnskeys); dns_zone_unlock_keyfiles(zone); if (result != ISC_R_SUCCESS) { goto failure; } } else if (result != ISC_R_NOTFOUND) { goto failure; } /* Get the CDS rdataset */ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_cds, dns_rdatatype_none, 0, &cdsset, NULL); if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdsset)) { dns_rdataset_disassociate(&cdsset); } /* Get the CDNSKEY rdataset */ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_cdnskey, dns_rdatatype_none, 0, &cdnskeyset, NULL); if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdnskeyset)) { dns_rdataset_disassociate(&cdnskeyset); } /* * True when called from "rndc sign". Indicates the zone should be * fully signed now. */ fullsign = DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_FULLSIGN); KASP_LOCK(kasp); dns_zone_lock_keyfiles(zone); result = dns_dnssec_findmatchingkeys(&zone->origin, dir, now, mctx, &keys); dns_zone_unlock_keyfiles(zone); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_DEBUG(1), "zone_rekey:dns_dnssec_findmatchingkeys failed: %s", isc_result_totext(result)); } if (kasp != NULL) { /* * Check DS at parental agents. Clear ongoing checks. */ LOCK_ZONE(zone); checkds_cancel(zone); clear_keylist(&zone->checkds_ok, zone->mctx); ISC_LIST_INIT(zone->checkds_ok); UNLOCK_ZONE(zone); result = dns_zone_getdnsseckeys(zone, db, ver, now, &zone->checkds_ok); if (result == ISC_R_SUCCESS) { zone_checkds(zone); } else { dnssec_log(zone, (result == ISC_R_NOTFOUND) ? ISC_LOG_DEBUG(1) : ISC_LOG_ERROR, "zone_rekey:dns_zone_getdnsseckeys failed: " "%s", isc_result_totext(result)); } if (result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND) { dns_zone_lock_keyfiles(zone); result = dns_keymgr_run(&zone->origin, zone->rdclass, dir, mctx, &keys, &dnskeys, kasp, now, &nexttime); dns_zone_unlock_keyfiles(zone); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:dns_dnssec_keymgr " "failed: %s", isc_result_totext(result)); KASP_UNLOCK(kasp); goto failure; } } } KASP_UNLOCK(kasp); if (result == ISC_R_SUCCESS) { bool cdsdel = false; bool cdnskeydel = false; bool sane_diff, sane_dnskey; isc_stdtime_t when; /* * Publish CDS/CDNSKEY DELETE records if the zone is * transitioning from secure to insecure. */ if (kasp != NULL) { if (strcmp(dns_kasp_getname(kasp), "insecure") == 0) { cdsdel = true; cdnskeydel = true; } } else { /* Check if there is a CDS DELETE record. */ if (dns_rdataset_isassociated(&cdsset)) { for (result = dns_rdataset_first(&cdsset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&cdsset)) { dns_rdata_t crdata = DNS_RDATA_INIT; dns_rdataset_current(&cdsset, &crdata); /* * CDS deletion record has this form * "0 0 0 00" which is 5 zero octets. */ if (crdata.length == 5U && memcmp(crdata.data, (unsigned char[5]){ 0, 0, 0, 0, 0 }, 5) == 0) { cdsdel = true; break; } } } /* Check if there is a CDNSKEY DELETE record. */ if (dns_rdataset_isassociated(&cdnskeyset)) { for (result = dns_rdataset_first(&cdnskeyset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&cdnskeyset)) { dns_rdata_t crdata = DNS_RDATA_INIT; dns_rdataset_current(&cdnskeyset, &crdata); /* * CDNSKEY deletion record has this form * "0 3 0 AA==" which is 2 zero octets, * a 3, and 2 zero octets. */ if (crdata.length == 5U && memcmp(crdata.data, (unsigned char[5]){ 0, 0, 3, 0, 0 }, 5) == 0) { cdnskeydel = true; break; } } } } /* * Only update DNSKEY TTL if we have a policy. */ if (kasp != NULL) { ttl = dns_kasp_dnskeyttl(kasp); } result = dns_dnssec_updatekeys(&dnskeys, &keys, &rmkeys, &zone->origin, ttl, &diff, mctx, dnssec_report); /* * Keys couldn't be updated for some reason; * try again later. */ if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update zone keys: %s", isc_result_totext(result)); goto failure; } /* * Update CDS / CDNSKEY records. */ result = dns_dnssec_syncupdate(&dnskeys, &rmkeys, &cdsset, &cdnskeyset, now, ttl, &diff, mctx); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update CDS/CDNSKEY: %s", isc_result_totext(result)); goto failure; } if (cdsdel || cdnskeydel) { /* * Only publish CDS/CDNSKEY DELETE records if there is * a KSK that can be used to verify the RRset. This * means there must be a key with the KSK role that is * published and is used for signing. */ bool allow = false; for (key = ISC_LIST_HEAD(dnskeys); key != NULL; key = ISC_LIST_NEXT(key, link)) { dst_key_t *dstk = key->key; if (dst_key_is_published(dstk, now, &when) && dst_key_is_signing(dstk, DST_BOOL_KSK, now, &when)) { allow = true; break; } } if (cdsdel) { cdsdel = allow; } if (cdnskeydel) { cdnskeydel = allow; } } result = dns_dnssec_syncdelete( &cdsset, &cdnskeyset, &zone->origin, zone->rdclass, ttl, &diff, mctx, cdsdel, cdnskeydel); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_rekey:couldn't update CDS/CDNSKEY " "DELETE records: %s", isc_result_totext(result)); goto failure; } /* * See if any pre-existing keys have newly become active; * also, see if any new key is for a new algorithm, as in that * event, we need to sign the zone fully. (If there's a new * key, but it's for an already-existing algorithm, then * the zone signing can be handled incrementally.) */ for (key = ISC_LIST_HEAD(dnskeys); key != NULL; key = ISC_LIST_NEXT(key, link)) { if (!key->first_sign) { continue; } newactive = true; if (!dns_rdataset_isassociated(&keysigs)) { newalg = true; break; } if (signed_with_alg(&keysigs, dst_key_alg(key->key))) { /* * This isn't a new algorithm; clear * first_sign so we won't sign the * whole zone with this key later. */ key->first_sign = false; } else { newalg = true; break; } } /* * A sane diff is one that is not empty, and that does not * introduce a zone with NSEC only DNSKEYs along with NSEC3 * chains. */ sane_dnskey = dns_zone_check_dnskey_nsec3(zone, db, ver, &diff, NULL, 0); sane_diff = !ISC_LIST_EMPTY(diff.tuples) && sane_dnskey; if (!sane_dnskey) { dnssec_log(zone, ISC_LOG_ERROR, "NSEC only DNSKEYs and NSEC3 chains not " "allowed"); } if (newactive || fullsign || sane_diff) { CHECK(dns_diff_apply(&diff, db, ver)); CHECK(clean_nsec3param(zone, db, ver, &diff)); CHECK(add_signing_records(db, zone->privatetype, ver, &diff, (newalg || fullsign))); CHECK(update_soa_serial(zone, db, ver, &diff, mctx, zone->updatemethod)); CHECK(add_chains(zone, db, ver, &diff)); CHECK(sign_apex(zone, db, ver, now, &diff, &zonediff)); CHECK(zone_journal(zone, zonediff.diff, NULL, "zone_rekey")); commit = true; } } dns_db_closeversion(db, &ver, true); LOCK_ZONE(zone); if (commit) { dns_difftuple_t *tuple; dns_stats_t *dnssecsignstats = dns_zone_getdnssecsignstats(zone); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY); zone_needdump(zone, DNS_DUMP_DELAY); zone_settimer(zone, &timenow); /* Remove any signatures from removed keys. */ if (!ISC_LIST_EMPTY(rmkeys)) { for (key = ISC_LIST_HEAD(rmkeys); key != NULL; key = ISC_LIST_NEXT(key, link)) { result = zone_signwithkey( zone, dst_key_alg(key->key), dst_key_id(key->key), true); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_signwithkey failed: " "%s", isc_result_totext(result)); } /* Clear DNSSEC sign statistics. */ if (dnssecsignstats != NULL) { dns_dnssecsignstats_clear( dnssecsignstats, dst_key_id(key->key), dst_key_alg(key->key)); /* * Also clear the dnssec-sign * statistics of the revoked key id. */ dns_dnssecsignstats_clear( dnssecsignstats, dst_key_rid(key->key), dst_key_alg(key->key)); } } } if (fullsign) { /* * "rndc sign" was called, so we now sign the zone * with all active keys, whether they're new or not. */ for (key = ISC_LIST_HEAD(dnskeys); key != NULL; key = ISC_LIST_NEXT(key, link)) { if (!key->force_sign && !key->hint_sign) { continue; } result = zone_signwithkey( zone, dst_key_alg(key->key), dst_key_id(key->key), false); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_signwithkey failed: " "%s", isc_result_totext(result)); } } } else if (newalg) { /* * We haven't been told to sign fully, but a new * algorithm was added to the DNSKEY. We sign * the full zone, but only with newly active * keys. */ for (key = ISC_LIST_HEAD(dnskeys); key != NULL; key = ISC_LIST_NEXT(key, link)) { if (!key->first_sign) { continue; } result = zone_signwithkey( zone, dst_key_alg(key->key), dst_key_id(key->key), false); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_signwithkey failed: " "%s", isc_result_totext(result)); } } } /* * Clear fullsign flag, if it was set, so we don't do * another full signing next time. */ DNS_ZONEKEY_CLROPTION(zone, DNS_ZONEKEY_FULLSIGN); /* * Cause the zone to add/delete NSEC3 chains for the * deferred NSEC3PARAM changes. */ for (tuple = ISC_LIST_HEAD(zonediff.diff->tuples); tuple != NULL; tuple = ISC_LIST_NEXT(tuple, link)) { unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_nsec3param_t nsec3param; if (tuple->rdata.type != zone->privatetype || tuple->op != DNS_DIFFOP_ADD) { continue; } if (!dns_nsec3param_fromprivate(&tuple->rdata, &rdata, buf, sizeof(buf))) { continue; } result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (nsec3param.flags == 0) { continue; } result = zone_addnsec3chain(zone, &nsec3param); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone_addnsec3chain failed: %s", isc_result_totext(result)); } } /* * Activate any NSEC3 chain updates that may have * been scheduled before this rekey. */ if (fullsign || newalg) { resume_addnsec3chain(zone); } /* * Schedule the next resigning event */ set_resigntime(zone); } isc_time_settoepoch(&zone->refreshkeytime); /* * If keymgr provided a next time, use the calculated next rekey time. */ if (kasp != NULL) { isc_time_t timenext; uint32_t nexttime_seconds; /* * Set the key refresh timer to the next scheduled key event * or to 'dnssec-loadkeys-interval' seconds in the future * if no next key event is scheduled (nexttime == 0). */ if (nexttime > 0) { nexttime_seconds = nexttime - now; } else { nexttime_seconds = zone->refreshkeyinterval; } DNS_ZONE_TIME_ADD(&timenow, nexttime_seconds, &timenext); zone->refreshkeytime = timenext; zone_settimer(zone, &timenow); isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); dnssec_log(zone, ISC_LOG_DEBUG(3), "next key event in %u seconds", nexttime_seconds); dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf); } /* * If we're doing key maintenance, set the key refresh timer to * the next scheduled key event or to 'dnssec-loadkeys-interval' * seconds in the future, whichever is sooner. */ else if (DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) { isc_time_t timethen; isc_stdtime_t then; DNS_ZONE_TIME_ADD(&timenow, zone->refreshkeyinterval, &timethen); zone->refreshkeytime = timethen; for (key = ISC_LIST_HEAD(dnskeys); key != NULL; key = ISC_LIST_NEXT(key, link)) { then = now; result = next_keyevent(key->key, &then); if (result != ISC_R_SUCCESS) { continue; } DNS_ZONE_TIME_ADD(&timenow, then - now, &timethen); if (isc_time_compare(&timethen, &zone->refreshkeytime) < 0) { zone->refreshkeytime = timethen; } } zone_settimer(zone, &timenow); isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80); dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf); } UNLOCK_ZONE(zone); if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { for (key = ISC_LIST_HEAD(dnskeys); key != NULL; key = ISC_LIST_NEXT(key, link)) { /* This debug log is used in the kasp system test */ char algbuf[DNS_SECALG_FORMATSIZE]; dns_secalg_format(dst_key_alg(key->key), algbuf, sizeof(algbuf)); dnssec_log(zone, ISC_LOG_DEBUG(3), "zone_rekey done: key %d/%s", dst_key_id(key->key), algbuf); } } result = ISC_R_SUCCESS; failure: LOCK_ZONE(zone); if (result != ISC_R_SUCCESS) { /* * Something went wrong; try again in ten minutes or * after a key refresh interval, whichever is shorter. */ dnssec_log(zone, ISC_LOG_DEBUG(3), "zone_rekey failure: %s (retry in %u seconds)", isc_result_totext(result), ISC_MIN(zone->refreshkeyinterval, 600)); isc_interval_set(&ival, ISC_MIN(zone->refreshkeyinterval, 600), 0); isc_time_nowplusinterval(&zone->refreshkeytime, &ival); } UNLOCK_ZONE(zone); dns_diff_clear(&diff); dns_diff_clear(&_sig_diff); clear_keylist(&dnskeys, mctx); clear_keylist(&keys, mctx); clear_keylist(&rmkeys, mctx); if (ver != NULL) { dns_db_closeversion(db, &ver, false); } if (dns_rdataset_isassociated(&cdsset)) { dns_rdataset_disassociate(&cdsset); } if (dns_rdataset_isassociated(&keyset)) { dns_rdataset_disassociate(&keyset); } if (dns_rdataset_isassociated(&keysigs)) { dns_rdataset_disassociate(&keysigs); } if (dns_rdataset_isassociated(&soasigs)) { dns_rdataset_disassociate(&soasigs); } if (dns_rdataset_isassociated(&cdnskeyset)) { dns_rdataset_disassociate(&cdnskeyset); } if (node != NULL) { dns_db_detachnode(db, &node); } if (db != NULL) { dns_db_detach(&db); } INSIST(ver == NULL); } void dns_zone_rekey(dns_zone_t *zone, bool fullsign) { isc_time_t now; if (zone->type == dns_zone_primary && zone->task != NULL) { LOCK_ZONE(zone); if (fullsign) { DNS_ZONEKEY_SETOPTION(zone, DNS_ZONEKEY_FULLSIGN); } TIME_NOW(&now); zone->refreshkeytime = now; zone_settimer(zone, &now); UNLOCK_ZONE(zone); } } isc_result_t dns_zone_nscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, unsigned int *errors) { isc_result_t result; dns_dbnode_t *node = NULL; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(errors != NULL); result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) { return (result); } result = zone_count_ns_rr(zone, db, node, version, NULL, errors, false); dns_db_detachnode(db, &node); return (result); } isc_result_t dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { isc_result_t result; dns_dbnode_t *node = NULL; dns_rdataset_t dnskey, cds, cdnskey; unsigned char algorithms[256]; unsigned int i; bool empty = false; enum { notexpected = 0, expected = 1, found = 2 }; REQUIRE(DNS_ZONE_VALID(zone)); result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) { return (result); } dns_rdataset_init(&cds); dns_rdataset_init(&dnskey); dns_rdataset_init(&cdnskey); result = dns_db_findrdataset(db, node, version, dns_rdatatype_cds, dns_rdatatype_none, 0, &cds, NULL); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { goto failure; } result = dns_db_findrdataset(db, node, version, dns_rdatatype_cdnskey, dns_rdatatype_none, 0, &cdnskey, NULL); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { goto failure; } if (!dns_rdataset_isassociated(&cds) && !dns_rdataset_isassociated(&cdnskey)) { result = ISC_R_SUCCESS; goto failure; } result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, dns_rdatatype_none, 0, &dnskey, NULL); if (result == ISC_R_NOTFOUND) { empty = true; } else if (result != ISC_R_SUCCESS) { goto failure; } /* * For each DNSSEC algorithm in the CDS RRset there must be * a matching DNSKEY record with the exception of a CDS deletion * record which must be by itself. */ if (dns_rdataset_isassociated(&cds)) { bool delete = false; memset(algorithms, notexpected, sizeof(algorithms)); for (result = dns_rdataset_first(&cds); result == ISC_R_SUCCESS; result = dns_rdataset_next(&cds)) { dns_rdata_t crdata = DNS_RDATA_INIT; dns_rdata_cds_t structcds; dns_rdataset_current(&cds, &crdata); /* * CDS deletion record has this form "0 0 0 00" which * is 5 zero octets. */ if (crdata.length == 5U && memcmp(crdata.data, (unsigned char[5]){ 0, 0, 0, 0, 0 }, 5) == 0) { delete = true; continue; } if (empty) { result = DNS_R_BADCDS; goto failure; } CHECK(dns_rdata_tostruct(&crdata, &structcds, NULL)); if (algorithms[structcds.algorithm] == 0) { algorithms[structcds.algorithm] = expected; } for (result = dns_rdataset_first(&dnskey); result == ISC_R_SUCCESS; result = dns_rdataset_next(&dnskey)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_dnskey_t structdnskey; dns_rdataset_current(&dnskey, &rdata); CHECK(dns_rdata_tostruct(&rdata, &structdnskey, NULL)); if (structdnskey.algorithm == structcds.algorithm) { algorithms[structcds.algorithm] = found; } } if (result != ISC_R_NOMORE) { goto failure; } } for (i = 0; i < sizeof(algorithms); i++) { if (delete) { if (algorithms[i] != notexpected) { result = DNS_R_BADCDS; goto failure; } } else if (algorithms[i] == expected) { result = DNS_R_BADCDS; goto failure; } } } /* * For each DNSSEC algorithm in the CDNSKEY RRset there must be * a matching DNSKEY record with the exception of a CDNSKEY deletion * record which must be by itself. */ if (dns_rdataset_isassociated(&cdnskey)) { bool delete = false; memset(algorithms, notexpected, sizeof(algorithms)); for (result = dns_rdataset_first(&cdnskey); result == ISC_R_SUCCESS; result = dns_rdataset_next(&cdnskey)) { dns_rdata_t crdata = DNS_RDATA_INIT; dns_rdata_cdnskey_t structcdnskey; dns_rdataset_current(&cdnskey, &crdata); /* * CDNSKEY deletion record has this form * "0 3 0 AA==" which is 2 zero octets, a 3, * and 2 zero octets. */ if (crdata.length == 5U && memcmp(crdata.data, (unsigned char[5]){ 0, 0, 3, 0, 0 }, 5) == 0) { delete = true; continue; } if (empty) { result = DNS_R_BADCDNSKEY; goto failure; } CHECK(dns_rdata_tostruct(&crdata, &structcdnskey, NULL)); if (algorithms[structcdnskey.algorithm] == 0) { algorithms[structcdnskey.algorithm] = expected; } for (result = dns_rdataset_first(&dnskey); result == ISC_R_SUCCESS; result = dns_rdataset_next(&dnskey)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_dnskey_t structdnskey; dns_rdataset_current(&dnskey, &rdata); CHECK(dns_rdata_tostruct(&rdata, &structdnskey, NULL)); if (structdnskey.algorithm == structcdnskey.algorithm) { algorithms[structcdnskey.algorithm] = found; } } if (result != ISC_R_NOMORE) { goto failure; } } for (i = 0; i < sizeof(algorithms); i++) { if (delete) { if (algorithms[i] != notexpected) { result = DNS_R_BADCDNSKEY; goto failure; } } else if (algorithms[i] == expected) { result = DNS_R_BADCDNSKEY; goto failure; } } } result = ISC_R_SUCCESS; failure: if (dns_rdataset_isassociated(&cds)) { dns_rdataset_disassociate(&cds); } if (dns_rdataset_isassociated(&dnskey)) { dns_rdataset_disassociate(&dnskey); } if (dns_rdataset_isassociated(&cdnskey)) { dns_rdataset_disassociate(&cdnskey); } dns_db_detachnode(db, &node); return (result); } void dns_zone_setautomatic(dns_zone_t *zone, bool automatic) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->automatic = automatic; UNLOCK_ZONE(zone); } bool dns_zone_getautomatic(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->automatic); } void dns_zone_setadded(dns_zone_t *zone, bool added) { REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); zone->added = added; UNLOCK_ZONE(zone); } bool dns_zone_getadded(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->added); } isc_result_t dns_zone_dlzpostload(dns_zone_t *zone, dns_db_t *db) { isc_time_t loadtime; isc_result_t result; dns_zone_t *secure = NULL; TIME_NOW(&loadtime); /* * Lock hierarchy: zmgr, zone, raw. */ again: LOCK_ZONE(zone); INSIST(zone != zone->raw); if (inline_secure(zone)) { LOCK_ZONE(zone->raw); } else if (inline_raw(zone)) { secure = zone->secure; TRYLOCK_ZONE(result, secure); if (result != ISC_R_SUCCESS) { UNLOCK_ZONE(zone); secure = NULL; isc_thread_yield(); goto again; } } result = zone_postload(zone, db, loadtime, ISC_R_SUCCESS); if (inline_secure(zone)) { UNLOCK_ZONE(zone->raw); } else if (secure != NULL) { UNLOCK_ZONE(secure); } UNLOCK_ZONE(zone); return (result); } isc_result_t dns_zone_setrefreshkeyinterval(dns_zone_t *zone, uint32_t interval) { REQUIRE(DNS_ZONE_VALID(zone)); if (interval == 0) { return (ISC_R_RANGE); } /* Maximum value: 24 hours (3600 minutes) */ if (interval > (24 * 60)) { interval = (24 * 60); } /* Multiply by 60 for seconds */ zone->refreshkeyinterval = interval * 60; return (ISC_R_SUCCESS); } void dns_zone_setrequestixfr(dns_zone_t *zone, bool flag) { REQUIRE(DNS_ZONE_VALID(zone)); zone->requestixfr = flag; } bool dns_zone_getrequestixfr(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->requestixfr); } void dns_zone_setixfrratio(dns_zone_t *zone, uint32_t ratio) { REQUIRE(DNS_ZONE_VALID(zone)); zone->ixfr_ratio = ratio; } uint32_t dns_zone_getixfrratio(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->ixfr_ratio); } void dns_zone_setrequestexpire(dns_zone_t *zone, bool flag) { REQUIRE(DNS_ZONE_VALID(zone)); zone->requestexpire = flag; } bool dns_zone_getrequestexpire(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->requestexpire); } void dns_zone_setserialupdatemethod(dns_zone_t *zone, dns_updatemethod_t method) { REQUIRE(DNS_ZONE_VALID(zone)); zone->updatemethod = method; } dns_updatemethod_t dns_zone_getserialupdatemethod(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->updatemethod); } /* * Lock hierarchy: zmgr, zone, raw. */ isc_result_t dns_zone_link(dns_zone_t *zone, dns_zone_t *raw) { isc_result_t result; dns_zonemgr_t *zmgr; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(zone->zmgr != NULL); REQUIRE(zone->task != NULL); REQUIRE(zone->loadtask != NULL); REQUIRE(zone->raw == NULL); REQUIRE(DNS_ZONE_VALID(raw)); REQUIRE(raw->zmgr == NULL); REQUIRE(raw->task == NULL); REQUIRE(raw->loadtask == NULL); REQUIRE(raw->secure == NULL); REQUIRE(zone != raw); /* * Lock hierarchy: zmgr, zone, raw. */ zmgr = zone->zmgr; RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); LOCK_ZONE(zone); LOCK_ZONE(raw); result = isc_timer_create(zmgr->timermgr, isc_timertype_inactive, NULL, NULL, zone->task, zone_timer, raw, &raw->timer); if (result != ISC_R_SUCCESS) { goto unlock; } /* * The timer "holds" a iref. */ isc_refcount_increment0(&raw->irefs); /* dns_zone_attach(raw, &zone->raw); */ isc_refcount_increment(&raw->erefs); zone->raw = raw; /* dns_zone_iattach(zone, &raw->secure); */ zone_iattach(zone, &raw->secure); isc_task_attach(zone->task, &raw->task); isc_task_attach(zone->loadtask, &raw->loadtask); ISC_LIST_APPEND(zmgr->zones, raw, link); raw->zmgr = zmgr; isc_refcount_increment(&zmgr->refs); unlock: UNLOCK_ZONE(raw); UNLOCK_ZONE(zone); RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); return (result); } void dns_zone_getraw(dns_zone_t *zone, dns_zone_t **raw) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(raw != NULL && *raw == NULL); LOCK(&zone->lock); INSIST(zone != zone->raw); if (zone->raw != NULL) { dns_zone_attach(zone->raw, raw); } UNLOCK(&zone->lock); } struct keydone { isc_event_t event; bool all; unsigned char data[5]; }; #define PENDINGFLAGS (DNS_NSEC3FLAG_CREATE | DNS_NSEC3FLAG_INITIAL) static void keydone(isc_task_t *task, isc_event_t *event) { const char *me = "keydone"; bool commit = false; isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; dns_dbversion_t *oldver = NULL, *newver = NULL; dns_zone_t *zone; dns_db_t *db = NULL; dns_dbnode_t *node = NULL; dns_rdataset_t rdataset; dns_diff_t diff; struct keydone *kd = (struct keydone *)event; dns_update_log_t log = { update_log_cb, NULL }; bool clear_pending = false; UNUSED(task); zone = event->ev_arg; INSIST(DNS_ZONE_VALID(zone)); ENTER; dns_rdataset_init(&rdataset); dns_diff_init(zone->mctx, &diff); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { goto failure; } dns_db_currentversion(db, &oldver); result = dns_db_newversion(db, &newver); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "keydone:dns_db_newversion -> %s", isc_result_totext(result)); goto failure; } result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) { goto failure; } result = dns_db_findrdataset(db, node, newver, zone->privatetype, dns_rdatatype_none, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto failure; } if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); goto failure; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { bool found = false; dns_rdataset_current(&rdataset, &rdata); if (kd->all) { if (rdata.length == 5 && rdata.data[0] != 0 && rdata.data[3] == 0 && rdata.data[4] == 1) { found = true; } else if (rdata.data[0] == 0 && (rdata.data[2] & PENDINGFLAGS) != 0) { found = true; clear_pending = true; } } else if (rdata.length == 5 && memcmp(rdata.data, kd->data, 5) == 0) { found = true; } if (found) { CHECK(update_one_rr(db, newver, &diff, DNS_DIFFOP_DEL, &zone->origin, rdataset.ttl, &rdata)); } dns_rdata_reset(&rdata); } if (!ISC_LIST_EMPTY(diff.tuples)) { /* Write changes to journal file. */ CHECK(update_soa_serial(zone, db, newver, &diff, zone->mctx, zone->updatemethod)); result = dns_update_signatures(&log, zone, db, oldver, newver, &diff, zone->sigvalidityinterval); if (!clear_pending) { CHECK(result); } CHECK(zone_journal(zone, &diff, NULL, "keydone")); commit = true; LOCK_ZONE(zone); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY); zone_needdump(zone, 30); UNLOCK_ZONE(zone); } failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (db != NULL) { if (node != NULL) { dns_db_detachnode(db, &node); } if (oldver != NULL) { dns_db_closeversion(db, &oldver, false); } if (newver != NULL) { dns_db_closeversion(db, &newver, commit); } dns_db_detach(&db); } dns_diff_clear(&diff); isc_event_free(&event); dns_zone_idetach(&zone); INSIST(oldver == NULL); INSIST(newver == NULL); } isc_result_t dns_zone_keydone(dns_zone_t *zone, const char *keystr) { isc_result_t result = ISC_R_SUCCESS; isc_event_t *e; isc_buffer_t b; dns_zone_t *dummy = NULL; struct keydone *kd; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_KEYDONE, keydone, zone, sizeof(struct keydone)); kd = (struct keydone *)e; if (strcasecmp(keystr, "all") == 0) { kd->all = true; } else { isc_textregion_t r; const char *algstr; dns_keytag_t keyid; dns_secalg_t alg; size_t n; kd->all = false; n = sscanf(keystr, "%hu/", &keyid); if (n == 0U) { CHECK(ISC_R_FAILURE); } algstr = strchr(keystr, '/'); if (algstr != NULL) { algstr++; } else { CHECK(ISC_R_FAILURE); } n = sscanf(algstr, "%hhu", &alg); if (n == 0U) { DE_CONST(algstr, r.base); r.length = strlen(algstr); CHECK(dns_secalg_fromtext(&alg, &r)); } /* construct a private-type rdata */ isc_buffer_init(&b, kd->data, sizeof(kd->data)); isc_buffer_putuint8(&b, alg); isc_buffer_putuint8(&b, (keyid & 0xff00) >> 8); isc_buffer_putuint8(&b, (keyid & 0xff)); isc_buffer_putuint8(&b, 0); isc_buffer_putuint8(&b, 1); } zone_iattach(zone, &dummy); isc_task_send(zone->task, &e); failure: if (e != NULL) { isc_event_free(&e); } UNLOCK_ZONE(zone); return (result); } /* * Called from the zone task's queue after the relevant event is posted by * dns_zone_setnsec3param(). */ static void setnsec3param(isc_task_t *task, isc_event_t *event) { const char *me = "setnsec3param"; dns_zone_t *zone = event->ev_arg; bool loadpending; INSIST(DNS_ZONE_VALID(zone)); UNUSED(task); ENTER; LOCK_ZONE(zone); loadpending = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING); UNLOCK_ZONE(zone); /* * If receive_secure_serial is still processing or we have a * queued event append rss_post queue. */ if (zone->rss_newver != NULL || ISC_LIST_HEAD(zone->rss_post) != NULL) { /* * Wait for receive_secure_serial() to finish processing. */ ISC_LIST_APPEND(zone->rss_post, event, ev_link); } else { bool rescheduled = false; ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); /* * The zone is not yet fully loaded. Reschedule the event to * be picked up later. This turns this function into a busy * wait, but it only happens at startup. */ if (zone->db == NULL && loadpending) { rescheduled = true; isc_task_send(task, &event); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (rescheduled) { return; } rss_post(zone, event); } dns_zone_idetach(&zone); } static void salt2text(unsigned char *salt, uint8_t saltlen, unsigned char *text, unsigned int textlen) { isc_region_t r; isc_buffer_t buf; isc_result_t result; r.base = salt; r.length = (unsigned int)saltlen; isc_buffer_init(&buf, text, textlen); result = isc_hex_totext(&r, 2, "", &buf); if (result == ISC_R_SUCCESS) { text[saltlen * 2] = 0; } else { text[0] = 0; } } /* * Check whether NSEC3 chain addition or removal specified by the private-type * record passed with the event was already queued (or even fully performed). * If not, modify the relevant private-type records at the zone apex and call * resume_addnsec3chain(). */ static void rss_post(dns_zone_t *zone, isc_event_t *event) { const char *me = "rss_post"; bool commit = false; isc_result_t result; dns_dbversion_t *oldver = NULL, *newver = NULL; dns_db_t *db = NULL; dns_dbnode_t *node = NULL; dns_rdataset_t prdataset, nrdataset; dns_diff_t diff; struct np3event *npe = (struct np3event *)event; nsec3param_t *np; dns_update_log_t log = { update_log_cb, NULL }; dns_rdata_t rdata; bool nseconly; bool exists = false; ENTER; np = &npe->params; dns_rdataset_init(&prdataset); dns_rdataset_init(&nrdataset); dns_diff_init(zone->mctx, &diff); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { goto failure; } dns_db_currentversion(db, &oldver); result = dns_db_newversion(db, &newver); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "setnsec3param:dns_db_newversion -> %s", isc_result_totext(result)); goto failure; } CHECK(dns_db_getoriginnode(db, &node)); /* * Do we need to look up the NSEC3 parameters? */ if (np->lookup) { dns_rdata_nsec3param_t param; dns_rdata_t nrdata = DNS_RDATA_INIT; dns_rdata_t prdata = DNS_RDATA_INIT; unsigned char nbuf[DNS_NSEC3PARAM_BUFFERSIZE]; unsigned char saltbuf[255]; isc_buffer_t b; param.salt = NULL; result = dns__zone_lookup_nsec3param(zone, &np->rdata, ¶m, saltbuf, np->resalt); if (result == ISC_R_SUCCESS) { /* * Success because the NSEC3PARAM already exists, but * function returns void, so goto failure to clean up. */ goto failure; } if (result != DNS_R_NSEC3RESALT && result != ISC_R_NOTFOUND) { dnssec_log(zone, ISC_LOG_DEBUG(3), "setnsec3param:lookup nsec3param -> %s", isc_result_totext(result)); goto failure; } INSIST(param.salt != NULL); /* Update NSEC3 parameters. */ np->rdata.hash = param.hash; np->rdata.flags = param.flags; np->rdata.iterations = param.iterations; np->rdata.salt_length = param.salt_length; np->rdata.salt = param.salt; isc_buffer_init(&b, nbuf, sizeof(nbuf)); CHECK(dns_rdata_fromstruct(&nrdata, zone->rdclass, dns_rdatatype_nsec3param, &np->rdata, &b)); dns_nsec3param_toprivate(&nrdata, &prdata, zone->privatetype, np->data, sizeof(np->data)); np->length = prdata.length; np->nsec = false; } /* * Does a private-type record already exist for this chain? */ result = dns_db_findrdataset(db, node, newver, zone->privatetype, dns_rdatatype_none, 0, &prdataset, NULL); if (result == ISC_R_SUCCESS) { for (result = dns_rdataset_first(&prdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&prdataset)) { dns_rdata_init(&rdata); dns_rdataset_current(&prdataset, &rdata); if (np->length == rdata.length && memcmp(rdata.data, np->data, np->length) == 0) { exists = true; break; } } } else if (result != ISC_R_NOTFOUND) { INSIST(!dns_rdataset_isassociated(&prdataset)); goto failure; } /* * Does the chain already exist? */ result = dns_db_findrdataset(db, node, newver, dns_rdatatype_nsec3param, dns_rdatatype_none, 0, &nrdataset, NULL); if (result == ISC_R_SUCCESS) { for (result = dns_rdataset_first(&nrdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&nrdataset)) { dns_rdata_init(&rdata); dns_rdataset_current(&nrdataset, &rdata); if (np->length == (rdata.length + 1) && memcmp(rdata.data, np->data + 1, np->length - 1) == 0) { exists = true; break; } } } else if (result != ISC_R_NOTFOUND) { INSIST(!dns_rdataset_isassociated(&nrdataset)); goto failure; } /* * We need to remove any existing NSEC3 chains if the supplied NSEC3 * parameters are supposed to replace the current ones or if we are * switching to NSEC. */ if (!exists && np->replace && (np->length != 0 || np->nsec)) { CHECK(dns_nsec3param_deletechains(db, newver, zone, !np->nsec, &diff)); } if (!exists && np->length != 0) { /* * We're creating an NSEC3 chain. Add the private-type record * passed in the event handler's argument to the zone apex. * * If the zone is not currently capable of supporting an NSEC3 * chain (due to the DNSKEY RRset at the zone apex not existing * or containing at least one key using an NSEC-only * algorithm), add the INITIAL flag, so these parameters can be * used later when NSEC3 becomes available. */ dns_rdata_init(&rdata); np->data[2] |= DNS_NSEC3FLAG_CREATE; result = dns_nsec_nseconly(db, newver, NULL, &nseconly); if (result == ISC_R_NOTFOUND || nseconly) { np->data[2] |= DNS_NSEC3FLAG_INITIAL; } rdata.length = np->length; rdata.data = np->data; rdata.type = zone->privatetype; rdata.rdclass = zone->rdclass; CHECK(update_one_rr(db, newver, &diff, DNS_DIFFOP_ADD, &zone->origin, 0, &rdata)); } /* * If we changed anything in the zone, write changes to journal file * and set commit to true so that resume_addnsec3chain() will be * called below in order to kick off adding/removing relevant NSEC3 * records. */ if (!ISC_LIST_EMPTY(diff.tuples)) { CHECK(update_soa_serial(zone, db, newver, &diff, zone->mctx, zone->updatemethod)); result = dns_update_signatures(&log, zone, db, oldver, newver, &diff, zone->sigvalidityinterval); if (result != ISC_R_NOTFOUND) { CHECK(result); } CHECK(zone_journal(zone, &diff, NULL, "setnsec3param")); commit = true; LOCK_ZONE(zone); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); zone_needdump(zone, 30); UNLOCK_ZONE(zone); } failure: if (dns_rdataset_isassociated(&prdataset)) { dns_rdataset_disassociate(&prdataset); } if (dns_rdataset_isassociated(&nrdataset)) { dns_rdataset_disassociate(&nrdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } if (oldver != NULL) { dns_db_closeversion(db, &oldver, false); } if (newver != NULL) { dns_db_closeversion(db, &newver, commit); } if (db != NULL) { dns_db_detach(&db); } if (commit) { LOCK_ZONE(zone); resume_addnsec3chain(zone); UNLOCK_ZONE(zone); } dns_diff_clear(&diff); isc_event_free(&event); INSIST(oldver == NULL); INSIST(newver == NULL); } /* * Check if zone has NSEC3PARAM (and thus a chain) with the right parameters. * * If 'salt' is NULL, a match is found if the salt has the requested length, * otherwise the NSEC3 salt must match the requested salt value too. * * Returns ISC_R_SUCCESS, if a match is found, or an error if no match is * found, or if the db lookup failed. */ isc_result_t dns__zone_lookup_nsec3param(dns_zone_t *zone, dns_rdata_nsec3param_t *lookup, dns_rdata_nsec3param_t *param, unsigned char saltbuf[255], bool resalt) { isc_result_t result = ISC_R_UNEXPECTED; dns_dbnode_t *node = NULL; dns_db_t *db = NULL; dns_dbversion_t *version = NULL; dns_rdataset_t rdataset; dns_rdata_nsec3param_t nsec3param; dns_rdata_t rdata = DNS_RDATA_INIT; REQUIRE(DNS_ZONE_VALID(zone)); dns_rdataset_init(&rdataset); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { result = ISC_R_FAILURE; goto setparam; } result = dns_db_findnode(db, &zone->origin, false, &node); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "dns__zone_lookup_nsec3param:" "dns_db_findnode -> %s", isc_result_totext(result)); result = ISC_R_FAILURE; goto setparam; } dns_db_currentversion(db, &version); result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3param, dns_rdatatype_none, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { INSIST(!dns_rdataset_isassociated(&rdataset)); if (result != ISC_R_NOTFOUND) { dns_zone_log(zone, ISC_LOG_ERROR, "dns__zone_lookup_nsec3param:" "dns_db_findrdataset -> %s", isc_result_totext(result)); } goto setparam; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); INSIST(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); /* Check parameters. */ if (nsec3param.hash != lookup->hash) { continue; } if (nsec3param.iterations != lookup->iterations) { continue; } if (nsec3param.salt_length != lookup->salt_length) { continue; } if (lookup->salt != NULL) { if (memcmp(nsec3param.salt, lookup->salt, lookup->salt_length) != 0) { continue; } } /* Found a match. */ result = ISC_R_SUCCESS; param->hash = nsec3param.hash; param->flags = nsec3param.flags; param->iterations = nsec3param.iterations; param->salt_length = nsec3param.salt_length; param->salt = nsec3param.salt; break; } if (result == ISC_R_NOMORE) { result = ISC_R_NOTFOUND; } setparam: if (result != ISC_R_SUCCESS) { /* Found no match. */ param->hash = lookup->hash; param->flags = lookup->flags; param->iterations = lookup->iterations; param->salt_length = lookup->salt_length; param->salt = lookup->salt; } if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) { goto failure; } if (param->salt_length == 0) { DE_CONST("-", param->salt); } else if (resalt || param->salt == NULL) { unsigned char *newsalt; unsigned char salttext[255 * 2 + 1]; do { /* Generate a new salt. */ result = dns_nsec3_generate_salt(saltbuf, param->salt_length); if (result != ISC_R_SUCCESS) { break; } newsalt = saltbuf; salt2text(newsalt, param->salt_length, salttext, sizeof(salttext)); dnssec_log(zone, ISC_LOG_INFO, "generated salt: %s", salttext); /* Check for salt conflict. */ if (param->salt != NULL && memcmp(newsalt, param->salt, param->salt_length) == 0) { result = ISC_R_SUCCESS; } else { param->salt = newsalt; result = DNS_R_NSEC3RESALT; } } while (result == ISC_R_SUCCESS); INSIST(result != ISC_R_SUCCESS); } failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } if (version != NULL) { dns_db_closeversion(db, &version, false); } if (db != NULL) { dns_db_detach(&db); } return (result); } /* * Called when an "rndc signing -nsec3param ..." command is received, or the * 'dnssec-policy' has changed. * * Allocate and prepare an nsec3param_t structure which holds information about * the NSEC3 changes requested for the zone: * * - if NSEC3 is to be disabled ("-nsec3param none"), only set the "nsec" * field of the structure to true and the "replace" field to the value * of the "replace" argument, leaving other fields initialized to zeros, to * signal that the zone should be signed using NSEC instead of NSEC3, * * - otherwise, prepare NSEC3PARAM RDATA that will eventually be inserted at * the zone apex, convert it to a private-type record and store the latter * in the "data" field of the nsec3param_t structure. * * Once the nsec3param_t structure is prepared, post an event to the zone's * task which will cause setnsec3param() to be called with the prepared * structure passed as an argument. */ isc_result_t dns_zone_setnsec3param(dns_zone_t *zone, uint8_t hash, uint8_t flags, uint16_t iter, uint8_t saltlen, unsigned char *salt, bool replace, bool resalt) { isc_result_t result = ISC_R_SUCCESS; dns_rdata_nsec3param_t param, lookup; dns_rdata_t nrdata = DNS_RDATA_INIT; dns_rdata_t prdata = DNS_RDATA_INIT; unsigned char nbuf[DNS_NSEC3PARAM_BUFFERSIZE]; unsigned char saltbuf[255]; struct np3event *npe; nsec3param_t *np; dns_zone_t *dummy = NULL; isc_buffer_t b; isc_event_t *e = NULL; bool do_lookup = false; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); /* * First check if the requested NSEC3 parameters are already set, * if so, no need to set again. */ if (hash != 0) { lookup.hash = hash; lookup.flags = flags; lookup.iterations = iter; lookup.salt_length = saltlen; lookup.salt = salt; param.salt = NULL; result = dns__zone_lookup_nsec3param(zone, &lookup, ¶m, saltbuf, resalt); if (result == ISC_R_SUCCESS) { UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } /* * Schedule lookup if lookup above failed (may happen if zone * db is NULL for example). */ do_lookup = (param.salt == NULL) ? true : false; } e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_SETNSEC3PARAM, setnsec3param, zone, sizeof(struct np3event)); npe = (struct np3event *)e; np = &npe->params; np->replace = replace; np->resalt = resalt; np->lookup = do_lookup; if (hash == 0) { np->length = 0; np->nsec = true; dnssec_log(zone, ISC_LOG_DEBUG(3), "setnsec3param:nsec"); } else { param.common.rdclass = zone->rdclass; param.common.rdtype = dns_rdatatype_nsec3param; ISC_LINK_INIT(¶m.common, link); param.mctx = NULL; /* nsec3 specific param set in dns__zone_lookup_nsec3param() */ isc_buffer_init(&b, nbuf, sizeof(nbuf)); if (param.salt != NULL) { CHECK(dns_rdata_fromstruct(&nrdata, zone->rdclass, dns_rdatatype_nsec3param, ¶m, &b)); dns_nsec3param_toprivate(&nrdata, &prdata, zone->privatetype, np->data, sizeof(np->data)); np->length = prdata.length; } np->rdata = param; np->nsec = false; if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { unsigned char salttext[255 * 2 + 1]; if (param.salt != NULL) { salt2text(param.salt, param.salt_length, salttext, sizeof(salttext)); } dnssec_log(zone, ISC_LOG_DEBUG(3), "setnsec3param:nsec3 %u %u %u %u:%s", param.hash, param.flags, param.iterations, param.salt_length, param.salt == NULL ? "unknown" : (char *)salttext); } } /* * setnsec3param() will silently return early if the zone does not yet * have a database. Prevent that by queueing the event up if zone->db * is NULL. All events queued here are subsequently processed by * receive_secure_db() if it ever gets called or simply freed by * zone_free() otherwise. */ ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { zone_iattach(zone, &dummy); isc_task_send(zone->task, &e); } else { ISC_LIST_APPEND(zone->setnsec3param_queue, e, ev_link); e = NULL; } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); result = ISC_R_SUCCESS; failure: if (e != NULL) { isc_event_free(&e); } UNLOCK_ZONE(zone); return (result); } isc_result_t dns_zone_getloadtime(dns_zone_t *zone, isc_time_t *loadtime) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(loadtime != NULL); LOCK_ZONE(zone); *loadtime = zone->loadtime; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_result_t dns_zone_getexpiretime(dns_zone_t *zone, isc_time_t *expiretime) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(expiretime != NULL); LOCK_ZONE(zone); *expiretime = zone->expiretime; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_result_t dns_zone_getrefreshtime(dns_zone_t *zone, isc_time_t *refreshtime) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(refreshtime != NULL); LOCK_ZONE(zone); *refreshtime = zone->refreshtime; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } isc_result_t dns_zone_getrefreshkeytime(dns_zone_t *zone, isc_time_t *refreshkeytime) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(refreshkeytime != NULL); LOCK_ZONE(zone); *refreshkeytime = zone->refreshkeytime; UNLOCK_ZONE(zone); return (ISC_R_SUCCESS); } unsigned int dns_zone_getincludes(dns_zone_t *zone, char ***includesp) { dns_include_t *include; char **array = NULL; unsigned int n = 0; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(includesp != NULL && *includesp == NULL); LOCK_ZONE(zone); if (zone->nincludes == 0) { goto done; } array = isc_mem_allocate(zone->mctx, sizeof(char *) * zone->nincludes); for (include = ISC_LIST_HEAD(zone->includes); include != NULL; include = ISC_LIST_NEXT(include, link)) { INSIST(n < zone->nincludes); array[n++] = isc_mem_strdup(zone->mctx, include->name); } INSIST(n == zone->nincludes); *includesp = array; done: UNLOCK_ZONE(zone); return (n); } void dns_zone_setstatlevel(dns_zone_t *zone, dns_zonestat_level_t level) { REQUIRE(DNS_ZONE_VALID(zone)); zone->statlevel = level; } dns_zonestat_level_t dns_zone_getstatlevel(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->statlevel); } static void setserial(isc_task_t *task, isc_event_t *event) { uint32_t oldserial, desired; const char *me = "setserial"; bool commit = false; isc_result_t result; dns_dbversion_t *oldver = NULL, *newver = NULL; dns_zone_t *zone; dns_db_t *db = NULL; dns_diff_t diff; struct ssevent *sse = (struct ssevent *)event; dns_update_log_t log = { update_log_cb, NULL }; dns_difftuple_t *oldtuple = NULL, *newtuple = NULL; UNUSED(task); zone = event->ev_arg; INSIST(DNS_ZONE_VALID(zone)); ENTER; if (zone->update_disabled) { goto disabled; } desired = sse->serial; dns_diff_init(zone->mctx, &diff); ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_read); if (zone->db != NULL) { dns_db_attach(zone->db, &db); } ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read); if (db == NULL) { goto failure; } dns_db_currentversion(db, &oldver); result = dns_db_newversion(db, &newver); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "setserial:dns_db_newversion -> %s", isc_result_totext(result)); goto failure; } CHECK(dns_db_createsoatuple(db, oldver, diff.mctx, DNS_DIFFOP_DEL, &oldtuple)); CHECK(dns_difftuple_copy(oldtuple, &newtuple)); newtuple->op = DNS_DIFFOP_ADD; oldserial = dns_soa_getserial(&oldtuple->rdata); if (desired == 0U) { desired = 1; } if (!isc_serial_gt(desired, oldserial)) { if (desired != oldserial) { dns_zone_log(zone, ISC_LOG_INFO, "setserial: desired serial (%u) " "out of range (%u-%u)", desired, oldserial + 1, (oldserial + 0x7fffffff)); } goto failure; } dns_soa_setserial(desired, &newtuple->rdata); CHECK(do_one_tuple(&oldtuple, db, newver, &diff)); CHECK(do_one_tuple(&newtuple, db, newver, &diff)); result = dns_update_signatures(&log, zone, db, oldver, newver, &diff, zone->sigvalidityinterval); if (result != ISC_R_NOTFOUND) { CHECK(result); } /* Write changes to journal file. */ CHECK(zone_journal(zone, &diff, NULL, "setserial")); commit = true; LOCK_ZONE(zone); zone_needdump(zone, 30); UNLOCK_ZONE(zone); failure: if (oldtuple != NULL) { dns_difftuple_free(&oldtuple); } if (newtuple != NULL) { dns_difftuple_free(&newtuple); } if (oldver != NULL) { dns_db_closeversion(db, &oldver, false); } if (newver != NULL) { dns_db_closeversion(db, &newver, commit); } if (db != NULL) { dns_db_detach(&db); } dns_diff_clear(&diff); disabled: isc_event_free(&event); dns_zone_idetach(&zone); INSIST(oldver == NULL); INSIST(newver == NULL); } isc_result_t dns_zone_setserial(dns_zone_t *zone, uint32_t serial) { isc_result_t result = ISC_R_SUCCESS; dns_zone_t *dummy = NULL; isc_event_t *e = NULL; struct ssevent *sse; REQUIRE(DNS_ZONE_VALID(zone)); LOCK_ZONE(zone); if (!inline_secure(zone)) { if (!dns_zone_isdynamic(zone, true)) { result = DNS_R_NOTDYNAMIC; goto failure; } } if (zone->update_disabled) { result = DNS_R_FROZEN; goto failure; } e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_SETSERIAL, setserial, zone, sizeof(struct ssevent)); sse = (struct ssevent *)e; sse->serial = serial; zone_iattach(zone, &dummy); isc_task_send(zone->task, &e); failure: if (e != NULL) { isc_event_free(&e); } UNLOCK_ZONE(zone); return (result); } isc_stats_t * dns_zone_getgluecachestats(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (zone->gluecachestats); } bool dns_zone_isloaded(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED)); } isc_result_t dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver) { dns_dbversion_t *version = NULL; dns_keytable_t *secroots = NULL; isc_result_t result; dns_name_t *origin; const char me[] = "dns_zone_verifydb"; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(db != NULL); ENTER; if (dns_zone_gettype(zone) != dns_zone_mirror) { return (ISC_R_SUCCESS); } if (ver == NULL) { dns_db_currentversion(db, &version); } else { version = ver; } if (zone->view != NULL) { result = dns_view_getsecroots(zone->view, &secroots); if (result != ISC_R_SUCCESS) { goto done; } } origin = dns_db_origin(db); result = dns_zoneverify_dnssec(zone, db, version, origin, secroots, zone->mctx, true, false, dnssec_report); done: if (secroots != NULL) { dns_keytable_detach(&secroots); } if (ver == NULL) { dns_db_closeversion(db, &version, false); } if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, "zone verification failed: %s", isc_result_totext(result)); result = DNS_R_VERIFYFAILURE; } return (result); } static dns_ttl_t zone_nsecttl(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone)); return (ISC_MIN(zone->minimum, zone->soattl)); } void dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr, isc_tlsctx_cache_t *tlsctx_cache) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); REQUIRE(tlsctx_cache != NULL); RWLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_write); if (zmgr->tlsctx_cache != NULL) { isc_tlsctx_cache_detach(&zmgr->tlsctx_cache); } isc_tlsctx_cache_attach(tlsctx_cache, &zmgr->tlsctx_cache); RWUNLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_write); } static void zmgr_tlsctx_attach(dns_zonemgr_t *zmgr, isc_tlsctx_cache_t **ptlsctx_cache) { REQUIRE(DNS_ZONEMGR_VALID(zmgr)); REQUIRE(ptlsctx_cache != NULL && *ptlsctx_cache == NULL); RWLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_read); INSIST(zmgr->tlsctx_cache != NULL); isc_tlsctx_cache_attach(zmgr->tlsctx_cache, ptlsctx_cache); RWUNLOCK(&zmgr->tlsctx_cache_rwlock, isc_rwlocktype_read); }