bind9/lib/dns/zone.c
Matthijs Mekking f4dc62f33b With update-check-ksk also consider offline keys
The option `update-check-ksk` will look if both KSK and ZSK are
available before signing records.  It will make sure the keys are
active and available.  However, for operational practices keys may
be offline.  This commit relaxes the update-check-ksk check and will
mark a key that is offline to be available when adding signature
tasks.

(cherry picked from commit 3cb8c49c73)
2019-04-12 11:33:06 +02:00

19914 lines
509 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
/*! \file */
#include <config.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <isc/file.h>
#include <isc/hex.h>
#include <isc/mutex.h>
#include <isc/pool.h>
#include <isc/print.h>
#include <isc/random.h>
#include <isc/ratelimiter.h>
#include <isc/refcount.h>
#include <isc/rwlock.h>
#include <isc/serial.h>
#include <isc/stats.h>
#include <isc/stdtime.h>
#include <isc/strerr.h>
#include <isc/string.h>
#include <isc/taskpool.h>
#include <isc/thread.h>
#include <isc/timer.h>
#include <isc/util.h>
#include <dns/acl.h>
#include <dns/adb.h>
#include <dns/callbacks.h>
#include <dns/catz.h>
#include <dns/db.h>
#include <dns/dbiterator.h>
#include <dns/dlz.h>
#include <dns/dnssec.h>
#include <dns/events.h>
#include <dns/journal.h>
#include <dns/keydata.h>
#include <dns/keytable.h>
#include <dns/keyvalues.h>
#include <dns/log.h>
#include <dns/master.h>
#include <dns/masterdump.h>
#include <dns/message.h>
#include <dns/name.h>
#include <dns/nsec.h>
#include <dns/nsec3.h>
#include <dns/peer.h>
#include <dns/private.h>
#include <dns/rcode.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatastruct.h>
#include <dns/rdatatype.h>
#include <dns/request.h>
#include <dns/resolver.h>
#include <dns/result.h>
#include <dns/rriterator.h>
#include <dns/soa.h>
#include <dns/ssu.h>
#include <dns/stats.h>
#include <dns/time.h>
#include <dns/tsig.h>
#include <dns/update.h>
#include <dns/xfrin.h>
#include <dns/zone.h>
#include <dns/zoneverify.h>
#include <dns/zt.h>
#include <dst/dst.h>
#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 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)
/*%
* 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 ALG(x) dst_key_alg(x)
/*
* 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_DUMP_DELAY
#define DNS_DUMP_DELAY 900 /*%< 15 minutes */
#endif
typedef struct dns_notify dns_notify_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_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 == false); \
(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 == false); \
(z)->locked = true; \
} \
} while (0)
#else
#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
#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
struct dns_zone {
/* Unlocked */
unsigned int magic;
isc_mutex_t lock;
#ifdef DNS_ZONE_CHECKLOCK
bool locked;
#endif
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;
unsigned int irefs;
dns_name_t origin;
char *masterfile;
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;
unsigned int flags;
dns_zoneopt_t options;
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;
uint32_t maxrefresh;
uint32_t minrefresh;
uint32_t maxretry;
uint32_t minretry;
uint32_t maxrecords;
isc_sockaddr_t *masters;
isc_dscp_t *masterdscps;
dns_name_t **masterkeynames;
bool *mastersok;
unsigned int masterscnt;
unsigned int curmaster;
isc_sockaddr_t masteraddr;
dns_notifytype_t notifytype;
isc_sockaddr_t *notify;
dns_name_t **notifykeynames;
isc_dscp_t *notifydscp;
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 xfrsource4;
isc_sockaddr_t xfrsource6;
isc_sockaddr_t altxfrsource4;
isc_sockaddr_t altxfrsource6;
isc_sockaddr_t sourceaddr;
isc_dscp_t notifysrc4dscp;
isc_dscp_t notifysrc6dscp;
isc_dscp_t xfrsource4dscp;
isc_dscp_t xfrsource6dscp;
isc_dscp_t altxfrsource4dscp;
isc_dscp_t altxfrsource6dscp;
dns_xfrin_ctx_t *xfr; /* task locked */
dns_tsigkey_t *tsigkey; /* key 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;
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_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;
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
*/
uint32_t keyopts;
/*%
* 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;
/*%
* 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;
/*%
* maximum zone ttl
*/
dns_ttl_t maxttl;
/*
* Inline zone signing state.
*/
dns_diff_t rss_diff;
isc_eventlist_t rss_events;
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) ((z)->flags & (f))
#define DNS_ZONE_SETFLAG(z,f) do { \
INSIST(LOCKED_ZONE(z)); \
(z)->flags |= (f); \
} while (0)
#define DNS_ZONE_CLRFLAG(z,f) do { \
INSIST(LOCKED_ZONE(z)); \
(z)->flags &= ~(f); \
} while (0)
/* XXX MPA these may need to go back into zone.h */
#define DNS_ZONEFLG_REFRESH 0x00000001U /*%< refresh check in progress */
#define DNS_ZONEFLG_NEEDDUMP 0x00000002U /*%< zone need consolidation */
#define DNS_ZONEFLG_USEVC 0x00000004U /*%< use tcp for refresh query */
#define DNS_ZONEFLG_DUMPING 0x00000008U /*%< a dump is in progress */
#define DNS_ZONEFLG_HASINCLUDE 0x00000010U /*%< $INCLUDE in zone file */
#define DNS_ZONEFLG_LOADED 0x00000020U /*%< database has loaded */
#define DNS_ZONEFLG_EXITING 0x00000040U /*%< zone is being destroyed */
#define DNS_ZONEFLG_EXPIRED 0x00000080U /*%< zone has expired */
#define DNS_ZONEFLG_NEEDREFRESH 0x00000100U /*%< refresh check needed */
#define DNS_ZONEFLG_UPTODATE 0x00000200U /*%< zone contents are
* uptodate */
#define DNS_ZONEFLG_NEEDNOTIFY 0x00000400U /*%< need to send out notify
* messages */
#define DNS_ZONEFLG_DIFFONRELOAD 0x00000800U /*%< generate a journal diff on
* reload */
#define DNS_ZONEFLG_NOMASTERS 0x00001000U /*%< an attempt to refresh a
* zone with no masters
* occurred */
#define DNS_ZONEFLG_LOADING 0x00002000U /*%< load from disk in progress*/
#define DNS_ZONEFLG_HAVETIMERS 0x00004000U /*%< timer values have been set
* from SOA (if not set, we
* are still using
* default timer values) */
#define DNS_ZONEFLG_FORCEXFER 0x00008000U /*%< Force a zone xfer */
#define DNS_ZONEFLG_NOREFRESH 0x00010000U
#define DNS_ZONEFLG_DIALNOTIFY 0x00020000U
#define DNS_ZONEFLG_DIALREFRESH 0x00040000U
#define DNS_ZONEFLG_SHUTDOWN 0x00080000U
#define DNS_ZONEFLAG_NOIXFR 0x00100000U /*%< IXFR failed, force AXFR */
#define DNS_ZONEFLG_FLUSH 0x00200000U
#define DNS_ZONEFLG_NOEDNS 0x00400000U
#define DNS_ZONEFLG_USEALTXFRSRC 0x00800000U
#define DNS_ZONEFLG_SOABEFOREAXFR 0x01000000U
#define DNS_ZONEFLG_NEEDCOMPACT 0x02000000U
#define DNS_ZONEFLG_REFRESHING 0x04000000U /*%< Refreshing keydata */
#define DNS_ZONEFLG_THAW 0x08000000U
#define DNS_ZONEFLG_LOADPENDING 0x10000000U /*%< Loading scheduled */
#define DNS_ZONEFLG_NODELAY 0x20000000U
#define DNS_ZONEFLG_SENDSECURE 0x40000000U
#define DNS_ZONEFLG_NEEDSTARTUPNOTIFY 0x80000000U /*%< need to send out notify
* due to the zone just
* being loaded for the
* first time. */
#define DNS_ZONE_OPTION(z,o) (((z)->options & (o)) != 0)
#define DNS_ZONEKEY_OPTION(z,o) (((z)->keyopts & (o)) != 0)
/* Flags for zone_load() */
#define DNS_ZONELOADFLAG_NOSTAT 0x00000001U /* Do not stat() master files */
#define DNS_ZONELOADFLAG_THAW 0x00000002U /* Thaw the zone on successful
load. */
#define UNREACH_CHACHE_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;
uint32_t expire;
uint32_t last;
uint32_t count;
};
struct dns_zonemgr {
unsigned int magic;
isc_mem_t * mctx;
int refs; /* Locked by rwlock */
isc_taskmgr_t * taskmgr;
isc_timermgr_t * timermgr;
isc_socketmgr_t * socketmgr;
isc_taskpool_t * zonetasks;
isc_taskpool_t * loadtasks;
isc_task_t * task;
isc_pool_t * mctxpool;
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 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_CHACHE_SIZE];
};
/*%
* 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;
isc_dscp_t dscp;
ISC_LINK(dns_notify_t) link;
isc_event_t *event;
};
#define DNS_NOTIFY_NOSOA 0x0001U
#define DNS_NOTIFY_STARTUP 0x0002U
/*%
* 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;
};
/*%
* 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 {
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)
LIBDNS_EXTERNAL_DATA unsigned int dns_zone_mkey_hour = HOUR;
LIBDNS_EXTERNAL_DATA unsigned int dns_zone_mkey_day = DAY;
LIBDNS_EXTERNAL_DATA 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_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 inline void zone_attachdb(dns_zone_t *zone, dns_db_t *db);
static inline void zone_detachdb(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 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 isc_result_t
zone_get_from_db(dns_zone_t *zone, dns_db_t *db, unsigned int *nscount,
unsigned int *soacount, 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 void setrl(isc_ratelimiter_t *rl, unsigned int *rate,
unsigned int value);
#define ENTER zone_debuglog(zone, me, 1, "enter")
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 {
unsigned char data[DNS_NSEC3PARAM_BUFFERSIZE + 1];
unsigned int length;
bool nsec;
bool replace;
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;
};
/*%
* Increment resolver-related statistics counters. Zone must be locked.
*/
static inline 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;
dns_zone_t *zone;
isc_time_t now;
REQUIRE(zonep != NULL && *zonep == NULL);
REQUIRE(mctx != NULL);
TIME_NOW(&now);
zone = isc_mem_get(mctx, sizeof(*zone));
if (zone == NULL) {
return (ISC_R_NOMEMORY);
}
zone->mctx = NULL;
isc_mem_attach(mctx, &zone->mctx);
isc_mutex_init(&zone->lock);
result = ZONEDB_INITLOCK(&zone->dblock);
if (result != ISC_R_SUCCESS) {
goto free_mutex;
}
/* XXX MPA check that all elements are initialised */
#ifdef DNS_ZONE_CHECKLOCK
zone->locked = false;
#endif
zone->db = NULL;
zone->zmgr = NULL;
ISC_LINK_INIT(zone, link);
isc_refcount_init(&zone->erefs, 1); /* Implicit attach. */
zone->irefs = 0;
dns_name_init(&zone->origin, NULL);
zone->strnamerd = NULL;
zone->strname = NULL;
zone->strrdclass = NULL;
zone->strviewname = NULL;
zone->masterfile = NULL;
ISC_LIST_INIT(zone->includes);
ISC_LIST_INIT(zone->newincludes);
zone->nincludes = 0;
zone->masterformat = dns_masterformat_none;
zone->masterstyle = NULL;
zone->keydirectory = NULL;
zone->journalsize = -1;
zone->journal = NULL;
zone->rdclass = dns_rdataclass_none;
zone->type = dns_zone_none;
zone->flags = 0;
zone->options = 0;
zone->keyopts = 0;
zone->db_argc = 0;
zone->db_argv = NULL;
isc_time_settoepoch(&zone->expiretime);
isc_time_settoepoch(&zone->refreshtime);
isc_time_settoepoch(&zone->dumptime);
isc_time_settoepoch(&zone->loadtime);
zone->notifytime = now;
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);
zone->refreshkeyinterval = 0;
zone->refreshkeycount = 0;
zone->refresh = DNS_ZONE_DEFAULTREFRESH;
zone->retry = DNS_ZONE_DEFAULTRETRY;
zone->expire = 0;
zone->minimum = 0;
zone->maxrefresh = DNS_ZONE_MAXREFRESH;
zone->minrefresh = DNS_ZONE_MINREFRESH;
zone->maxretry = DNS_ZONE_MAXRETRY;
zone->minretry = DNS_ZONE_MINRETRY;
zone->masters = NULL;
zone->masterdscps = NULL;
zone->masterkeynames = NULL;
zone->mastersok = NULL;
zone->masterscnt = 0;
zone->curmaster = 0;
zone->maxttl = 0;
zone->notify = NULL;
zone->notifykeynames = NULL;
zone->notifydscp = NULL;
zone->notifytype = dns_notifytype_yes;
zone->notifycnt = 0;
zone->task = NULL;
zone->loadtask = NULL;
zone->update_acl = NULL;
zone->forward_acl = NULL;
zone->notify_acl = NULL;
zone->query_acl = NULL;
zone->queryon_acl = NULL;
zone->xfr_acl = NULL;
zone->update_disabled = false;
zone->zero_no_soa_ttl = true;
zone->check_names = dns_severity_ignore;
zone->request = NULL;
zone->lctx = NULL;
zone->readio = NULL;
zone->dctx = NULL;
zone->writeio = NULL;
zone->timer = NULL;
zone->idlein = DNS_DEFAULT_IDLEIN;
zone->idleout = DNS_DEFAULT_IDLEOUT;
zone->log_key_expired_timer = 0;
ISC_LIST_INIT(zone->notifies);
isc_sockaddr_any(&zone->notifysrc4);
isc_sockaddr_any6(&zone->notifysrc6);
isc_sockaddr_any(&zone->xfrsource4);
isc_sockaddr_any6(&zone->xfrsource6);
isc_sockaddr_any(&zone->altxfrsource4);
isc_sockaddr_any6(&zone->altxfrsource6);
zone->notifysrc4dscp = -1;
zone->notifysrc6dscp = -1;
zone->xfrsource4dscp = -1;
zone->xfrsource6dscp = -1;
zone->altxfrsource4dscp = -1;
zone->altxfrsource6dscp = -1;
zone->xfr = NULL;
zone->tsigkey = NULL;
zone->maxxfrin = MAX_XFER_TIME;
zone->maxxfrout = MAX_XFER_TIME;
zone->ssutable = NULL;
zone->sigvalidityinterval = 30 * 24 * 3600;
zone->keyvalidityinterval = 0;
zone->sigresigninginterval = 7 * 24 * 3600;
zone->view = NULL;
zone->prev_view = NULL;
zone->checkmx = NULL;
zone->checksrv = NULL;
zone->checkns = NULL;
ISC_LINK_INIT(zone, statelink);
zone->statelist = NULL;
zone->stats = NULL;
zone->requeststats_on = false;
zone->statlevel = dns_zonestat_none;
zone->requeststats = NULL;
zone->rcvquerystats = NULL;
zone->notifydelay = 5;
zone->isself = NULL;
zone->isselfarg = NULL;
ISC_LIST_INIT(zone->signing);
ISC_LIST_INIT(zone->nsec3chain);
ISC_LIST_INIT(zone->setnsec3param_queue);
zone->signatures = 10;
zone->nodes = 100;
zone->privatetype = (dns_rdatatype_t)0xffffU;
zone->added = false;
zone->automatic = false;
zone->rpzs = NULL;
zone->rpz_num = DNS_RPZ_INVALID_NUM;
zone->catzs = NULL;
zone->parentcatz = NULL;
ISC_LIST_INIT(zone->forwards);
zone->raw = NULL;
zone->secure = NULL;
zone->sourceserial = 0;
zone->sourceserialset = false;
zone->requestixfr = true;
zone->requestexpire = true;
ISC_LIST_INIT(zone->rss_events);
zone->rss_db = NULL;
zone->rss_raw = NULL;
zone->rss_newver = NULL;
zone->rss_oldver = NULL;
zone->rss_event = NULL;
zone->rss_state = NULL;
zone->updatemethod = dns_updatemethod_increment;
zone->maxrecords = 0U;
zone->magic = ZONE_MAGIC;
zone->gluecachestats = NULL;
result = isc_stats_create(mctx, &zone->gluecachestats,
dns_gluecachestatscounter_max);
if (result != ISC_R_SUCCESS) {
goto free_erefs;
}
/* Must be after magic is set. */
result = dns_zone_setdbtype(zone, dbargc_default, dbargv_default);
if (result != ISC_R_SUCCESS) {
goto free_stats;
}
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_stats:
if (zone->gluecachestats != NULL)
isc_stats_detach(&zone->gluecachestats);
free_erefs:
INSIST(isc_refcount_decrement(&zone->erefs) > 0);
isc_refcount_destroy(&zone->erefs);
ZONEDB_DESTROYLOCK(&zone->dblock);
free_mutex:
isc_mutex_destroy(&zone->lock);
isc_mem_putanddetach(&zone->mctx, zone, sizeof(*zone));
return (result);
}
/*
* 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) {
isc_mem_t *mctx = NULL;
dns_signing_t *signing;
dns_nsec3chain_t *nsec3chain;
isc_event_t *setnsec3param_event;
dns_include_t *include;
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(isc_refcount_current(&zone->erefs) == 0);
REQUIRE(zone->irefs == 0);
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);
if (zone->task != NULL) {
isc_task_detach(&zone->task);
}
if (zone->loadtask != NULL) {
isc_task_detach(&zone->loadtask);
}
if (zone->view != NULL) {
dns_view_weakdetach(&zone->view);
}
if (zone->prev_view != NULL) {
dns_view_weakdetach(&zone->prev_view);
}
/* Unmanaged objects */
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);
isc_event_free(&setnsec3param_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;
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->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_catzs_detach(&zone->catzs);
}
zone_freedbargs(zone);
RUNTIME_CHECK(dns_zone_setmasterswithkeys(zone, NULL,
NULL, 0) == ISC_R_SUCCESS);
RUNTIME_CHECK(dns_zone_setalsonotify(zone, NULL, 0) == ISC_R_SUCCESS);
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;
mctx = zone->mctx;
isc_mem_put(mctx, zone, sizeof(*zone));
isc_mem_detach(&mctx);
}
/*
* Returns true iff this the signed side of an inline-signing zone.
* Caller should hold zone lock.
*/
static inline 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 inline 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,
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);
if (mem != NULL) {
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;
} else
result = ISC_R_NOMEMORY;
UNLOCK_ZONE(zone);
*argv = mem;
return (result);
}
isc_result_t
dns_zone_setdbtype(dns_zone_t *zone,
unsigned int dbargc, const char * const *dbargv)
{
isc_result_t result = ISC_R_SUCCESS;
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));
if (argv == NULL) {
goto nomem;
}
for (i = 0; i < dbargc; i++) {
argv[i] = NULL;
}
for (i = 0; i < dbargc; i++) {
argv[i] = isc_mem_strdup(zone->mctx, dbargv[i]);
if (argv[i] == NULL)
goto nomem;
}
/* Free the old list. */
zone_freedbargs(zone);
zone->db_argc = dbargc;
zone->db_argv = argv;
result = ISC_R_SUCCESS;
goto unlock;
nomem:
if (argv != NULL) {
for (i = 0; i < dbargc; i++) {
if (argv[i] != NULL) {
isc_mem_free(zone->mctx, argv[i]);
}
}
isc_mem_put(zone->mctx, argv, dbargc * sizeof(*argv));
}
result = ISC_R_NOMEMORY;
unlock:
UNLOCK_ZONE(zone);
return (result);
}
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_weakdetach(&zone->view);
}
dns_view_weakattach(view, &zone->view);
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);
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);
}
UNLOCK_ZONE(zone);
}
isc_result_t
dns_zone_setorigin(dns_zone_t *zone, const dns_name_t *origin) {
isc_result_t result;
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);
}
result = 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 (result == ISC_R_SUCCESS && 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);
if (copy == NULL)
return (ISC_R_NOMEMORY);
} 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));
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);
}
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)
zone->options |= DNS_ZONEOPT_CHECKTTL;
else
zone->options &= ~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);
if (journal == NULL)
return (ISC_R_NOMEMORY);
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 slave 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_slave || zone->type == dns_zone_mirror ||
zone->type == dns_zone_stub || zone->type == dns_zone_key ||
(zone->type == dns_zone_redirect && zone->masters != NULL))
return (true);
/* Inline zones are always dynamic. */
if (zone->type == dns_zone_master && zone->raw != NULL)
return (true);
/* If !ignore_freeze, we need check whether updates are disabled. */
if (zone->type == dns_zone_master &&
(!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 load the 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);
if (zone->masterformat == dns_masterformat_map)
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]);
}
void
dns_zone_catz_enable(dns_zone_t *zone, dns_catz_zones_t *catzs) {
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(catzs != NULL);
LOCK_ZONE(zone);
INSIST(zone->catzs == NULL || zone->catzs == catzs);
dns_catz_catzs_set_view(catzs, zone->view);
if (zone->catzs == NULL) {
dns_catz_catzs_attach(catzs, &zone->catzs);
}
UNLOCK_ZONE(zone);
}
/*
* 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_db_updatenotify_register(db, dns_catz_dbupdate_callback,
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_db_updatenotify_unregister(db, dns_catz_dbupdate_callback,
zone->catzs);
}
}
/*
* 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;
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;
}
if (zone->db != NULL && dns_zone_isdynamic(zone, false)) {
/*
* This is a slave, 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_master && !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_master &&
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_slave || zone->type == dns_zone_mirror ||
zone->type == dns_zone_stub ||
(zone->type == dns_zone_redirect && zone->masters != NULL)) &&
rbt) {
if (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_master || zone->type == dns_zone_slave ||
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) {
result = zone_startload(db, zone, loadtime);
} else {
result = DNS_R_NOMASTERFILE;
if (zone->type == dns_zone_master ||
(zone->type == dns_zone_redirect &&
zone->masters == 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);
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;
isc_result_t result = ISC_R_SUCCESS;
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));
if (asl == NULL)
CHECK(ISC_R_NOMEMORY);
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));
if (e == NULL)
CHECK(ISC_R_NOMEMORY);
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);
failure:
if (asl != NULL)
isc_mem_put(zone->mctx, asl, sizeof (*asl));
UNLOCK_ZONE(zone);
return (result);
}
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
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_master_options(dns_zone_t *zone) {
unsigned int options;
options = DNS_MASTER_ZONE | DNS_MASTER_RESIGN;
if (zone->type == dns_zone_slave || zone->type == dns_zone_mirror ||
(zone->type == dns_zone_redirect && zone->masters == NULL))
{
options |= DNS_MASTER_SLAVE;
}
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));
if (inc == NULL)
return;
inc->name = isc_mem_strdup(zone->mctx, filename);
if (inc->name == NULL) {
isc_mem_put(zone->mctx, inc, sizeof(dns_include_t));
return;
}
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_master_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,
&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;
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) {
const dns_master_style_t *output_style;
dns_db_currentversion(zone->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_dumpinc(zone->mctx, zone->db, version,
output_style, zone->masterfile,
zone->task, dump_done, zone,
&zone->dctx, zone->masterformat,
&rawdata);
dns_db_closeversion(zone->db, &version, false);
} else
result = ISC_R_CANCELED;
ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
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_master_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));
if (load == NULL)
return (ISC_R_NOMEMORY);
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);
}
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_master)
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_master)
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_master)
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, &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;
/*
* 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, NULL);
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, NULL);
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;
bool logit, foundrsa = false;
const char *algorithm;
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.algorithm == DST_ALG_RSASHA1 &&
dnskey.datalen > 1 && dnskey.data[0] == 1 &&
dnskey.data[1] == 3)
{
if (dnskey.algorithm == DST_ALG_RSASHA1) {
logit = !foundrsa;
foundrsa = true;
algorithm = "RSASHA1";
}
if (logit) {
dnssec_log(zone, ISC_LOG_WARNING,
"weak %s (%u) key found "
"(exponent=3)", algorithm,
dnskey.algorithm);
}
}
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",
dns_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, &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);
if (nsec3chain == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
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, &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",
dns_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;
/* We only re-sign zones that can be dynamically updated */
if (zone->update_disabled)
return;
if (!inline_secure(zone) && (zone->type != dns_zone_master ||
(zone->ssutable == NULL &&
(zone->update_acl == NULL || dns_acl_isnone(zone->update_acl)))))
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 - zone->sigresigninginterval;
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) {
dns_dbnode_t *node = NULL;
dns_rdataset_t rdataset;
dns_dbversion_t *version = NULL;
dns_rdata_nsec3param_t nsec3param;
bool ok = false;
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
bool dynamic = (zone->type == dns_zone_master)
? 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",
dns_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",
dns_result_totext(result));
goto cleanup;
}
/*
* 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.
*/
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);
dns_rdata_reset(&rdata);
INSIST(result == ISC_R_SUCCESS);
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;
}
}
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);
}
/*
* Convert key(s) linked from 'keynode' to KEYDATA and add to the key zone.
* 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_keytable_t *keytable,
dns_keynode_t **keynodep, bool *changed)
{
const char me[] = "create_keydata";
isc_result_t result = ISC_R_SUCCESS;
isc_buffer_t keyb, dstb;
unsigned char key_buf[4096], dst_buf[DST_KEY_MAXSIZE];
dns_rdata_keydata_t keydata;
dns_rdata_dnskey_t dnskey;
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_keynode_t *keynode;
isc_stdtime_t now;
isc_region_t r;
dst_key_t *key;
REQUIRE(keynodep != NULL);
keynode = *keynodep;
ENTER;
isc_stdtime_get(&now);
/* Loop in case there's more than one key. */
while (result == ISC_R_SUCCESS) {
dns_keynode_t *nextnode = NULL;
key = dns_keynode_key(keynode);
if (key == NULL)
goto skip;
isc_buffer_init(&dstb, dst_buf, sizeof(dst_buf));
CHECK(dst_key_todns(key, &dstb));
/* Convert DST key to DNSKEY. */
dns_rdata_reset(&rdata);
isc_buffer_usedregion(&dstb, &r);
dns_rdata_fromregion(&rdata, dst_key_class(key),
dns_rdatatype_dnskey, &r);
/* DSTKEY to KEYDATA. */
CHECK(dns_rdata_tostruct(&rdata, &dnskey, NULL));
CHECK(dns_keydata_fromdnskey(&keydata, &dnskey, now, 0, 0,
NULL));
/* KEYDATA to rdata. */
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));
/* Add rdata to zone. */
CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADD,
dst_key_name(key), 0, &rdata));
*changed = true;
/* Refresh new keys from the zone apex as soon as possible. */
set_refreshkeytimer(zone, &keydata, now, true);
skip:
result = dns_keytable_nextkeynode(keytable, keynode, &nextnode);
if (result != ISC_R_NOTFOUND) {
dns_keytable_detachkeynode(keytable, &keynode);
keynode = nextnode;
}
}
if (keynode != NULL)
dns_keytable_detachkeynode(keytable, &keynode);
*keynodep = NULL;
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);
}
/*
* 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_mem_t *mctx)
{
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
unsigned char data[4096];
isc_buffer_t buffer;
dns_keytable_t *sr = NULL;
dst_key_t *dstkey = NULL;
/* Convert dnskey to DST key. */
isc_buffer_init(&buffer, data, sizeof(data));
dns_rdata_fromstruct(&rdata, dnskey->common.rdclass,
dns_rdatatype_dnskey, dnskey, &buffer);
result = dns_view_getsecroots(zone->view, &sr);
if (result != ISC_R_SUCCESS)
goto failure;
CHECK(dns_dnssec_keyfromrdata(keyname, &rdata, mctx, &dstkey));
CHECK(dns_keytable_add(sr, true, initial, &dstkey));
dns_keytable_detach(&sr);
failure:
if (dstkey != NULL)
dst_key_free(&dstkey);
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;
isc_mem_t *mctx = zone->mctx;
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);
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), mctx);
}
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_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;
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);
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, dns_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, dns_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",
dns_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",
dns_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, 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_keynode_t *dummy = NULL;
if (((struct addifmissing_arg *)arg)->result != ISC_R_SUCCESS)
return;
if (dns_keynode_managed(keynode)) {
dns_fixedname_t fname;
dns_name_t *keyname;
dst_key_t *key;
key = dns_keynode_key(keynode);
if (key == NULL)
return;
dns_fixedname_init(&fname);
keyname = dst_key_name(key);
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;
dns_keytable_attachkeynode(keytable, keynode, &dummy);
result = create_keydata(zone, db, ver, diff, keytable,
&dummy, 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 mentioned in in
* managed-keys but there are no references to it in the key zone, load
* the key zone with the initializing key(s) for that domain.
*/
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",
dns_result_totext(result));
goto failure;
}
/*
* Walk the zone DB. If we find any keys whose names are no longer
* in managed-keys (or *are* in trusted-keys, 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_name_t *rrname = NULL;
uint32_t ttl;
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;
}
result = dns_keytable_find(sr, rrname, &keynode);
if ((result != ISC_R_SUCCESS &&
result != DNS_R_PARTIALMATCH) ||
dns_keynode_managed(keynode) == false)
{
CHECK(delete_keydata(db, ver, &diff,
rrname, rdataset));
changed = true;
} else {
load_secroots(zone, rrname, rdataset);
}
if (keynode != NULL) {
dns_keytable_detachkeynode(sr, &keynode);
}
}
dns_rriterator_destroy(&rrit);
/*
* Now walk secroots to find any managed keys that aren't
* in the zone. If we find any, we add them to the zone.
*/
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(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 &&
!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED))
{
dnssec_log(zone, ISC_LOG_ERROR,
"unable to synchronize managed keys: %s",
dns_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, &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;
isc_time_t now;
bool needdump = false;
bool hasinclude = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_HASINCLUDE);
bool nomaster = false;
bool had_db = false;
unsigned int options;
dns_include_t *inc;
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_slave ||
zone->type == dns_zone_mirror ||
zone->type == dns_zone_stub ||
(zone->type == dns_zone_redirect &&
zone->masters == 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,
dns_result_totext(result));
}
} else if (zone->type == dns_zone_master &&
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,
dns_result_totext(result));
nomaster = 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));
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 (nomaster && 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))
{
if (zone->type == dns_zone_master && (inline_secure(zone) ||
(zone->update_acl != NULL || zone->ssutable != NULL)))
{
options = DNS_JOURNALOPT_RESIGN;
} else {
options = 0;
}
result = dns_journal_rollforward(zone->mctx, db, options,
zone->journal);
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND &&
result != DNS_R_UPTODATE && result != DNS_R_NOJOURNAL &&
result != ISC_R_RANGE)
{
dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
ISC_LOG_ERROR,
"journal rollforward failed: %s",
dns_result_totext(result));
goto cleanup;
}
if (result == ISC_R_NOTFOUND || result == ISC_R_RANGE) {
dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
ISC_LOG_ERROR,
"journal rollforward failed: "
"journal out of sync with zone");
goto cleanup;
}
dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1),
"journal rollforward completed "
"successfully: %s",
dns_result_totext(result));
if (result == ISC_R_SUCCESS) {
needdump = true;
}
}
/*
* Obtain ns, soa and cname counts for top of zone.
*/
INSIST(db != NULL);
result = zone_get_from_db(zone, db, &nscount, &soacount, &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");
}
/*
* 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 && dns_zone_isdynamic(zone, true) &&
! 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");
/*
* Master / Slave / 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_master:
case dns_zone_slave:
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_master && 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_master &&
DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKINTEGRITY) &&
!integrity_checks(zone, db))
{
result = DNS_R_BADZONE;
goto cleanup;
}
if (zone->type == dns_zone_master &&
DNS_ZONE_OPTION(zone, DNS_ZONEOPT_CHECKDUPRR) &&
!zone_check_dup(zone, db))
{
result = DNS_R_BADZONE;
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 slave zones
* as they don't reload from disk.
*/
result = zone_get_from_db(zone, zone->db, NULL,
&oldsoacount, &oldserial,
NULL, NULL, NULL, NULL,
NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
RUNTIME_CHECK(soacount > 0U);
if (DNS_ZONE_OPTION(zone, DNS_ZONEOPT_IXFRFROMDIFFS) &&
!isc_serial_gt(serial, oldserial)) {
uint32_t serialmin, serialmax;
INSIST(zone->type == dns_zone_master);
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");
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 slaves.", serial);
}
}
if (zone->type == dns_zone_master &&
(zone->update_acl != NULL || zone->ssutable != NULL) &&
zone->sigresigninginterval < (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->minimum = minimum;
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS);
if (zone->type == dns_zone_slave ||
zone->type == dns_zone_mirror ||
zone->type == dns_zone_stub ||
(zone->type == dns_zone_redirect &&
zone->masters != 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:
result = sync_keyzone(zone, db);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
break;
default:
UNEXPECTED_ERROR(__FILE__, __LINE__,
"unexpected zone type %d", zone->type);
result = ISC_R_UNEXPECTED;
goto cleanup;
}
/*
* Check for weak DNSKEY's.
*/
if (zone->type == dns_zone_master) {
zone_check_dnskeys(zone, db);
}
/*
* Schedule DNSSEC key refresh.
*/
if (zone->type == dns_zone_master &&
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_master && inline_secure(zone)) {
maybe_send_secure(zone);
}
result = ISC_R_SUCCESS;
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_master) {
set_resigntime(zone);
resume_signingwithkey(zone);
resume_addnsec3chain(zone);
}
if (zone->type == dns_zone_master &&
!DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_NORESIGN) &&
dns_zone_isdynamic(zone, false) &&
dns_db_issecure(db))
{
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 -
zone->sigresigninginterval);
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 (zone->type == dns_zone_key && result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"failed to initialize managed-keys (%s): "
"DNSSEC validation is at risk",
isc_result_totext(result));
}
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_slave ||
zone->type == dns_zone_mirror ||
zone->type == dns_zone_stub ||
zone->type == dns_zone_key ||
(zone->type == dns_zone_redirect && zone->masters != 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_master ||
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_master) {
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);
}
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) && 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_master)
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_master ||
zone->type == dns_zone_slave ||
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);
}
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 *serial, uint32_t *refresh,
uint32_t *retry, uint32_t *expire,
uint32_t *minimum)
{
isc_result_t result;
unsigned int count;
dns_rdataset_t rdataset;
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_soa_t soa;
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));
if (soacount != NULL)
*soacount = 0;
if (serial != NULL)
*serial = 0;
if (refresh != NULL)
*refresh = 0;
if (retry != NULL)
*retry = 0;
if (expire != NULL)
*expire = 0;
if (minimum != NULL)
*minimum = 0;
result = ISC_R_SUCCESS;
goto invalidate_rdataset;
}
if (result != ISC_R_SUCCESS) {
INSIST(!dns_rdataset_isassociated(&rdataset));
goto invalidate_rdataset;
}
count = 0;
result = dns_rdataset_first(&rdataset);
while (result == ISC_R_SUCCESS) {
dns_rdata_init(&rdata);
dns_rdataset_current(&rdataset, &rdata);
count++;
if (count == 1) {
result = dns_rdata_tostruct(&rdata, &soa, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
}
result = dns_rdataset_next(&rdataset);
dns_rdata_reset(&rdata);
}
dns_rdataset_disassociate(&rdataset);
if (soacount != NULL)
*soacount = count;
if (count > 0) {
if (serial != NULL)
*serial = soa.serial;
if (refresh != NULL)
*refresh = soa.refresh;
if (retry != NULL)
*retry = soa.retry;
if (expire != NULL)
*expire = soa.expire;
if (minimum != NULL)
*minimum = soa.minimum;
} else {
if (soacount != NULL)
*soacount = 0;
if (serial != NULL)
*serial = 0;
if (refresh != NULL)
*refresh = 0;
if (retry != NULL)
*retry = 0;
if (expire != NULL)
*expire = 0;
if (minimum != NULL)
*minimum = 0;
}
result = ISC_R_SUCCESS;
invalidate_rdataset:
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 *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);
if (nscount != NULL)
*nscount = 0;
if (soacount != NULL)
*soacount = 0;
if (serial != NULL)
*serial = 0;
if (refresh != NULL)
*refresh = 0;
if (retry != NULL)
*retry = 0;
if (expire != NULL)
*expire = 0;
if (errors != NULL)
*errors = 0;
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 || serial != NULL || refresh != NULL
|| retry != NULL || expire != NULL || minimum != NULL) {
result = zone_load_soa_rr(db, node, version, soacount,
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;
bool free_now = false;
dns_zone_t *raw = NULL;
dns_zone_t *secure = NULL;
if (isc_refcount_decrement(&zone->erefs) == 1) {
isc_refcount_destroy(&zone->erefs);
LOCK_ZONE(zone);
INSIST(zone != zone->raw);
/*
* We just detached the last external reference.
*/
if (zone->task != NULL) {
/*
* This zone is being managed. Post
* its control event and let it clean
* up synchronously in the context of
* its task.
*/
isc_event_t *ev = &zone->ctlevent;
isc_task_send(zone->task, &ev);
} else {
/*
* This zone is not being managed; it has
* no task and can have no outstanding
* events. Free it immediately.
*/
/*
* Unmanaged zones should not have non-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);
free_now = true;
raw = zone->raw;
zone->raw = NULL;
secure = zone->secure;
zone->secure = NULL;
}
UNLOCK_ZONE(zone);
}
if (free_now) {
if (raw != NULL) {
dns_zone_detach(&raw);
}
if (secure != NULL) {
dns_zone_idetach(&secure);
}
zone_free(zone);
}
}
void
dns_zone_iattach(dns_zone_t *source, dns_zone_t **target) {
REQUIRE(DNS_ZONE_VALID(source));
REQUIRE(target != NULL && *target == NULL);
LOCK_ZONE(source);
zone_iattach(source, target);
UNLOCK_ZONE(source);
}
static void
zone_iattach(dns_zone_t *source, dns_zone_t **target) {
/*
* 'source' locked by caller.
*/
REQUIRE(LOCKED_ZONE(source));
REQUIRE(DNS_ZONE_VALID(source));
REQUIRE(target != NULL && *target == NULL);
INSIST(source->irefs + isc_refcount_current(&source->erefs) > 0);
source->irefs++;
INSIST(source->irefs != 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));
zone = *zonep;
REQUIRE(LOCKED_ZONE(*zonep));
*zonep = NULL;
INSIST(zone->irefs > 0);
zone->irefs--;
INSIST(zone->irefs + isc_refcount_current(&zone->erefs) > 0);
}
void
dns_zone_idetach(dns_zone_t **zonep) {
dns_zone_t *zone;
bool free_needed;
REQUIRE(zonep != NULL && DNS_ZONE_VALID(*zonep));
zone = *zonep;
*zonep = NULL;
LOCK_ZONE(zone);
INSIST(zone->irefs > 0);
zone->irefs--;
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_setflag(dns_zone_t *zone, unsigned int flags, bool value) {
REQUIRE(DNS_ZONE_VALID(zone));
LOCK_ZONE(zone);
if (value)
DNS_ZONE_SETFLAG(zone, flags);
else
DNS_ZONE_CLRFLAG(zone, flags);
UNLOCK_ZONE(zone);
}
void
dns_zone_setoption(dns_zone_t *zone, dns_zoneopt_t option,
bool value)
{
REQUIRE(DNS_ZONE_VALID(zone));
LOCK_ZONE(zone);
if (value)
zone->options |= option;
else
zone->options &= ~option;
UNLOCK_ZONE(zone);
}
dns_zoneopt_t
dns_zone_getoptions(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return (zone->options);
}
void
dns_zone_setkeyopt(dns_zone_t *zone, unsigned int keyopt, bool value)
{
REQUIRE(DNS_ZONE_VALID(zone));
LOCK_ZONE(zone);
if (value)
zone->keyopts |= keyopt;
else
zone->keyopts &= ~keyopt;
UNLOCK_ZONE(zone);
}
unsigned int
dns_zone_getkeyopts(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return (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_setxfrsource4dscp(dns_zone_t *zone, isc_dscp_t dscp) {
REQUIRE(DNS_ZONE_VALID(zone));
LOCK_ZONE(zone);
zone->xfrsource4dscp = dscp;
UNLOCK_ZONE(zone);
return (ISC_R_SUCCESS);
}
isc_dscp_t
dns_zone_getxfrsource4dscp(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return (zone->xfrsource4dscp);
}
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_dscp_t
dns_zone_getxfrsource6dscp(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return (zone->xfrsource6dscp);
}
isc_result_t
dns_zone_setxfrsource6dscp(dns_zone_t *zone, isc_dscp_t dscp) {
REQUIRE(DNS_ZONE_VALID(zone));
LOCK_ZONE(zone);
zone->xfrsource6dscp = dscp;
UNLOCK_ZONE(zone);
return (ISC_R_SUCCESS);
}
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_setaltxfrsource4dscp(dns_zone_t *zone, isc_dscp_t dscp) {
REQUIRE(DNS_ZONE_VALID(zone));
LOCK_ZONE(zone);
zone->altxfrsource4dscp = dscp;
UNLOCK_ZONE(zone);
return (ISC_R_SUCCESS);
}
isc_dscp_t
dns_zone_getaltxfrsource4dscp(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return (zone->altxfrsource4dscp);
}
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_setaltxfrsource6dscp(dns_zone_t *zone, isc_dscp_t dscp) {
REQUIRE(DNS_ZONE_VALID(zone));
LOCK_ZONE(zone);
zone->altxfrsource6dscp = dscp;
UNLOCK_ZONE(zone);
return (ISC_R_SUCCESS);
}
isc_dscp_t
dns_zone_getaltxfrsource6dscp(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return (zone->altxfrsource6dscp);
}
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_setnotifysrc4dscp(dns_zone_t *zone, isc_dscp_t dscp) {
REQUIRE(DNS_ZONE_VALID(zone));
LOCK_ZONE(zone);
zone->notifysrc4dscp = dscp;
UNLOCK_ZONE(zone);
return (ISC_R_SUCCESS);
}
isc_dscp_t
dns_zone_getnotifysrc4dscp(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return (zone->notifysrc4dscp);
}
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_keynames(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_addresskeylist(isc_sockaddr_t **addrsp, isc_dscp_t **dscpsp,
dns_name_t ***keynamesp, unsigned int *countp,
isc_mem_t *mctx)
{
unsigned int count;
isc_sockaddr_t *addrs;
isc_dscp_t *dscps;
dns_name_t **keynames;
REQUIRE(countp != NULL && addrsp != NULL && dscpsp != NULL &&
keynamesp != NULL);
count = *countp;
*countp = 0;
addrs = *addrsp;
*addrsp = NULL;
dscps = *dscpsp;
*dscpsp = NULL;
keynames = *keynamesp;
*keynamesp = NULL;
if (addrs != NULL)
isc_mem_put(mctx, addrs, count * sizeof(isc_sockaddr_t));
if (dscps != NULL)
isc_mem_put(mctx, dscps, count * sizeof(isc_dscp_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 *));
}
}
static isc_result_t
set_addrkeylist(unsigned int count,
const isc_sockaddr_t *addrs, isc_sockaddr_t **newaddrsp,
const isc_dscp_t *dscp, isc_dscp_t **newdscpp,
dns_name_t **names, dns_name_t ***newnamesp,
isc_mem_t *mctx)
{
isc_result_t result;
isc_sockaddr_t *newaddrs = NULL;
isc_dscp_t *newdscp = NULL;
dns_name_t **newnames = NULL;
unsigned int i;
REQUIRE(newaddrsp != NULL && *newaddrsp == NULL);
REQUIRE(newdscpp != NULL && *newdscpp == NULL);
REQUIRE(newnamesp != NULL && *newnamesp == NULL);
newaddrs = isc_mem_get(mctx, count * sizeof(*newaddrs));
if (newaddrs == NULL)
return (ISC_R_NOMEMORY);
memmove(newaddrs, addrs, count * sizeof(*newaddrs));
if (dscp != NULL) {
newdscp = isc_mem_get(mctx, count * sizeof(*newdscp));
if (newdscp == NULL) {
isc_mem_put(mctx, newaddrs, count * sizeof(*newaddrs));
return (ISC_R_NOMEMORY);
}
memmove(newdscp, dscp, count * sizeof(*newdscp));
} else
newdscp = NULL;
if (names != NULL) {
newnames = isc_mem_get(mctx, count * sizeof(*newnames));
if (newnames == NULL) {
if (newdscp != NULL)
isc_mem_put(mctx, newdscp,
count * sizeof(*newdscp));
isc_mem_put(mctx, newaddrs, count * sizeof(*newaddrs));
return (ISC_R_NOMEMORY);
}
for (i = 0; i < count; i++)
newnames[i] = NULL;
for (i = 0; i < count; i++) {
if (names[i] != NULL) {
newnames[i] = isc_mem_get(mctx,
sizeof(dns_name_t));
if (newnames[i] == NULL)
goto allocfail;
dns_name_init(newnames[i], NULL);
result = dns_name_dup(names[i], mctx,
newnames[i]);
if (result != ISC_R_SUCCESS) {
allocfail:
for (i = 0; i < count; i++)
if (newnames[i] != NULL)
dns_name_free(
newnames[i],
mctx);
isc_mem_put(mctx, newaddrs,
count * sizeof(*newaddrs));
isc_mem_put(mctx, newdscp,
count * sizeof(*newdscp));
isc_mem_put(mctx, newnames,
count * sizeof(*newnames));
return (ISC_R_NOMEMORY);
}
}
}
} else
newnames = NULL;
*newdscpp = newdscp;
*newaddrsp = newaddrs;
*newnamesp = newnames;
return (ISC_R_SUCCESS);
}
isc_result_t
dns_zone_setnotifysrc6dscp(dns_zone_t *zone, isc_dscp_t dscp) {
REQUIRE(DNS_ZONE_VALID(zone));
LOCK_ZONE(zone);
zone->notifysrc6dscp = dscp;
UNLOCK_ZONE(zone);
return (ISC_R_SUCCESS);
}
isc_dscp_t
dns_zone_getnotifysrc6dscp(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return (zone->notifysrc6dscp);
}
isc_result_t
dns_zone_setalsonotify(dns_zone_t *zone, const isc_sockaddr_t *notify,
uint32_t count)
{
return (dns_zone_setalsonotifydscpkeys(zone, notify, NULL, NULL,
count));
}
isc_result_t
dns_zone_setalsonotifywithkeys(dns_zone_t *zone, const isc_sockaddr_t *notify,
dns_name_t **keynames, uint32_t count)
{
return (dns_zone_setalsonotifydscpkeys(zone, notify, NULL, keynames,
count));
}
isc_result_t
dns_zone_setalsonotifydscpkeys(dns_zone_t *zone, const isc_sockaddr_t *notify,
const isc_dscp_t *dscps, dns_name_t **keynames,
uint32_t count)
{
isc_result_t result;
isc_sockaddr_t *newaddrs = NULL;
isc_dscp_t *newdscps = NULL;
dns_name_t **newnames = 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_keynames(zone->notifykeynames, keynames, count))
goto unlock;
clear_addresskeylist(&zone->notify, &zone->notifydscp,
&zone->notifykeynames, &zone->notifycnt,
zone->mctx);
if (count == 0)
goto unlock;
/*
* Set up the notify and notifykey lists
*/
result = set_addrkeylist(count, notify, &newaddrs, dscps, &newdscps,
keynames, &newnames, zone->mctx);
if (result != ISC_R_SUCCESS)
goto unlock;
/*
* Everything is ok so attach to the zone.
*/
zone->notify = newaddrs;
zone->notifydscp = newdscps;
zone->notifykeynames = newnames;
zone->notifycnt = count;
unlock:
UNLOCK_ZONE(zone);
return (ISC_R_SUCCESS);
}
isc_result_t
dns_zone_setmasters(dns_zone_t *zone, const isc_sockaddr_t *masters,
uint32_t count)
{
isc_result_t result;
result = dns_zone_setmasterswithkeys(zone, masters, NULL, count);
return (result);
}
isc_result_t
dns_zone_setmasterswithkeys(dns_zone_t *zone,
const isc_sockaddr_t *masters,
dns_name_t **keynames,
uint32_t count)
{
isc_result_t result = ISC_R_SUCCESS;
isc_sockaddr_t *newaddrs = NULL;
isc_dscp_t *newdscps = NULL;
dns_name_t **newnames = NULL;
bool *newok;
unsigned int i;
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(count == 0 || masters != NULL);
if (keynames != NULL) {
REQUIRE(count != 0);
}
LOCK_ZONE(zone);
/*
* The refresh code assumes that 'masters' wouldn't change under it.
* If it will change then kill off any current refresh in progress
* and update the masters info. If it won't change then we can just
* unlock and exit.
*/
if (count != zone->masterscnt ||
!same_addrs(zone->masters, masters, count) ||
!same_keynames(zone->masterkeynames, keynames, count)) {
if (zone->request != NULL)
dns_request_cancel(zone->request);
} else
goto unlock;
/*
* This needs to happen before clear_addresskeylist() sets
* zone->masterscnt to 0:
*/
if (zone->mastersok != NULL) {
isc_mem_put(zone->mctx, zone->mastersok,
zone->masterscnt * sizeof(bool));
zone->mastersok = NULL;
}
clear_addresskeylist(&zone->masters, &zone->masterdscps,
&zone->masterkeynames, &zone->masterscnt,
zone->mctx);
/*
* If count == 0, don't allocate any space for masters, mastersok or
* keynames so internally, those pointers are NULL if count == 0
*/
if (count == 0)
goto unlock;
/*
* mastersok must contain count elements
*/
newok = isc_mem_get(zone->mctx, count * sizeof(*newok));
if (newok == NULL) {
result = ISC_R_NOMEMORY;
isc_mem_put(zone->mctx, newaddrs, count * sizeof(*newaddrs));
goto unlock;
};
for (i = 0; i < count; i++)
newok[i] = false;
/*
* Now set up the masters and masterkey lists
*/
result = set_addrkeylist(count, masters, &newaddrs, NULL, &newdscps,
keynames, &newnames, zone->mctx);
INSIST(newdscps == NULL);
if (result != ISC_R_SUCCESS) {
isc_mem_put(zone->mctx, newok, count * sizeof(*newok));
goto unlock;
}
/*
* Everything is ok so attach to the zone.
*/
zone->curmaster = 0;
zone->mastersok = newok;
zone->masters = newaddrs;
zone->masterdscps = newdscps;
zone->masterkeynames = newnames;
zone->masterscnt = count;
DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOMASTERS);
unlock:
UNLOCK_ZONE(zone);
return (result);
}
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);
}
/*
* Co-ordinates 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 inline bool
was_dumping(dns_zone_t *zone) {
bool dumping;
REQUIRE(LOCKED_ZONE(zone));
dumping = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DUMPING);
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_DUMPING);
if (!dumping) {
DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NEEDDUMP);
isc_time_settoepoch(&zone->dumptime);
}
return (dumping);
}
/*%
* 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);
result = dns_dnssec_findzonekeys(db, ver, node, dns_db_origin(db),
directory, now, mctx, maxkeys, keys,
nkeys);
if (result == ISC_R_NOTFOUND)
result = ISC_R_SUCCESS;
failure:
if (node != NULL)
dns_db_detachnode(db, &node);
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];
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);
}
}
/*
* 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 *warn)
{
unsigned int i = 0;
bool have_ksk = false, have_zsk = false;
bool have_pksk = false, have_pzsk = false;
for (i = 0; i < nkeys; i++) {
if (rrsig_ptr->algorithm != dst_key_alg(keys[i]))
continue;
if (dst_key_isprivate(keys[i])) {
if (KSK(keys[i]))
have_ksk = have_pksk = true;
else
have_zsk = have_pzsk = true;
} else {
if (KSK(keys[i]))
have_ksk = true;
else
have_zsk = true;
}
}
if (have_zsk && have_ksk && !have_pzsk)
*warn = true;
/*
* It's okay to delete a signature if there is an active key
* with the same algorithm to replace it.
*/
if (have_pksk || have_pzsk)
return (true);
/*
* 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 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) {
bool warn = false, deleted = false;
if (delsig_ok(&rrsig, keys, nkeys, &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) {
/*
* 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 && !deleted) {
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;
}
/*
* RRSIG(DNSKEY) 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 RRSIG(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_rdatatype_t type, dns_diff_t *diff, dst_key_t **keys,
unsigned int nkeys, isc_mem_t *mctx, isc_stdtime_t inception,
isc_stdtime_t expire, bool check_ksk,
bool keyset_kskonly)
{
isc_result_t result;
dns_dbnode_t *node = NULL;
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;
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 key 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 {
have_nonksk = true;
}
both = have_ksk && have_nonksk;
if (both) {
break;
}
}
}
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));
}
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, stop;
uint32_t jitter, sigvalidityinterval;
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);
dns_db_attach(zone->db, &db);
ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
result = dns_db_newversion(db, &version);
if (result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_ERROR,
"zone_resigninc:dns_db_newversion -> %s",
dns_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",
dns_result_totext(result));
goto failure;
}
sigvalidityinterval = zone->sigvalidityinterval;
inception = now - 3600; /* Allow for clock skew. */
soaexpire = now + sigvalidityinterval;
/*
* 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(3600);
} else {
jitter = isc_random_uniform(1200);
}
expire = soaexpire - jitter - 1;
} else {
expire = 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",
dns_result_totext(result));
}
i = 0;
while (result == ISC_R_SUCCESS) {
resign = rdataset.resign - zone->sigresigninginterval;
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 || 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",
dns_result_totext(result));
break;
}
result = add_sigs(db, version, name, covers, zonediff.diff,
zone_keys, nkeys, zone->mctx, inception,
expire, check_ksk, keyset_kskonly);
if (result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_ERROR,
"zone_resigninc:add_sigs -> %s",
dns_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",
dns_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",
dns_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(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",
dns_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, dns_rdatatype_soa,
zonediff.diff, zone_keys, nkeys, zone->mctx,
inception, soaexpire, check_ksk, keyset_kskonly);
if (result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_ERROR,
"zone_resigninc:add_sigs -> %s",
dns_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);
if (result == ISC_R_SUCCESS) {
set_resigntime(zone);
LOCK_ZONE(zone);
zone_needdump(zone, DNS_DUMP_DELAY);
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NEEDNOTIFY);
UNLOCK_ZONE(zone);
} else {
/*
* Something failed. Retry in 5 minutes.
*/
isc_interval_t ival;
isc_interval_set(&ival, 300, 0);
isc_time_nowplusinterval(&zone->resigntime, &ival);
}
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, &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_key(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;
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);
}
dns_rdata_reset(&rdata);
}
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, &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_name_t *name, dns_dbnode_t *node,
dns_dbversion_t *version, bool build_nsec3,
bool build_nsec, dst_key_t *key,
isc_stdtime_t inception, isc_stdtime_t expire,
unsigned int minimum, bool is_ksk,
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;
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, &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, minimum,
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, minimum,
is_bottom_of_zone, diff));
/* Count a NSEC generation as a signature generation. */
(*signatures)--;
}
}
result = dns_rdatasetiter_first(iterator);
while (result == ISC_R_SUCCESS) {
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_ksk) {
goto next_rdataset;
}
if (seen_ns && !seen_soa &&
rdataset.type != dns_rdatatype_ds &&
rdataset.type != dns_rdatatype_nsec)
{
goto next_rdataset;
}
if (signed_with_key(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);
(*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 minimum, 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, minimum, 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 minimum, 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,
minimum, false, diff));
CHECK(updatesecure(signing->db, version, origin, minimum,
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, &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, &parambuf, 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",
dns_result_totext(result));
return (result);
}
result = add_sigs(db, version, &tuple->name,
tuple->rdata.type, zonediff->diff,
zone_keys, nkeys, zone->mctx, inception,
exp, check_ksk, keyset_kskonly);
if (result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_ERROR,
"dns__zone_updatesigs:add_sigs -> %s",
dns_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;
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, &param_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",
dns_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",
dns_result_totext(result));
goto failure;
}
sigvalidityinterval = dns_zone_getsigvalidityinterval(zone);
inception = now - 3600; /* Allow for clock skew. */
soaexpire = now + sigvalidityinterval;
/*
* 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(3600);
} 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) {
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, NULL);
delegation = true;
goto next_addnode;
}
}
/*
* Check to see if this is a bottom of zone node.
*/
result = dns_db_allrdatasets(db, node, version, 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->minimum, unsecure,
&nsec3_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain:"
"dns_nsec3_addnsec3 -> %s",
dns_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,
&param_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,
&param_diff));
nsec3chain->delete_nsec = true;
goto same_addchain;
}
CHECK(fixup_nsec3param(db, version, nsec3chain,
false, privatetype,
&param_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",
dns_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) {
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",
dns_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);
delegation = false;
if (!buildnsecchain) {
/*
* Delete the NSEC3PARAM record matching this chain.
*/
if (first) {
result = fixup_nsec3param(db, version,
nsec3chain,
true, privatetype,
&param_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"zone_nsec3chain:"
"fixup_nsec3param -> %s",
dns_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",
dns_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, NULL);
delegation = true;
goto next_removenode;
}
}
/*
* Check to see if this is a bottom of zone node.
*/
result = dns_db_allrdatasets(db, node, version, 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->minimum,
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) {
LOCK_ZONE(zone);
ISC_LIST_UNLINK(zone->nsec3chain, nsec3chain,
link);
UNLOCK_ZONE(zone);
ISC_LIST_APPEND(cleanup, nsec3chain, link);
dns_dbiterator_pause(nsec3chain->dbiterator);
result = fixup_nsec3param(db, version,
nsec3chain, false,
privatetype,
&param_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"zone_nsec3chain:"
"fixup_nsec3param -> %s",
dns_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",
dns_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, &iterator);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"zone_nsec3chain:dns_db_allrdatasets -> %s",
dns_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->minimum, true,
&nsec_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"zone_nsec3chain:updatesecure -> %s",
dns_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->minimum, false,
&nsec3_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"zone_nsec3chain:"
"dns_nsec3_addnsec3s -> %s",
dns_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",
dns_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(&param_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",
dns_result_totext(result));
goto failure;
}
if (updatensec) {
result = updatesecure(db, version, &zone->origin,
zone->minimum, false, &nsec_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"zone_nsec3chain:updatesecure -> %s",
dns_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",
dns_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",
dns_result_totext(result));
goto failure;
}
result = update_soa_serial(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",
dns_result_totext(result));
goto failure;
}
result = add_sigs(db, version, &zone->origin, dns_rdatatype_soa,
zonediff.diff, zone_keys, nkeys, zone->mctx,
inception, soaexpire, check_ksk, keyset_kskonly);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"zone_nsec3chain:add_sigs -> %s",
dns_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);
}
set_resigntime(zone);
failure:
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR, "zone_nsec3chain: %s",
dns_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(&param_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, &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);
}
/*
* 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_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;
bool with_ksk, with_zsk;
bool commit = false;
bool is_bottom_of_zone;
bool build_nsec = false;
bool build_nsec3 = 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",
dns_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",
dns_result_totext(result));
goto cleanup;
}
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;
check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
keyset_kskonly = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_DNSKEYKSKONLY);
/* Determine which type of chain to build */
CHECK(dns_private_chains(db, version, zone->privatetype,
&build_nsec, &build_nsec3));
/* If neither chain is found, default to NSEC */
if (!build_nsec && !build_nsec3) {
build_nsec = true;
}
while (signing != NULL && nodes-- > 0 && signatures > 0) {
bool has_alg = false;
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
* 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)
{
if (KSK(zone_keys[i]))
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, NULL);
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 keys 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 {
have_nonksk = true;
}
both = have_ksk && have_nonksk;
if (both) {
break;
}
}
}
if (both || REVOKE(zone_keys[i])) {
is_ksk = KSK(zone_keys[i]);
} else {
is_ksk = false;
}
/*
* 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_ksk && with_zsk) {
continue;
}
if (signing->deleteit && is_ksk && with_ksk) {
continue;
}
CHECK(sign_a_node(db, name, node, version, build_nsec3,
build_nsec, zone_keys[i], inception,
expire, zone->minimum, is_ksk,
(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_ksk) {
with_zsk = true;
}
if (KSK(zone_keys[i])) {
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->minimum,
false,
&post_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"updatesecure -> %s",
dns_result_totext(result));
goto cleanup;
}
}
result = updatesignwithkey(zone, signing,
version,
build_nsec3,
zone->minimum,
&post_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"updatesignwithkey -> %s",
dns_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",
dns_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",
dns_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",
dns_result_totext(result));
goto cleanup;
}
result = update_soa_serial(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",
dns_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, dns_rdatatype_soa,
zonediff.diff, zone_keys, nkeys, zone->mctx,
inception, soaexpire, check_ksk, keyset_kskonly);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR, "zone_sign:add_sigs -> %s",
dns_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);
}
set_resigntime(zone);
if (commit) {
LOCK_ZONE(zone);
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",
dns_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);
}
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);
}
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:
INSIST(0);
ISC_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 inline 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",
dns_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;
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;
isc_stdtime_t now;
int pending = 0;
bool secure = false, initial = false;
bool free_needed;
UNUSED(task);
INSIST(event != NULL && event->ev_type == DNS_EVENT_FETCHDONE);
INSIST(event->ev_arg != NULL);
kfetch = event->ev_arg;
zone = kfetch->zone;
isc_mem_attach(zone->mctx, &mctx);
keyname = dns_fixedname_name(&kfetch->name);
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, dns_result_totext(eresult));
/* Fetch failed */
if (eresult != ISC_R_SUCCESS ||
!dns_rdataset_isassociated(&kfetch->dnskeyset))
{
dnssec_log(zone, ISC_LOG_WARNING,
"Unable to fetch DNSKEY set '%s': %s",
namebuf, dns_result_totext(eresult));
CHECK(minimal_update(kfetch, ver, &diff));
goto done;
}
/* No RRSIGs found */
if (!dns_rdataset_isassociated(&kfetch->dnskeysigset)) {
dnssec_log(zone, ISC_LOG_WARNING,
"No DNSKEY RRSIGs found for '%s': %s",
namebuf, dns_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.
*/
kfetch->dnskeyset.trust = kfetch->dnskeysigset.trust = dns_trust_none;
/*
* Validate the dnskeyset against the current trusted keys.
*/
for (result = dns_rdataset_first(&kfetch->dnskeysigset);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(&kfetch->dnskeysigset))
{
dns_keynode_t *keynode = NULL;
dns_rdata_reset(&sigrr);
dns_rdataset_current(&kfetch->dnskeysigset, &sigrr);
result = dns_rdata_tostruct(&sigrr, &sig, NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
result = dns_keytable_find(secroots, keyname, &keynode);
while (result == ISC_R_SUCCESS) {
dns_keynode_t *nextnode = NULL;
dns_fixedname_t fixed;
dns_fixedname_init(&fixed);
dstkey = dns_keynode_key(keynode);
if (dstkey == NULL) {
/* fail_secure() was called */
break;
}
if (dst_key_alg(dstkey) == sig.algorithm &&
dst_key_id(dstkey) == sig.keyid)
{
result = dns_dnssec_verify(keyname,
&kfetch->dnskeyset,
dstkey, false,
0,
zone->view->mctx,
&sigrr,
dns_fixedname_name(
&fixed));
dnssec_log(zone, ISC_LOG_DEBUG(3),
"Verifying DNSKEY set for zone "
"'%s' using key %d/%d: %s",
namebuf, sig.keyid, sig.algorithm,
dns_result_totext(result));
if (result == ISC_R_SUCCESS) {
kfetch->dnskeyset.trust =
dns_trust_secure;
kfetch->dnskeysigset.trust =
dns_trust_secure;
secure = true;
initial = dns_keynode_initial(keynode);
dns_keynode_trust(keynode);
break;
}
}
result = dns_keytable_nextkeynode(secroots,
keynode, &nextnode);
dns_keytable_detachkeynode(secroots, &keynode);
keynode = nextnode;
}
if (keynode != NULL) {
dns_keytable_detachkeynode(secroots, &keynode);
}
if (secure) {
break;
}
}
/*
* 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(&kfetch->keydataset);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(&kfetch->keydataset))
{
dns_keytag_t keytag;
dns_rdata_reset(&keydatarr);
dns_rdataset_current(&kfetch->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, dns_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(&kfetch->dnskeyset, &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 "
"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",
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 "
"missing: 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(&kfetch->dnskeyset);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(&kfetch->dnskeyset))
{
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(&kfetch->dnskeyset, &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, dns_result_totext(result));
continue;
}
RUNTIME_CHECK(result == ISC_R_SUCCESS);
revoked = ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0);
if (matchkey(&kfetch->keydataset, &dnskeyrr)) {
dns_rdata_reset(&keydatarr);
dns_rdataset_current(&kfetch->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, mctx);
/* 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, mctx);
}
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(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);
INSIST(zone->irefs > 0);
zone->irefs--;
kfetch->zone = NULL;
if (dns_rdataset_isassociated(&kfetch->keydataset)) {
dns_rdataset_disassociate(&kfetch->keydataset);
}
if (dns_rdataset_isassociated(&kfetch->dnskeyset)) {
dns_rdataset_disassociate(&kfetch->dnskeyset);
}
if (dns_rdataset_isassociated(&kfetch->dnskeysigset)) {
dns_rdataset_disassociate(&kfetch->dnskeysigset);
}
dns_name_free(keyname, mctx);
isc_mem_put(mctx, kfetch, sizeof(dns_keyfetch_t));
isc_mem_detach(&mctx);
if (secroots != NULL) {
dns_keytable_detach(&secroots);
}
free_needed = exit_check(zone);
UNLOCK_ZONE(zone);
if (free_needed) {
zone_free(zone);
}
INSIST(ver == NULL);
}
/*
* Refresh the data in the key zone. Initiate a fetch to get new DNSKEY
* records from the zone apex.
*/
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, fetch_err = 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;
dns_keyfetch_t *kfetch;
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) {
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;
}
set_refreshkeytimer(zone, &kd, now, false);
timerset = true;
}
if (timer > now) {
continue;
}
kfetch = isc_mem_get(zone->mctx, sizeof(dns_keyfetch_t));
if (kfetch == NULL) {
fetch_err = true;
goto failure;
}
zone->refreshkeycount++;
kfetch->zone = zone;
zone->irefs++;
INSIST(zone->irefs != 0);
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);
kfetch->db = NULL;
dns_db_attach(db, &kfetch->db);
kfetch->fetch = NULL;
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);
}
/*
* 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.
*/
#ifdef ENABLE_AFL
if (dns_fuzzing_resolver == false) {
#endif
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);
#ifdef ENABLE_AFL
} else {
result = ISC_R_FAILURE;
}
#endif
if (result == ISC_R_SUCCESS) {
fetching = true;
} else {
zone->refreshkeycount--;
zone->irefs--;
dns_db_detach(&kfetch->db);
dns_rdataset_disassociate(&kfetch->keydataset);
dns_name_free(kname, zone->mctx);
isc_mem_put(zone->mctx, kfetch, sizeof(dns_keyfetch_t));
dnssec_log(zone, ISC_LOG_WARNING,
"Failed to create fetch for DNSKEY update");
fetch_err = true;
}
}
if (!ISC_LIST_EMPTY(diff.tuples)) {
CHECK(update_soa_serial(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 (fetch_err) {
/*
* Error during a key fetch; retry in an hour.
*/
isc_time_t timenow, timethen;
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);
} else 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 dumping, load_pending;
REQUIRE(DNS_ZONE_VALID(zone));
ENTER;
/*
* Are we pending load/reload?
*/
LOCK_ZONE(zone);
load_pending = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADPENDING);
UNLOCK_ZONE(zone);
if (load_pending) {
return;
}
/*
* Configuring the view of this zone may have
* failed, for example because the config file
* had a syntax error. In that case, the view
* adb or resolver will be NULL, and we had better not try
* to do further maintenance on it.
*/
if (zone->view == NULL || zone->view->adb == NULL)
return;
TIME_NOW(&now);
/*
* Expire check.
*/
switch (zone->type) {
case dns_zone_redirect:
if (zone->masters == NULL)
break;
/* FALLTHROUGH */
case dns_zone_slave:
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->masters == NULL)
break;
/* FALLTHROUGH */
case dns_zone_slave:
case dns_zone_mirror:
case dns_zone_stub:
if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH) &&
isc_time_compare(&now, &zone->refreshtime) >= 0)
dns_zone_refresh(zone);
break;
default:
break;
}
/*
* Slaves send notifies before backing up to disk, masters after.
*/
if ((zone->type == dns_zone_slave || 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)
zone_notify(zone, &now);
/*
* Do we need to consolidate the backing store?
*/
switch (zone->type) {
case dns_zone_master:
case dns_zone_slave:
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",
dns_result_totext(result));
}
break;
default:
break;
}
/*
* Master/redirect zones send notifies now, if needed
*/
switch (zone->type) {
case dns_zone_master:
case dns_zone_redirect:
if ((DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDNOTIFY) ||
DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDSTARTUPNOTIFY))&&
isc_time_compare(&now, &zone->notifytime) >= 0)
zone_notify(zone, &now);
default:
break;
}
/*
* Do we need to refresh keys?
*/
switch (zone->type) {
case dns_zone_key:
if (isc_time_compare(&now, &zone->refreshkeytime) >= 0) {
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED) &&
!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESHING)) {
zone_refreshkeys(zone);
}
}
break;
case dns_zone_master:
if (!isc_time_isepoch(&zone->refreshkeytime) &&
isc_time_compare(&now, &zone->refreshkeytime) >= 0 &&
zone->rss_event == NULL)
zone_rekey(zone);
default:
break;
}
switch (zone->type) {
case dns_zone_master:
case dns_zone_redirect:
case dns_zone_slave:
/*
* Do we need to sign/resign some RRsets?
*/
if (zone->rss_event != NULL)
break;
if (!isc_time_isepoch(&zone->signingtime) &&
isc_time_compare(&now, &zone->signingtime) >= 0) {
zone_sign(zone);
}
else if (!isc_time_isepoch(&zone->resigntime) &&
isc_time_compare(&now, &zone->resigntime) >= 0) {
zone_resigninc(zone);
}
else if (!isc_time_isepoch(&zone->nsec3chaintime) &&
isc_time_compare(&now, &zone->nsec3chaintime) >= 0) {
zone_nsec3chain(zone);
}
/*
* Do we need to issue a key expiry warning?
*/
if (!isc_time_isepoch(&zone->keywarntime) &&
isc_time_compare(&now, &zone->keywarntime) >= 0)
set_key_expiry_warning(zone, zone->key_expiry,
isc_time_seconds(&now));
break;
default:
break;
}
zone_settimer(zone, &now);
}
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_master) {
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, &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 (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) {
/*
* '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);
zone_unload(zone);
}
void
dns_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));
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.
*/
LOCK_ZONE(zone);
oldflags = zone->flags;
if (zone->masterscnt == 0) {
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOMASTERS);
if ((oldflags & DNS_ZONEFLG_NOMASTERS) == 0)
dns_zone_log(zone, ISC_LOG_ERROR,
"cannot refresh: no masters");
goto unlock;
}
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)
goto unlock;
/*
* 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",
dns_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->curmaster = 0;
for (j = 0; j < zone->masterscnt; j++)
zone->mastersok[j] = false;
/* initiate soa query */
queue_soa_query(zone);
unlock:
UNLOCK_ZONE(zone);
}
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;
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;
}
}
zone_debuglog(zone, "zone_journal_compact", 1,
"target journal size %d", journalsize);
result = dns_journal_compact(zone->mctx, zone->journal,
serial, 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",
dns_result_totext(result));
break;
default:
dns_zone_log(zone, ISC_LOG_ERROR,
"dns_journal_compact failed: %s",
dns_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) {
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 (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
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_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(&notify->ns) &&
dns_name_equal(name, &notify->ns))
goto requeue;
if (addr != NULL && isc_sockaddr_equal(addr, &notify->dst) &&
notify->key == key)
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,
&notify->event);
if (result != ISC_R_SUCCESS) {
isc_event_free(&notify->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(&notify->zone);
else
dns_zone_idetach(&notify->zone);
}
if (notify->find != NULL)
dns_adb_destroyfind(&notify->find);
if (notify->request != NULL)
dns_request_destroy(&notify->request);
if (dns_name_dynamic(&notify->ns))
dns_name_free(&notify->ns, notify->mctx);
if (notify->key != NULL)
dns_tsigkey_detach(&notify->key);
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));
if (notify == NULL)
return (ISC_R_NOMEMORY);
notify->mctx = NULL;
isc_mem_attach(mctx, &notify->mctx);
notify->flags = flags;
notify->zone = NULL;
notify->find = NULL;
notify->request = NULL;
notify->key = NULL;
notify->event = NULL;
isc_sockaddr_any(&notify->dst);
dns_name_init(&notify->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(&notify->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,
&notify->ns, dns_rootname, 0,
options, 0, NULL,
notify->zone->view->dstport,
0, NULL, &notify->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 (e == NULL)
return (ISC_R_NOMEMORY);
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;
bool have_notifydscp = false;
isc_dscp_t dscp = -1;
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(&notify->dst) == PF_INET6 &&
IN6_IS_ADDR_V4MAPPED(&notify->dst.type.sin6.sin6_addr)) {
isc_sockaddr_format(&notify->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(&notify->dst, addrbuf, sizeof(addrbuf));
if (notify->key != NULL) {
/* Transfer ownership of key */
key = notify->key;
notify->key = NULL;
} else {
isc_netaddr_fromsockaddr(&dstip, &notify->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;
}
}
/* XXX: should we log the tsig key too? */
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;
dns_peer_getnotifydscp(peer, &dscp);
if (dscp != -1)
have_notifydscp = true;
result = dns_peer_getforcetcp(peer, &usetcp);
if (result == ISC_R_SUCCESS && usetcp)
options |= DNS_FETCHOPT_TCP;
}
}
switch (isc_sockaddr_pf(&notify->dst)) {
case PF_INET:
if (!have_notifysource)
src = notify->zone->notifysrc4;
if (!have_notifydscp)
dscp = notify->zone->notifysrc4dscp;
break;
case PF_INET6:
if (!have_notifysource)
src = notify->zone->notifysrc6;
if (!have_notifydscp)
dscp = notify->zone->notifysrc6dscp;
break;
default:
result = ISC_R_NOTIMPLEMENTED;
goto cleanup_key;
}
timeout = 15;
if (DNS_ZONE_FLAG(notify->zone, DNS_ZONEFLG_DIALNOTIFY))
timeout = 30;
result = dns_request_createvia(notify->zone->view->requestmgr,
message, &src, &notify->dst, dscp,
options, key, timeout * 3, timeout,
0, notify->zone->task, notify_done,
notify, &notify->request);
if (result == ISC_R_SUCCESS) {
if (isc_sockaddr_pf(&notify->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_destroy(&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))
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 master;
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_master)
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 master server's name.
*/
dns_name_init(&master, 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);
result = dns_name_dup(&soa.origin, zone->mctx, &master);
serial = soa.serial;
dns_rdataset_disassociate(&soardset);
if (result != ISC_R_SUCCESS)
goto cleanup3;
/*
* Enqueue notify requests for 'also-notify' servers.
*/
LOCK_ZONE(zone);
for (i = 0; i < zone->notifycnt; i++) {
dns_tsigkey_t *key = NULL;
dns_notify_t *notify = NULL;
if ((zone->notifykeynames != NULL) &&
(zone->notifykeynames[i] != NULL)) {
dns_view_t *view = dns_zone_getview(zone);
dns_name_t *keyname = zone->notifykeynames[i];
(void)dns_view_gettsig(view, keyname, &key);
}
dst = zone->notify[i];
if (notify_isqueued(zone, flags, NULL, &dst, key)) {
if (key != NULL)
dns_tsigkey_detach(&key);
continue;
}
result = notify_create(zone->mctx, flags, &notify);
if (result != ISC_R_SUCCESS) {
if (key != NULL)
dns_tsigkey_detach(&key);
continue;
}
zone_iattach(zone, &notify->zone);
notify->dst = dst;
INSIST(notify->key == NULL);
if (key != NULL) {
notify->key = key;
key = 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 master server unless explicitly
* configured to do so.
*/
if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_NOTIFYTOSOA) &&
dns_name_compare(&master, &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);
UNLOCK_ZONE(zone);
if (isqueued) {
result = dns_rdataset_next(&nsrdset);
continue;
}
result = notify_create(zone->mctx, flags, &notify);
if (result != ISC_R_SUCCESS)
continue;
dns_zone_iattach(zone, &notify->zone);
result = dns_name_dup(&ns.name, zone->mctx, &notify->ns);
if (result != ISC_R_SUCCESS) {
LOCK_ZONE(zone);
notify_destroy(notify, true);
UNLOCK_ZONE(zone);
continue;
}
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(&master))
dns_name_free(&master, zone->mctx);
cleanup2:
dns_db_detachnode(zonedb, &node);
cleanup1:
dns_db_closeversion(zonedb, &version, false);
dns_db_detach(&zonedb);
}
/***
*** Private
***/
static inline isc_result_t
save_nsrrset(dns_message_t *message, dns_name_t *name,
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;
/*
* 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 fail;
/*
* Add NS rdataset.
*/
result = dns_db_findnode(db, name, true, &node);
if (result != ISC_R_SUCCESS)
goto fail;
result = dns_db_addrdataset(db, node, version, 0,
nsrdataset, 0, NULL);
dns_db_detachnode(db, &node);
if (result != ISC_R_SUCCESS)
goto fail;
/*
* 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) {
result = dns_db_findnode(db, &ns.name,
true, &node);
if (result != ISC_R_SUCCESS)
goto fail;
result = dns_db_addrdataset(db, node, version, 0,
rdataset, 0, NULL);
dns_db_detachnode(db, &node);
if (result != ISC_R_SUCCESS)
goto fail;
}
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) {
result = dns_db_findnode(db, &ns.name,
true, &node);
if (result != ISC_R_SUCCESS)
goto fail;
result = dns_db_addrdataset(db, node, version, 0,
rdataset, 0, NULL);
dns_db_detachnode(db, &node);
if (result != ISC_R_SUCCESS)
goto fail;
}
}
if (result != ISC_R_NOMORE)
goto fail;
return (ISC_R_SUCCESS);
fail:
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 master[ISC_SOCKADDR_FORMATSIZE];
char source[ISC_SOCKADDR_FORMATSIZE];
uint32_t nscnt, cnamecnt, refresh, retry, expire;
isc_result_t result;
isc_time_t now;
bool exiting = false;
isc_interval_t i;
unsigned int j, soacount;
stub = revent->ev_arg;
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)) {
zone_debuglog(zone, me, 1, "exiting");
exiting = true;
goto next_master;
}
isc_sockaddr_format(&zone->masteraddr, master, sizeof(master));
isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
if (revent->result != ISC_R_SUCCESS) {
if (revent->result == ISC_R_TIMEDOUT &&
!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 master %s (source %s)",
master, source);
goto same_master;
}
dns_zonemgr_unreachableadd(zone->zmgr, &zone->masteraddr,
&zone->sourceaddr, &now);
dns_zone_log(zone, ISC_LOG_INFO,
"could not refresh stub from master %s"
" (source %s): %s", master, source,
dns_result_totext(revent->result));
goto next_master;
}
result = dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
if (result != ISC_R_SUCCESS)
goto next_master;
result = dns_request_getresponse(revent->request, msg, 0);
if (result != ISC_R_SUCCESS)
goto next_master;
/*
* 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)) {
dns_zone_log(zone, ISC_LOG_DEBUG(1),
"refreshing stub: rcode (%.*s) retrying "
"without EDNS master %s (source %s)",
(int)rb.used, rcode, master, source);
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
goto same_master;
}
dns_zone_log(zone, ISC_LOG_INFO,
"refreshing stub: "
"unexpected rcode (%.*s) from %s (source %s)",
(int)rb.used, rcode, master, source);
goto next_master;
}
/*
* 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 master %s (source %s)",
master, source);
goto next_master;
}
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC);
goto same_master;
}
/*
* If non-auth log and next master.
*/
if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) {
dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: "
"non-authoritative answer from "
"master %s (source %s)", master, source);
goto next_master;
}
/*
* 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 master %s (source %s)", master, source);
goto next_master;
}
if (nscnt == 0) {
dns_zone_log(zone, ISC_LOG_INFO,
"refreshing stub: no NS records in response "
"from master %s (source %s)", master, source);
goto next_master;
}
/*
* Save answer.
*/
result = save_nsrrset(msg, &zone->origin, stub->db, stub->version);
if (result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_INFO,
"refreshing stub: unable to save NS records "
"from master %s (source %s)", master, source);
goto next_master;
}
/*
* 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,
&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_message_destroy(&msg);
isc_event_free(&event);
dns_request_destroy(&zone->request);
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);
goto free_stub;
next_master:
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_destroy(&msg);
isc_event_free(&event);
dns_request_destroy(&zone->request);
/*
* Skip to next failed / untried master.
*/
do {
zone->curmaster++;
} while (zone->curmaster < zone->masterscnt &&
zone->mastersok[zone->curmaster]);
DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS);
if (exiting || zone->curmaster >= zone->masterscnt) {
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 masters?
*/
for (j = 0; j < zone->masterscnt; j++)
if (zone->mastersok[j] == false) {
done = false;
break;
}
} else
done = true;
if (!done) {
zone->curmaster = 0;
/*
* Find the next failed master.
*/
while (zone->curmaster < zone->masterscnt &&
zone->mastersok[zone->curmaster])
zone->curmaster++;
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_master:
if (msg != NULL)
dns_message_destroy(&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_NEEDDUMP))
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, dns_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 master[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)) {
isc_event_free(&event);
dns_request_destroy(&zone->request);
goto detach;
}
/*
* if timeout log and next master;
*/
isc_sockaddr_format(&zone->masteraddr, master, sizeof(master));
isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
if (revent->result != ISC_R_SUCCESS) {
if (revent->result == ISC_R_TIMEDOUT &&
!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 "
"master %s (source %s)", master, source);
goto same_master;
}
if (revent->result == ISC_R_TIMEDOUT &&
!dns_request_usedtcp(revent->request)) {
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: retry limit for "
"master %s exceeded (source %s)",
master, source);
/* Try with slave with TCP. */
if ((zone->type == dns_zone_slave ||
zone->type == dns_zone_mirror ||
zone->type == dns_zone_redirect) &&
DNS_ZONE_OPTION(zone, DNS_ZONEOPT_TRYTCPREFRESH))
{
if (!dns_zonemgr_unreachable(zone->zmgr,
&zone->masteraddr,
&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 master %s (source %s) is "
"unreachable (cached)",
master, source);
}
} else
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: failure trying master "
"%s (source %s): %s", master, source,
dns_result_totext(revent->result));
goto next_master;
}
result = dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
if (result != ISC_R_SUCCESS)
goto next_master;
result = dns_request_getresponse(revent->request, msg, 0);
if (result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: failure trying master "
"%s (source %s): %s", master, source,
dns_result_totext(result));
goto next_master;
}
/*
* 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)) {
dns_zone_log(zone, ISC_LOG_DEBUG(1),
"refresh: rcode (%.*s) retrying without "
"EDNS master %s (source %s)",
(int)rb.used, rcode, master, source);
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
goto same_master;
}
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 master %s (source %s)",
(int)rb.used, rcode, master, source);
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_NOEDNS);
goto same_master;
}
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: unexpected rcode (%.*s) from "
"master %s (source %s)", (int)rb.used, rcode,
master, source);
/*
* Perhaps AXFR/IXFR is allowed even if SOA queries aren't.
*/
if (msg->rcode == dns_rcode_refused &&
(zone->type == dns_zone_slave ||
zone->type == dns_zone_mirror ||
zone->type == dns_zone_redirect))
{
goto tcp_transfer;
}
goto next_master;
}
/*
* If truncated punt to zone transfer which will query again.
*/
if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) {
if (zone->type == dns_zone_slave ||
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 master %s (source %s)",
master, 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 master %s (source %s)",
master, source);
goto next_master;
}
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEVC);
goto same_master;
}
}
/*
* if non-auth log and next master;
*/
if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) {
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: non-authoritative answer from "
"master %s (source %s)", master, source);
goto next_master;
}
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 master %s (source %s)", master, source);
goto next_master;
}
/*
* if referral log and next master;
*/
if (soacnt == 0 && soacount == 0 && nscount != 0) {
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: referral response "
"from master %s (source %s)", master, source);
goto next_master;
}
/*
* if nodata log and next master;
*/
if (soacnt == 0 && (nscount == 0 || soacount != 0)) {
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: NODATA response "
"from master %s (source %s)", master, source);
goto next_master;
}
/*
* Only one soa at top of zone.
*/
if (soacnt != 1) {
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: answer SOA count (%d) != 1 "
"from master %s (source %s)",
soacnt, master, source);
goto next_master;
}
/*
* 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 master %s (source %s)", master, source);
goto next_master;
}
result = dns_rdataset_first(rdataset);
if (result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: dns_rdataset_first() failed");
goto next_master;
}
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,
&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->masteraddr,
&zone->sourceaddr, &now))
{
dns_zone_log(zone, ISC_LOG_INFO,
"refresh: skipping %s as master %s "
"(source %s) is unreachable (cached)",
(zone->type == dns_zone_slave ||
zone->type == dns_zone_mirror ||
zone->type == dns_zone_redirect) ?
"zone transfer" : "NS query",
master, source);
goto next_master;
}
tcp_transfer:
isc_event_free(&event);
dns_request_destroy(&zone->request);
if (zone->type == dns_zone_slave ||
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_destroy(&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->mastersok[zone->curmaster] = true;
goto next_master;
} else {
if (!DNS_ZONE_OPTION(zone, DNS_ZONEOPT_MULTIMASTER))
dns_zone_log(zone, ISC_LOG_INFO, "serial number (%u) "
"received from master %s < ours (%u)",
soa.serial, master, oldserial);
else
zone_debuglog(zone, me, 1, "ahead");
zone->mastersok[zone->curmaster] = true;
goto next_master;
}
if (msg != NULL)
dns_message_destroy(&msg);
goto detach;
next_master:
if (msg != NULL)
dns_message_destroy(&msg);
isc_event_free(&event);
dns_request_destroy(&zone->request);
/*
* Skip to next failed / untried master.
*/
do {
zone->curmaster++;
} while (zone->curmaster < zone->masterscnt &&
zone->mastersok[zone->curmaster]);
DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_NOEDNS);
if (zone->curmaster >= zone->masterscnt) {
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 masters?
*/
for (j = 0; j < zone->masterscnt; j++)
if (zone->mastersok[j] == false) {
done = false;
break;
}
} else
done = true;
if (!done) {
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_USEALTXFRSRC);
zone->curmaster = 0;
/*
* Find the next failed master.
*/
while (zone->curmaster < zone->masterscnt &&
zone->mastersok[zone->curmaster])
zone->curmaster++;
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;
same_master:
if (msg != NULL)
dns_message_destroy(&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));
if (e == NULL) {
cancel_refresh(zone);
return;
}
/*
* 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 inline isc_result_t
create_query(dns_zone_t *zone, dns_rdatatype_t rdtype,
dns_message_t **messagep)
{
dns_message_t *message = NULL;
dns_name_t *qname = NULL;
dns_rdataset_t *qrdataset = NULL;
isc_result_t result;
result = dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER,
&message);
if (result != ISC_R_SUCCESS)
goto cleanup;
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_init(qname, NULL);
dns_name_clone(&zone->origin, 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);
if (message != NULL)
dns_message_destroy(&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));
}
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 masterip;
dns_tsigkey_t *key = NULL;
uint32_t options;
bool cancel = true;
int timeout;
bool have_xfrsource, have_xfrdscp, reqnsid, reqexpire;
uint16_t udpsize = SEND_BUFFER_SIZE;
isc_dscp_t dscp = -1;
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:
result = create_query(zone, dns_rdatatype_soa, &message);
if (result != ISC_R_SUCCESS)
goto cleanup;
INSIST(zone->masterscnt > 0);
INSIST(zone->curmaster < zone->masterscnt);
zone->masteraddr = zone->masters[zone->curmaster];
isc_netaddr_fromsockaddr(&masterip, &zone->masteraddr);
/*
* First, look for a tsig key in the master statement, then
* try for a server key.
*/
if ((zone->masterkeynames != NULL) &&
(zone->masterkeynames[zone->curmaster] != NULL)) {
dns_view_t *view = dns_zone_getview(zone);
dns_name_t *keyname = zone->masterkeynames[zone->curmaster];
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_master;
}
}
if (key == NULL) {
result = dns_view_getpeertsig(zone->view, &masterip, &key);
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
char addrbuf[ISC_NETADDR_FORMATSIZE];
isc_netaddr_format(&masterip, addrbuf, sizeof(addrbuf));
dns_zone_log(zone, ISC_LOG_ERROR,
"unable to find TSIG key for %s", addrbuf);
goto skip_master;
}
}
options = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEVC) ?
DNS_REQUESTOPT_TCP : 0;
have_xfrsource = have_xfrdscp = false;
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,
&masterip, &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;
(void)dns_peer_gettransferdscp(peer, &dscp);
if (dscp != -1)
have_xfrdscp = 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->masteraddr)) {
case PF_INET:
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
if (isc_sockaddr_equal(&zone->altxfrsource4,
&zone->xfrsource4))
goto skip_master;
zone->sourceaddr = zone->altxfrsource4;
if (!have_xfrdscp)
dscp = zone->altxfrsource4dscp;
} else if (!have_xfrsource) {
zone->sourceaddr = zone->xfrsource4;
if (!have_xfrdscp)
dscp = zone->xfrsource4dscp;
}
break;
case PF_INET6:
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
if (isc_sockaddr_equal(&zone->altxfrsource6,
&zone->xfrsource6))
goto skip_master;
zone->sourceaddr = zone->altxfrsource6;
if (!have_xfrdscp)
dscp = zone->altxfrsource6dscp;
} else if (!have_xfrsource) {
zone->sourceaddr = zone->xfrsource6;
if (!have_xfrdscp)
dscp = zone->xfrsource6dscp;
}
break;
default:
result = ISC_R_NOTIMPLEMENTED;
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",
dns_result_totext(result));
}
zone_iattach(zone, &dummy);
timeout = 15;
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH))
timeout = 30;
result = dns_request_createvia(zone->view->requestmgr, message,
&zone->sourceaddr, &zone->masteraddr,
dscp, options, key, timeout * 3,
timeout, 0, zone->task,
refresh_callback, zone, &zone->request);
if (result != ISC_R_SUCCESS) {
zone_idetach(&dummy);
zone_debuglog(zone, me, 1,
"dns_request_createvia4() failed: %s",
dns_result_totext(result));
goto skip_master;
} else {
if (isc_sockaddr_pf(&zone->masteraddr) == PF_INET)
inc_stats(zone, dns_zonestatscounter_soaoutv4);
else
inc_stats(zone, dns_zonestatscounter_soaoutv6);
}
cancel = false;
cleanup:
if (key != NULL)
dns_tsigkey_detach(&key);
if (result != ISC_R_SUCCESS)
DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH);
if (message != NULL)
dns_message_destroy(&message);
if (cancel)
cancel_refresh(zone);
isc_event_free(&event);
UNLOCK_ZONE(zone);
dns_zone_idetach(&zone);
return;
skip_master:
if (key != NULL)
dns_tsigkey_detach(&key);
dns_message_destroy(&message);
/*
* Skip to next failed / untried master.
*/
do {
zone->curmaster++;
} while (zone->curmaster < zone->masterscnt &&
zone->mastersok[zone->curmaster]);
if (zone->curmaster < zone->masterscnt)
goto again;
zone->curmaster = 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 masterip;
dns_tsigkey_t *key = NULL;
dns_dbnode_t *node = NULL;
int timeout;
bool have_xfrsource = false, have_xfrdscp = false;
bool reqnsid;
uint16_t udpsize = SEND_BUFFER_SIZE;
isc_dscp_t dscp = -1;
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));
if (stub == NULL)
goto cleanup;
stub->magic = STUB_MAGIC;
stub->mctx = zone->mctx;
stub->zone = NULL;
stub->db = NULL;
stub->version = NULL;
/*
* 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",
dns_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",
dns_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",
dns_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",
dns_result_totext(result));
goto cleanup;
}
}
/*
* XXX Optimisation: Create message when zone is setup and reuse.
*/
result = create_query(zone, dns_rdatatype_ns, &message);
INSIST(result == ISC_R_SUCCESS);
INSIST(zone->masterscnt > 0);
INSIST(zone->curmaster < zone->masterscnt);
zone->masteraddr = zone->masters[zone->curmaster];
isc_netaddr_fromsockaddr(&masterip, &zone->masteraddr);
/*
* First, look for a tsig key in the master statement, then
* try for a server key.
*/
if ((zone->masterkeynames != NULL) &&
(zone->masterkeynames[zone->curmaster] != NULL)) {
dns_view_t *view = dns_zone_getview(zone);
dns_name_t *keyname = zone->masterkeynames[zone->curmaster];
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, &masterip, &key);
reqnsid = zone->view->requestnsid;
if (zone->view->peers != NULL) {
dns_peer_t *peer = NULL;
bool edns;
result = dns_peerlist_peerbyaddr(zone->view->peers,
&masterip, &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;
result = dns_peer_gettransferdscp(peer, &dscp);
if (result == ISC_R_SUCCESS && dscp != -1)
have_xfrdscp = 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",
dns_result_totext(result));
}
/*
* Always use TCP so that we shouldn't truncate in additional section.
*/
switch (isc_sockaddr_pf(&zone->masteraddr)) {
case PF_INET:
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
zone->sourceaddr = zone->altxfrsource4;
if (!have_xfrdscp)
dscp = zone->altxfrsource4dscp;
} else if (!have_xfrsource) {
zone->sourceaddr = zone->xfrsource4;
if (!have_xfrdscp)
dscp = zone->xfrsource4dscp;
}
break;
case PF_INET6:
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_USEALTXFRSRC)) {
zone->sourceaddr = zone->altxfrsource6;
if (!have_xfrdscp)
dscp = zone->altxfrsource6dscp;
} else if (!have_xfrsource) {
zone->sourceaddr = zone->xfrsource6;
if (!have_xfrdscp)
dscp = zone->xfrsource6dscp;
}
break;
default:
result = ISC_R_NOTIMPLEMENTED;
POST(result);
goto cleanup;
}
timeout = 15;
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH))
timeout = 30;
result = dns_request_createvia(zone->view->requestmgr, message,
&zone->sourceaddr, &zone->masteraddr,
dscp, DNS_REQUESTOPT_TCP, key,
timeout * 3, timeout, 0, zone->task,
stub_callback, stub, &zone->request);
if (result != ISC_R_SUCCESS) {
zone_debuglog(zone, me, 1,
"dns_request_createvia() failed: %s",
dns_result_totext(result));
goto cleanup;
}
dns_message_destroy(&message);
goto unlock;
cleanup:
cancel_refresh(zone);
if (stub != NULL) {
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_destroy(&message);
unlock:
if (key != NULL)
dns_tsigkey_detach(&key);
return;
}
/*
* Handle the control event. Note that although this event causes the zone
* to shut down, it is not a shutdown event in the sense of the task library.
*/
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;
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");
/*
* Stop things being restarted after we cancel them below.
*/
LOCK_ZONE(zone);
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_EXITING);
UNLOCK_ZONE(zone);
/*
* 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)
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);
if (linked) {
INSIST(zone->irefs > 0);
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);
}
notify_cancel(zone);
forward_cancel(zone);
if (zone->timer != NULL) {
isc_timer_detach(&zone->timer);
INSIST(zone->irefs > 0);
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 (inline_secure(zone)) {
raw = zone->raw;
zone->raw = NULL;
}
if (inline_raw(zone)) {
secure = zone->secure;
zone->secure = NULL;
}
UNLOCK_ZONE(zone);
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));
ENTER;
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING))
return;
isc_time_settoepoch(&next);
switch (zone->type) {
case dns_zone_redirect:
if (zone->masters != NULL)
goto treat_as_slave;
/* FALLTHROUGH */
case dns_zone_master:
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_slave:
case dns_zone_mirror:
treat_as_slave:
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_NOMASTERS) &&
!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);
result = dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER,
&message);
if (result != ISC_R_SUCCESS)
return (result);
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_init(tempname, NULL);
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_init(tempname, NULL);
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);
result = isc_buffer_allocate(zone->mctx, &b, r.length);
if (result != ISC_R_SUCCESS)
goto soa_cleanup;
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_destroy(&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;
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->masters).
* 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 master zone just succeed.
*/
if (zone->type == dns_zone_master) {
UNLOCK_ZONE(zone);
return (ISC_R_SUCCESS);
}
isc_netaddr_fromsockaddr(&netaddr, from);
for (i = 0; i < zone->masterscnt; i++) {
if (isc_sockaddr_eqaddr(from, &zone->masters[i]))
break;
if (zone->view->aclenv.match_mapped &&
IN6_IS_ADDR_V4MAPPED(&from->type.sin6.sin6_addr) &&
isc_sockaddr_pf(&zone->masters[i]) == AF_INET) {
isc_netaddr_t na1, na2;
isc_netaddr_fromv4mapped(&na1, &netaddr);
isc_netaddr_fromsockaddr(&na2, &zone->masters[i]);
if (isc_netaddr_equal(&na1, &na2))
break;
}
}
/*
* Accept notify requests from non masters if they are on
* 'zone->notify_acl'.
*/
tsigkey = dns_message_gettsigkey(msg);
tsig = dns_tsigkey_identity(tsigkey);
if (i >= zone->masterscnt && 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->masterscnt) {
UNLOCK_ZONE(zone);
dns_zone_log(zone, ISC_LOG_INFO,
"refused notify from non-master: %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, &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("<UNKNOWN>") - 1))
isc_buffer_putstr(&buffer, "<UNKNOWN>");
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("<UNKNOWN>") - 1))
isc_buffer_putstr(&buffer, "<UNKNOWN>");
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);
zone_namerd_tostr(zone, buf, length);
}
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;
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);
}
dns_zonetype_t
dns_zone_getredirecttype(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(zone->type == dns_zone_redirect);
return (zone->masters == NULL ? dns_zone_master : dns_zone_slave);
}
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(&notify->dst, addrbuf, sizeof(addrbuf));
result = revent->result;
if (result == ISC_R_SUCCESS)
result = dns_message_create(notify->zone->mctx,
DNS_MESSAGE_INTENTPARSE, &message);
if (result == ISC_R_SUCCESS)
result = dns_request_getresponse(revent->request, message,
DNS_MESSAGEPARSE_PRESERVEORDER);
if (result == ISC_R_SUCCESS)
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);
else
notify_log(notify->zone, ISC_LOG_DEBUG(2),
"notify to %s failed: %s", addrbuf,
dns_result_totext(result));
/*
* Old bind's return formerr if they see a soa record. Retry w/o
* the soa if we see a formerr and had sent a SOA.
*/
isc_event_free(&event);
if (message != NULL && message->rcode == dns_rcode_formerr &&
(notify->flags & DNS_NOTIFY_NOSOA) == 0) {
bool startup;
notify->flags |= DNS_NOTIFY_NOSOA;
dns_request_destroy(&notify->request);
startup = (notify->flags & DNS_NOTIFY_STARTUP);
result = notify_send_queue(notify, startup);
if (result != ISC_R_SUCCESS)
notify_destroy(notify, false);
} else {
if (result == ISC_R_TIMEDOUT)
notify_log(notify->zone, ISC_LOG_DEBUG(1),
"notify to %s: retries exceeded", addrbuf);
notify_destroy(notify, false);
}
if (message != NULL)
dns_message_destroy(&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));
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 (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;
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->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);
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)
dns_zone_log(zone, ISC_LOG_ERROR, "receive_secure_serial: %s",
dns_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);
INSIST(zone->irefs > 1);
zone->irefs--;
ISC_LIST_UNLINK(zone->rss_events, event, ev_link);
goto nextevent;
}
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));
if (e == NULL)
return (ISC_R_NOMEMORY);
((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 tho 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));
if (nsec3param == NULL)
CHECK(ISC_R_NOMEMORY);
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));
if (nsec3param == NULL)
CHECK(ISC_R_NOMEMORY);
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;
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 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_dbnode_t *rawnode = NULL, *node = NULL;
dns_fixedname_t fname;
dns_name_t *name;
dns_dbiterator_t *dbiterator = NULL;
dns_rdatasetiter_t *rdsit = NULL;
dns_rdataset_t rdataset;
dns_dbversion_t *version = NULL;
isc_time_t loadtime;
unsigned int oldserial = 0;
bool have_oldserial = false;
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);
name = dns_fixedname_initname(&fname);
dns_rdataset_init(&rdataset);
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)
have_oldserial = true;
/*
* 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 = dns_dbiterator_current(dbiterator, &rawnode, name);
if (result != ISC_R_SUCCESS)
continue;
result = dns_db_findnode(db, name, true, &node);
if (result != ISC_R_SUCCESS)
goto failure;
result = dns_db_allrdatasets(rawdb, rawnode, NULL, 0, &rdsit);
if (result != ISC_R_SUCCESS)
goto failure;
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 &&
have_oldserial) {
result = checkandaddsoa(db, node, version,
&rdataset, oldserial);
} else
result = dns_db_addrdataset(db, node, version,
0, &rdataset, 0,
NULL);
if (result != ISC_R_SUCCESS)
goto failure;
dns_rdataset_disassociate(&rdataset);
}
dns_rdatasetiter_destroy(&rdsit);
dns_db_detachnode(rawdb, &rawnode);
dns_db_detachnode(db, &node);
}
/*
* 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 (result != ISC_R_SUCCESS)
dns_zone_log(zone, ISC_LOG_ERROR, "receive_secure_db: %s",
dns_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 (dns_rdataset_isassociated(&rdataset))
dns_rdataset_disassociate(&rdataset);
if (db != NULL) {
if (node != NULL)
dns_db_detachnode(db, &node);
if (version != NULL)
dns_db_closeversion(db, &version, false);
dns_db_detach(&db);
}
if (rawnode != NULL)
dns_db_detachnode(rawdb, &rawnode);
dns_db_detach(&rawdb);
if (dbiterator != NULL)
dns_dbiterator_destroy(&dbiterator);
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));
if (e == NULL)
return (ISC_R_NOMEMORY);
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);
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",
dns_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 slave 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 master zones.
*/
result = zone_get_from_db(zone, zone->db, NULL, &soacount,
&oldserial, NULL, NULL, NULL, NULL,
NULL);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
RUNTIME_CHECK(soacount > 0U);
if ((zone->type == dns_zone_slave ||
(zone->type == dns_zone_redirect &&
zone->masters != 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_master && 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 inline 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 inline void
zone_detachdb(dns_zone_t *zone) {
REQUIRE(zone->db != NULL);
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;
isc_result_t xfrresult = result;
bool free_needed;
dns_zone_t *secure = NULL;
REQUIRE(DNS_ZONE_VALID(zone));
dns_zone_log(zone, ISC_LOG_DEBUG(1),
"zone transfer finished: %s", dns_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 the 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((zone->flags & DNS_ZONEFLG_REFRESH) != 0);
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_master;
}
/*
* 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, &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 record%s", soacount,
(soacount != 0) ? "s" : "");
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_master;
}
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->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);
}
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_log(zone, 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_log(zone, ISC_LOG_ERROR,
"transfer: could not set file "
"modification time of '%s': %s",
zone->masterfile,
dns_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_ZONEFLAG_NOIXFR);
goto same_master;
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_master:
/*
* Skip to next failed / untried master.
*/
do {
zone->curmaster++;
} while (zone->curmaster < zone->masterscnt &&
zone->mastersok[zone->curmaster]);
/* FALLTHROUGH */
same_master:
if (zone->curmaster >= zone->masterscnt) {
zone->curmaster = 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->curmaster < zone->masterscnt &&
zone->mastersok[zone->curmaster])
zone->curmaster++;
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);
/*
* 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);
INSIST(zone->irefs > 0);
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(result, secure);
if (result != 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);
LOCK_ZONE(zone);
zone->irefs++;
UNLOCK_ZONE(zone);
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 master[ISC_SOCKADDR_FORMATSIZE];
char source[ISC_SOCKADDR_FORMATSIZE];
dns_rdatatype_t xfrtype;
dns_zone_t *zone = event->ev_arg;
isc_netaddr_t masterip;
isc_sockaddr_t sourceaddr;
isc_sockaddr_t masteraddr;
isc_time_t now;
const char *soa_before = "";
isc_dscp_t dscp = -1;
bool loaded;
UNUSED(task);
INSIST(task == zone->task);
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
result = ISC_R_CANCELED;
goto cleanup;
}
TIME_NOW(&now);
isc_sockaddr_format(&zone->masteraddr, master, sizeof(master));
if (dns_zonemgr_unreachable(zone->zmgr, &zone->masteraddr,
&zone->sourceaddr, &now))
{
isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source));
dns_zone_log(zone, ISC_LOG_INFO,
"got_transfer_quota: skipping zone transfer as "
"master %s (source %s) is unreachable (cached)",
master, source);
result = ISC_R_CANCELED;
goto cleanup;
}
isc_netaddr_fromsockaddr(&masterip, &zone->masteraddr);
(void)dns_peerlist_peerbyaddr(zone->view->peers, &masterip, &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_log(zone, ISC_LOG_DEBUG(1),
"no database exists yet, requesting AXFR of "
"initial version from %s", master);
xfrtype = dns_rdatatype_axfr;
} else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER)) {
dns_zone_log(zone, ISC_LOG_DEBUG(1),
"forced reload, requesting AXFR of "
"initial version from %s", master);
xfrtype = dns_rdatatype_axfr;
} else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLAG_NOIXFR)) {
dns_zone_log(zone, ISC_LOG_DEBUG(1),
"retrying with AXFR from %s due to "
"previous IXFR failure", master);
xfrtype = dns_rdatatype_axfr;
LOCK_ZONE(zone);
DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLAG_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 == false) {
dns_zone_log(zone, ISC_LOG_DEBUG(1),
"IXFR disabled, requesting %sAXFR from %s",
soa_before, master);
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_SOABEFOREAXFR))
xfrtype = dns_rdatatype_soa;
else
xfrtype = dns_rdatatype_axfr;
} else {
dns_zone_log(zone, ISC_LOG_DEBUG(1),
"requesting IXFR from %s", master);
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 master statement, then
* try for a server key.
*/
if ((zone->masterkeynames != NULL) &&
(zone->masterkeynames[zone->curmaster] != NULL)) {
dns_view_t *view = dns_zone_getview(zone);
dns_name_t *keyname = zone->masterkeynames[zone->curmaster];
result = dns_view_gettsig(view, keyname, &zone->tsigkey);
}
if (zone->tsigkey == NULL)
result = dns_view_getpeertsig(zone->view, &masterip,
&zone->tsigkey);
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
dns_zone_log(zone, ISC_LOG_ERROR,
"could not get TSIG key for zone transfer: %s",
isc_result_totext(result));
}
if (zone->masterdscps != NULL)
dscp = zone->masterdscps[zone->curmaster];
LOCK_ZONE(zone);
masteraddr = zone->masteraddr;
sourceaddr = zone->sourceaddr;
switch (isc_sockaddr_pf(&masteraddr)) {
case PF_INET:
if (dscp == -1)
dscp = zone->xfrsource4dscp;
break;
case PF_INET6:
if (dscp == -1)
dscp = zone->xfrsource6dscp;
break;
default:
INSIST(0);
ISC_UNREACHABLE();
}
UNLOCK_ZONE(zone);
INSIST(isc_sockaddr_pf(&masteraddr) == isc_sockaddr_pf(&sourceaddr));
result = dns_xfrin_create(zone, xfrtype, &masteraddr, &sourceaddr,
dscp, zone->tsigkey, zone->mctx,
zone->zmgr->timermgr, zone->zmgr->socketmgr,
zone->task, zone_xfrdone, &zone->xfr);
if (result == ISC_R_SUCCESS) {
LOCK_ZONE(zone);
if (xfrtype == dns_rdatatype_axfr) {
if (isc_sockaddr_pf(&masteraddr) == 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(&masteraddr) == PF_INET)
inc_stats(zone, dns_zonestatscounter_ixfrreqv4);
else
inc_stats(zone, dns_zonestatscounter_ixfrreqv6);
}
UNLOCK_ZONE(zone);
}
cleanup:
/*
* 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);
isc_event_free(&event);
}
/*
* 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
sendtomaster(dns_forward_t *forward) {
isc_result_t result;
isc_sockaddr_t src;
isc_dscp_t dscp = -1;
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->masterscnt) {
UNLOCK_ZONE(forward->zone);
return (ISC_R_NOMORE);
}
forward->addr = forward->zone->masters[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 the master has to try several masters.
*/
switch (isc_sockaddr_pf(&forward->addr)) {
case PF_INET:
src = forward->zone->xfrsource4;
dscp = forward->zone->xfrsource4dscp;
break;
case PF_INET6:
src = forward->zone->xfrsource6;
dscp = forward->zone->xfrsource6dscp;
break;
default:
result = ISC_R_NOTIMPLEMENTED;
goto unlock;
}
result = dns_request_createraw(forward->zone->view->requestmgr,
forward->msgbuf,
&src, &forward->addr, dscp,
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 master[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, master, sizeof(master));
if (revent->result != ISC_R_SUCCESS) {
dns_zone_log(zone, ISC_LOG_INFO,
"could not forward dynamic update to %s: %s",
master, dns_result_totext(revent->result));
goto next_master;
}
result = dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
if (result != ISC_R_SUCCESS)
goto next_master;
result = dns_request_getresponse(revent->request, msg,
DNS_MESSAGEPARSE_PRESERVEORDER |
DNS_MESSAGEPARSE_CLONEBUFFER);
if (result != ISC_R_SUCCESS)
goto next_master;
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: "
"master %s returned: %.*s",
master, (int)rb.used, rcode);
break;
}
/* These should not occur if the masters/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: master %s returned: %.*s",
master, (int)rb.used, rcode);
goto next_master;
}
/* 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_master;
}
/* 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_master:
if (msg != NULL)
dns_message_destroy(&msg);
isc_event_free(&event);
forward->which++;
dns_request_destroy(&forward->request);
result = sendtomaster(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));
if (forward == NULL)
return (ISC_R_NOMEMORY);
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;
}
result = isc_buffer_allocate(zone->mctx, &forward->msgbuf, mr->length);
if (result != ISC_R_SUCCESS)
goto cleanup;
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 = sendtomaster(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.
***/
isc_result_t
dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr,
dns_zonemgr_t **zmgrp)
{
dns_zonemgr_t *zmgr;
isc_result_t result;
zmgr = isc_mem_get(mctx, sizeof(*zmgr));
if (zmgr == NULL)
return (ISC_R_NOMEMORY);
zmgr->mctx = NULL;
zmgr->refs = 1;
isc_mem_attach(mctx, &zmgr->mctx);
zmgr->taskmgr = taskmgr;
zmgr->timermgr = timermgr;
zmgr->socketmgr = socketmgr;
zmgr->zonetasks = NULL;
zmgr->loadtasks = NULL;
zmgr->mctxpool = NULL;
zmgr->task = 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));
result = isc_rwlock_init(&zmgr->rwlock, 0, 0);
if (result != ISC_R_SUCCESS)
goto free_mem;
zmgr->transfersin = 10;
zmgr->transfersperns = 2;
/* Unreachable lock. */
result = isc_rwlock_init(&zmgr->urlock, 0, 0);
if (result != ISC_R_SUCCESS)
goto free_rwlock;
/* 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->notifyrl);
if (result != ISC_R_SUCCESS)
goto free_task;
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;
/* default to 20 refresh queries / notifies per second. */
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->magic = ZONEMGR_MAGIC;
*zmgrp = zmgr;
return (ISC_R_SUCCESS);
#if 0
free_iolock:
isc_mutex_destroy(&zmgr->iolock);
#endif
free_startupnotifyrl:
isc_ratelimiter_detach(&zmgr->startupnotifyrl);
free_refreshrl:
isc_ratelimiter_detach(&zmgr->refreshrl);
free_notifyrl:
isc_ratelimiter_detach(&zmgr->notifyrl);
free_task:
isc_task_detach(&zmgr->task);
free_urlock:
isc_rwlock_destroy(&zmgr->urlock);
free_rwlock:
isc_rwlock_destroy(&zmgr->rwlock);
free_mem:
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.
*/
zone->irefs++;
INSIST(zone->irefs != 0);
ISC_LIST_APPEND(zmgr->zones, zone, link);
zone->zmgr = zmgr;
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) {
bool free_now = false;
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);
zone->zmgr = NULL;
zmgr->refs--;
if (zmgr->refs == 0)
free_now = true;
UNLOCK_ZONE(zone);
RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write);
if (free_now)
zonemgr_free(zmgr);
ENSURE(zone->zmgr == NULL);
}
void
dns_zonemgr_attach(dns_zonemgr_t *source, dns_zonemgr_t **target) {
REQUIRE(DNS_ZONEMGR_VALID(source));
REQUIRE(target != NULL && *target == NULL);
RWLOCK(&source->rwlock, isc_rwlocktype_write);
REQUIRE(source->refs > 0);
source->refs++;
INSIST(source->refs > 0);
RWUNLOCK(&source->rwlock, isc_rwlocktype_write);
*target = source;
}
void
dns_zonemgr_detach(dns_zonemgr_t **zmgrp) {
dns_zonemgr_t *zmgr;
bool free_now = false;
REQUIRE(zmgrp != NULL);
zmgr = *zmgrp;
REQUIRE(DNS_ZONEMGR_VALID(zmgr));
RWLOCK(&zmgr->rwlock, isc_rwlocktype_write);
zmgr->refs--;
if (zmgr->refs == 0)
free_now = true;
RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write);
if (free_now)
zonemgr_free(zmgr);
*zmgrp = NULL;
}
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->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_result_t result;
isc_mem_t *mctx = NULL;
UNUSED(arg);
REQUIRE(target != NULL && *target == NULL);
result = isc_mem_create(0, 0, &mctx);
if (result != ISC_R_SUCCESS)
return (result);
isc_mem_setname(mctx, "zonemgr-pool", NULL);
*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, &pool);
else
result = isc_taskpool_expand(&zmgr->zonetasks, ntasks, &pool);
if (result == ISC_R_SUCCESS)
zmgr->zonetasks = pool;
pool = NULL;
if (zmgr->loadtasks == NULL)
result = isc_taskpool_create(zmgr->taskmgr, zmgr->mctx,
ntasks, 2, &pool);
else
result = isc_taskpool_expand(&zmgr->loadtasks, ntasks, &pool);
if (result == ISC_R_SUCCESS)
zmgr->loadtasks = 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.
*
* NOTE: If we start using task privileges for any other
* part of the system than zone tasks, then this will need to be
* revisted. In that case we'd want to turn on privileges for
* zone tasks only when we were loading, and turn them off the
* rest of the time. For now, however, it's okay to just
* set it and forget it.
*/
isc_taskpool_setprivilege(zmgr->loadtasks, true);
/* 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(zmgr->refs == 0);
INSIST(ISC_LIST_EMPTY(zmgr->zones));
zmgr->magic = 0;
isc_mutex_destroy(&zmgr->iolock);
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);
mctx = zmgr->mctx;
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_getttransfersin(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_getttransfersperns(dns_zonemgr_t *zmgr) {
REQUIRE(DNS_ZONEMGR_VALID(zmgr));
return (zmgr->transfersperns);
}
/*
* 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 master.
*/
continue;
} else {
dns_zone_log(zone, 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 masterip;
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(&masterip, &zone->masteraddr);
(void)dns_peerlist_peerbyaddr(zone->view->peers, &masterip, &peer);
UNLOCK_ZONE(zone);
/*
* Determine the total maximum number of simultaneous
* transfers allowed, and the maximum for this specific
* master.
*/
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 master.
* We linearly scan a list of all transfers; if this turns
* out to be too slow, we could hash on the master 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->masteraddr);
UNLOCK_ZONE(x);
nxfrsin++;
if (isc_netaddr_equal(&xip, &masterip))
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));
if (e == NULL)
return (ISC_R_NOMEMORY);
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_log(zone, 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));
if (io == NULL)
return (ISC_R_NOMEMORY);
io->event = isc_event_allocate(zmgr->mctx, task, DNS_EVENT_IOREADY,
action, arg, sizeof(*io->event));
if (io->event == NULL) {
isc_mem_put(zmgr->mctx, io, sizeof(*io));
return (ISC_R_NOMEMORY);
}
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;
REQUIRE(DNS_IO_VALID(io));
*iop = NULL;
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);
if (buf == NULL)
return;
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_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 seperate 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;
isc_rwlocktype_t locktype;
isc_result_t result;
uint32_t seconds = isc_time_seconds(now);
uint32_t count = 0;
REQUIRE(DNS_ZONEMGR_VALID(zmgr));
locktype = isc_rwlocktype_read;
RWLOCK(&zmgr->urlock, locktype);
for (i = 0; i < UNREACH_CHACHE_SIZE; i++) {
if (zmgr->unreachable[i].expire >= seconds &&
isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) &&
isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) {
result = isc_rwlock_tryupgrade(&zmgr->urlock);
if (result == ISC_R_SUCCESS) {
locktype = isc_rwlocktype_write;
zmgr->unreachable[i].last = seconds;
count = zmgr->unreachable[i].count;
}
break;
}
}
RWUNLOCK(&zmgr->urlock, locktype);
return (i < UNREACH_CHACHE_SIZE && count > 1U);
}
void
dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote,
isc_sockaddr_t *local)
{
unsigned int i;
isc_rwlocktype_t locktype;
isc_result_t result;
char master[ISC_SOCKADDR_FORMATSIZE];
char source[ISC_SOCKADDR_FORMATSIZE];
isc_sockaddr_format(remote, master, sizeof(master));
isc_sockaddr_format(local, source, sizeof(source));
REQUIRE(DNS_ZONEMGR_VALID(zmgr));
locktype = isc_rwlocktype_read;
RWLOCK(&zmgr->urlock, locktype);
for (i = 0; i < UNREACH_CHACHE_SIZE; i++) {
if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) &&
isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) {
if (zmgr->unreachable[i].expire == 0)
break;
result = isc_rwlock_tryupgrade(&zmgr->urlock);
if (result == ISC_R_SUCCESS) {
locktype = isc_rwlocktype_write;
zmgr->unreachable[i].expire = 0;
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL,
DNS_LOGMODULE_ZONE, ISC_LOG_INFO,
"master %s (source %s) deleted "
"from unreachable cache",
master, source);
}
break;
}
}
RWUNLOCK(&zmgr->urlock, locktype);
}
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 last = seconds;
unsigned int i, slot = UNREACH_CHACHE_SIZE, oldest = 0;
REQUIRE(DNS_ZONEMGR_VALID(zmgr));
RWLOCK(&zmgr->urlock, isc_rwlocktype_write);
for (i = 0; i < UNREACH_CHACHE_SIZE; i++) {
/* Existing entry? */
if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) &&
isc_sockaddr_equal(&zmgr->unreachable[i].local, local))
break;
/* Empty slot? */
if (zmgr->unreachable[i].expire < seconds)
slot = i;
/* Least recently used slot? */
if (zmgr->unreachable[i].last < last) {
last = zmgr->unreachable[i].last;
oldest = i;
}
}
if (i < UNREACH_CHACHE_SIZE) {
/*
* Found a existing entry. Update the expire timer and
* last usage timestamps.
*/
zmgr->unreachable[i].expire = seconds + UNREACH_HOLD_TIME;
zmgr->unreachable[i].last = seconds;
if (zmgr->unreachable[i].expire < seconds)
zmgr->unreachable[i].count = 1;
else
zmgr->unreachable[i].count++;
} else if (slot != UNREACH_CHACHE_SIZE) {
/*
* Found a empty slot. Add a new entry to the cache.
*/
zmgr->unreachable[slot].expire = seconds + UNREACH_HOLD_TIME;
zmgr->unreachable[slot].last = seconds;
zmgr->unreachable[slot].remote = *remote;
zmgr->unreachable[slot].local = *local;
zmgr->unreachable[slot].count = 1;
} else {
/*
* Replace the least recently used entry in the cache.
*/
zmgr->unreachable[oldest].expire = seconds + UNREACH_HOLD_TIME;
zmgr->unreachable[oldest].last = seconds;
zmgr->unreachable[oldest].remote = *remote;
zmgr->unreachable[oldest].local = *local;
zmgr->unreachable[oldest].count = 1;
}
RWUNLOCK(&zmgr->urlock, isc_rwlocktype_write);
}
void
dns_zone_forcereload(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
if (zone->type == dns_zone_master ||
(zone->type == dns_zone_redirect && zone->masters == 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);
}
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_master && zone->masters != 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:
INSIST(0);
ISC_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:
INSIST(0);
ISC_UNREACHABLE();
}
RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_read);
return (count);
}
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,
dns_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, dns_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);
if (signing == NULL)
return (ISC_R_NOMEMORY);
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);
}
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);
}
}
/* 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);
}
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;
dns_difftuple_t *tuple;
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",
dns_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 signature and if
* not cause them to sign so that newly activated keys are used.
*/
for (tuple = ISC_LIST_HEAD(diff->tuples);
tuple != NULL;
tuple = ISC_LIST_NEXT(tuple, link))
{
if (tuple->rdata.type == dns_rdatatype_dnskey &&
dns_name_equal(&tuple->name, &zone->origin))
{
break;
}
}
if (tuple == NULL) {
result = del_sigs(zone, db, ver, &zone->origin,
dns_rdatatype_dnskey, zonediff,
zone_keys, nkeys, now, false);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"sign_apex:del_sigs -> %s",
dns_result_totext(result));
goto failure;
}
result = add_sigs(db, ver, &zone->origin, dns_rdatatype_dnskey,
zonediff->diff, zone_keys, nkeys, zone->mctx,
inception, keyexpire, check_ksk,
keyset_kskonly);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"sign_apex:add_sigs -> %s",
dns_result_totext(result));
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",
dns_result_totext(result));
goto failure;
}
failure:
for (i = 0; i < nkeys; i++) {
dst_key_free(&zone_keys[i]);
}
return (result);
}
/*
* Prevent the zone entering a inconsistent state where
* NSEC only DNSKEYs are present with NSEC3 chains.
* See update.c:check_dnssec()
*/
static bool
dnskey_sane(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
dns_diff_t *diff)
{
isc_result_t result;
dns_difftuple_t *tuple;
bool nseconly = false, nsec3 = false;
dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
/* Scan the tuples for an NSEC-only DNSKEY */
for (tuple = ISC_LIST_HEAD(diff->tuples);
tuple != NULL;
tuple = ISC_LIST_NEXT(tuple, link))
{
uint8_t alg;
if (tuple->rdata.type != dns_rdatatype_dnskey ||
tuple->op != DNS_DIFFOP_ADD)
{
continue;
}
alg = tuple->rdata.data[3];
if (alg == DST_ALG_RSASHA1) {
nseconly = true;
break;
}
}
/* Check existing DB for NSEC-only DNSKEY */
if (!nseconly) {
result = dns_nsec_nseconly(db, ver, &nseconly);
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));
}
/* Refuse to allow NSEC3 with NSEC-only keys */
if (nseconly && nsec3) {
dnssec_log(zone, ISC_LOG_ERROR,
"NSEC only DNSKEYs and NSEC3 chains not allowed");
goto failure;
}
return (true);
failure:
return (false);
}
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->minimum,
false, zone->privatetype, diff));
CHECK(updatesecure(db, ver, origin, zone->minimum, 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
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__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;
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);
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;
CHECK(dns_dnssec_keylistfromrdataset(&zone->origin, dir,
mctx, &keyset,
&keysigs, &soasigs,
false, false,
&dnskeys));
} 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);
result = dns_dnssec_findmatchingkeys(&zone->origin, dir, now, mctx,
&keys);
if (result == ISC_R_SUCCESS) {
bool check_ksk;
check_ksk = DNS_ZONE_OPTION(zone, DNS_ZONEOPT_UPDATECHECKKSK);
result = dns_dnssec_updatekeys(&dnskeys, &keys, &rmkeys,
&zone->origin, ttl, &diff,
!check_ksk, 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;
}
/*
* 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;
}
}
if ((newactive || fullsign || !ISC_LIST_EMPTY(diff.tuples)) &&
dnskey_sane(zone, db, ver, &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(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);
if (commit) {
dns_difftuple_t *tuple;
LOCK_ZONE(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",
dns_result_totext(result));
}
}
}
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",
dns_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",
dns_result_totext(result));
}
}
}
/*
* Clear fullsign flag, if it was set, so we don't do
* another full signing next time
*/
zone->keyopts &= ~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",
dns_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);
UNLOCK_ZONE(zone);
}
isc_time_settoepoch(&zone->refreshkeytime);
/*
* 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.
*/
if (DNS_ZONEKEY_OPTION(zone, DNS_ZONEKEY_MAINTAIN)) {
isc_time_t timethen;
isc_stdtime_t then;
LOCK_ZONE(zone);
DNS_ZONE_TIME_ADD(&timenow, zone->refreshkeyinterval,
&timethen);
zone->refreshkeytime = timethen;
UNLOCK_ZONE(zone);
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);
LOCK_ZONE(zone);
if (isc_time_compare(&timethen,
&zone->refreshkeytime) < 0)
{
zone->refreshkeytime = timethen;
}
UNLOCK_ZONE(zone);
}
zone_settimer(zone, &timenow);
isc_time_formattimestamp(&zone->refreshkeytime, timebuf, 80);
dnssec_log(zone, ISC_LOG_INFO, "next key event: %s", timebuf);
}
result = ISC_R_SUCCESS;
failure:
if (result != ISC_R_SUCCESS) {
/*
* Something went wrong; try again in ten minutes or
* after a key refresh interval, whichever is shorter.
*/
isc_interval_set(&ival,
ISC_MIN(zone->refreshkeyinterval, 600), 0);
isc_time_nowplusinterval(&zone->refreshkeytime, &ival);
}
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_master && zone->task != NULL) {
LOCK_ZONE(zone);
if (fullsign)
zone->keyopts |= 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 buffer[DNS_DS_BUFFERSIZE];
unsigned char algorithms[256];
unsigned int i;
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) {
if (dns_rdataset_isassociated(&cds))
result = DNS_R_BADCDS;
else
result = DNS_R_BADCDNSKEY;
goto failure;
}
if (result != ISC_R_SUCCESS)
goto failure;
/*
* For each DNSSEC algorithm in the CDS RRset there must be
* a matching DNSKEY record.
*/
if (dns_rdataset_isassociated(&cds)) {
memset(algorithms, 0, 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);
CHECK(dns_rdata_tostruct(&crdata, &structcds, NULL));
if (algorithms[structcds.algorithm] == 0)
algorithms[structcds.algorithm] = 1;
for (result = dns_rdataset_first(&dnskey);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(&dnskey)) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_t dsrdata = DNS_RDATA_INIT;
dns_rdataset_current(&dnskey, &rdata);
CHECK(dns_ds_buildrdata(&zone->origin, &rdata,
structcds.digest_type,
buffer, &dsrdata));
if (crdata.length == dsrdata.length &&
memcmp(crdata.data, dsrdata.data,
dsrdata.length) == 0) {
algorithms[structcds.algorithm] = 2;
}
}
if (result != ISC_R_NOMORE)
goto failure;
}
for (i = 0; i < sizeof(algorithms); i++) {
if (algorithms[i] == 1) {
result = DNS_R_BADCDNSKEY;
goto failure;
}
}
}
/*
* For each DNSSEC algorithm in the CDNSKEY RRset there must be
* a matching DNSKEY record.
*/
if (dns_rdataset_isassociated(&cdnskey)) {
memset(algorithms, 0, 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);
CHECK(dns_rdata_tostruct(&crdata, &structcdnskey,
NULL));
if (algorithms[structcdnskey.algorithm] == 0)
algorithms[structcdnskey.algorithm] = 1;
for (result = dns_rdataset_first(&dnskey);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(&dnskey)) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(&dnskey, &rdata);
if (crdata.length == rdata.length &&
memcmp(crdata.data, rdata.data,
rdata.length) == 0) {
algorithms[structcdnskey.algorithm] = 2;
}
}
if (result != ISC_R_NOMORE)
goto failure;
}
for (i = 0; i < sizeof(algorithms); i++) {
if (algorithms[i] == 1) {
result = DNS_R_BADCDS;
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_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.
*/
raw->irefs++;
INSIST(raw->irefs != 0);
/* 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;
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",
dns_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(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);
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));
if (e == NULL) {
result = ISC_R_NOMEMORY;
goto failure;
}
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().
*
* 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
setnsec3param(isc_task_t *task, isc_event_t *event) {
const char *me = "setnsec3param";
bool commit = false;
isc_result_t result;
dns_dbversion_t *oldver = NULL, *newver = NULL;
dns_zone_t *zone;
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;
UNUSED(task);
zone = event->ev_arg;
INSIST(DNS_ZONE_VALID(zone));
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) {
ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_read);
dnssec_log(zone, ISC_LOG_ERROR,
"setnsec3param:dns_db_newversion -> %s",
dns_result_totext(result));
goto failure;
}
CHECK(dns_db_getoriginnode(db, &node));
/*
* 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, &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(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);
dns_zone_idetach(&zone);
INSIST(oldver == NULL);
INSIST(newver == NULL);
}
/*
* Called when an "rndc signing -nsec3param ..." command is received.
*
* 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)
{
isc_result_t result = ISC_R_SUCCESS;
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];
struct np3event *npe;
nsec3param_t *np;
dns_zone_t *dummy = NULL;
isc_buffer_t b;
isc_event_t *e;
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(salt != NULL);
LOCK_ZONE(zone);
e = isc_event_allocate(zone->mctx, zone, DNS_EVENT_SETNSEC3PARAM,
setnsec3param, zone, sizeof(struct np3event));
if (e == NULL) {
result = ISC_R_NOMEMORY;
goto failure;
}
npe = (struct np3event *) e;
np = &npe->params;
np->replace = replace;
if (hash == 0) {
np->length = 0;
np->nsec = true;
} else {
param.common.rdclass = zone->rdclass;
param.common.rdtype = dns_rdatatype_nsec3param;
ISC_LINK_INIT(&param.common, link);
param.mctx = NULL;
param.hash = hash;
param.flags = flags;
param.iterations = iter;
param.salt_length = saltlen;
param.salt = salt;
isc_buffer_init(&b, nbuf, sizeof(nbuf));
CHECK(dns_rdata_fromstruct(&nrdata, zone->rdclass,
dns_rdatatype_nsec3param,
&param, &b));
dns_nsec3param_toprivate(&nrdata, &prdata, zone->privatetype,
np->data, sizeof(np->data));
np->length = prdata.length;
np->nsec = false;
}
/*
* 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);
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);
if (array == NULL)
goto done;
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 failure;
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",
dns_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);
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));
if (e == NULL) {
result = ISC_R_NOMEMORY;
goto failure;
}
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(const 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";
ENTER;
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(db != NULL);
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);
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);
}