update.c revision 1b05d22789fd9a17aca4f459639bc2b6848c3160
/*
* Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
*
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#include <time.h>
#include <isc/taskpool.h>
#include <dns/dbiterator.h>
#include <dns/fixedname.h>
#include <dns/keyvalues.h>
#include <dns/rdataclass.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatastruct.h>
#include <dns/rdatatype.h>
/**************************************************************************/
/*%
* Log level for tracing dynamic update protocol requests.
*/
#define LOGLEVEL_PROTOCOL ISC_LOG_INFO
/*%
* Log level for low-level debug tracing.
*/
/*%
* Check an operation for failure. These macros all assume that
* the function using them has a 'result' variable and a 'failure'
* label.
*/
} while (0)
/*%
* Fail unconditionally with result 'code', which must not
* be ISC_R_SUCCESS. The reason for failure presumably has
* been logged already.
*
* The test against ISC_R_SUCCESS is there to keep the Solaris compiler
* from complaining about "end-of-loop code not reached".
*/
do { \
} while (0)
/*%
* Fail unconditionally and log as a client error.
* The test against ISC_R_SUCCESS is there to keep the Solaris compiler
* from complaining about "end-of-loop code not reached".
*/
do { \
const char *_what = "failed"; \
switch (result) { \
case DNS_R_NXDOMAIN: \
case DNS_R_YXDOMAIN: \
case DNS_R_YXRRSET: \
case DNS_R_NXRRSET: \
_what = "unsuccessful"; \
} \
"update %s: %s (%s)", _what, \
} while (0)
do { \
const char *_what = "failed"; \
switch (result) { \
case DNS_R_NXDOMAIN: \
case DNS_R_YXDOMAIN: \
case DNS_R_YXRRSET: \
case DNS_R_NXRRSET: \
_what = "unsuccessful"; \
} \
char _nbuf[DNS_NAME_FORMATSIZE]; \
} \
} while (0)
do { \
const char *_what = "failed"; \
switch (result) { \
case DNS_R_NXDOMAIN: \
case DNS_R_YXDOMAIN: \
case DNS_R_YXRRSET: \
case DNS_R_NXRRSET: \
_what = "unsuccessful"; \
} \
char _nbuf[DNS_NAME_FORMATSIZE]; \
char _tbuf[DNS_RDATATYPE_FORMATSIZE]; \
"update %s: %s/%s: %s (%s)", \
isc_result_totext(result)); \
} \
} while (0)
/*%
* Fail unconditionally and log as a server error.
* The test against ISC_R_SUCCESS is there to keep the Solaris compiler
* from complaining about "end-of-loop code not reached".
*/
do { \
"error: %s: %s", \
} while (0)
/**************************************************************************/
struct rr {
/* dns_name_t name; */
};
typedef struct update_event update_event_t;
/**************************************************************************/
static void
static void
{
char message[4096];
return;
return;
}
/*%
* Update a single RR in version 'ver' of 'db' and log the
* update in 'diff'.
*
* Ensures:
* \li '*tuple' == NULL. Either the tuple is freed, or its
* ownership has been transferred to the diff.
*/
static isc_result_t
{
/*
* Create a singleton diff.
*/
/*
* Apply it to the database.
*/
if (result != ISC_R_SUCCESS) {
return (result);
}
/*
* Merge it into the current pending journal entry.
*/
/*
* Do not clear temp_diff.
*/
return (ISC_R_SUCCESS);
}
static isc_result_t
{
if (result != ISC_R_SUCCESS)
return (result);
}
/**************************************************************************/
/*
* Callback-style iteration over rdatasets and rdatas.
*
* foreach_rrset() can be used to iterate over the RRsets
* of a name and call a callback function with each
* one. Similarly, foreach_rr() can be used to iterate
* over the individual RRs at name, optionally restricted
* to RRs of a given type.
*
* The callback functions are called "actions" and take
* two arguments: a void pointer for passing arbitrary
* context information, and a pointer to the current RRset
* or RR. By convention, their names end in "_action".
*/
/*
* XXXRTH We might want to make this public somewhere in libdns.
*/
/*%
* Function type for foreach_rrset() iterator actions.
*/
/*%
* Function type for foreach_rr() iterator actions.
*/
/*%
* Internal context struct for foreach_node_rr().
*/
typedef struct {
void * rr_action_data;
/*%
* Internal helper function for foreach_node_rr().
*/
static isc_result_t
result == ISC_R_SUCCESS;
{
if (result != ISC_R_SUCCESS)
return (result);
}
if (result != ISC_R_NOMORE)
return (result);
return (ISC_R_SUCCESS);
}
/*%
* For each rdataset of 'name' in 'ver' of 'db', call 'action'
* with the rdataset and 'action_data' as arguments. If the name
* does not exist, do nothing.
*
* If 'action' returns an error, abort iteration and return the error.
*/
static isc_result_t
{
if (result == ISC_R_NOTFOUND)
return (ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS)
return (result);
(isc_stdtime_t) 0, &iter);
if (result != ISC_R_SUCCESS)
goto cleanup_node;
result == ISC_R_SUCCESS;
{
if (result != ISC_R_SUCCESS)
goto cleanup_iterator;
}
if (result == ISC_R_NOMORE)
return (result);
}
/*%
* For each RR of 'name' in 'ver' of 'db', call 'action'
* with the RR and 'action_data' as arguments. If the name
* does not exist, do nothing.
*
* If 'action' returns an error, abort iteration
* and return the error.
*/
static isc_result_t
{
}
/*%
* For each of the RRs specified by 'db', 'ver', 'name', 'type',
* (which can be dns_rdatatype_any to match any type), and 'covers', call
* 'action' with the RR and 'action_data' as arguments. If the name
* does not exist, or if no RRset of the given type exists at the name,
* do nothing.
*
* If 'action' returns an error, abort iteration and return the error.
*/
static isc_result_t
void *rr_action_data)
{
if (type == dns_rdatatype_any)
if (type == dns_rdatatype_nsec3 ||
else
if (result == ISC_R_NOTFOUND)
return (ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS)
return (result);
if (result == ISC_R_NOTFOUND) {
goto cleanup_node;
}
if (result != ISC_R_SUCCESS)
goto cleanup_node;
result == ISC_R_SUCCESS;
{
if (result != ISC_R_SUCCESS)
goto cleanup_rdataset;
}
if (result != ISC_R_NOMORE)
goto cleanup_rdataset;
return (result);
}
/**************************************************************************/
/*
* Various tests on the database contents (for prerequisites, etc).
*/
/*%
* Function type for predicate functions that compare a database RR 'db_rr'
* against an update RR 'update_rr'.
*/
/*%
* Helper function for rrset_exists().
*/
static isc_result_t
return (ISC_R_EXISTS);
}
/*%
* Utility macro for RR existence checking functions.
*
* If the variable 'result' has the value ISC_R_EXISTS or
* ISC_R_SUCCESS, set *exists to ISC_TRUE or ISC_FALSE,
* respectively, and return success.
*
* If 'result' has any other value, there was a failure.
* Return the failure result code and do not set *exists.
*
* This would be more readable as "do { if ... } while(0)",
* but that form generates tons of warnings on Solaris 2.6.
*/
#define RETURN_EXISTENCE_FLAG \
return ((result == ISC_R_EXISTS) ? \
((result == ISC_R_SUCCESS) ? \
result))
/*%
* Set '*exists' to true iff an rrset of the given type exists,
* to false otherwise.
*/
static isc_result_t
{
}
/*%
* Set '*visible' to true if the RRset exists and is part of the
* visible zone. Otherwise '*visible' is set to false unless a
* error occurs.
*/
static isc_result_t
{
(isc_stdtime_t) 0, NULL,
switch (result) {
case ISC_R_SUCCESS:
break;
/*
* Glue, obscured, deleted or replaced records.
*/
case DNS_R_DELEGATION:
case DNS_R_DNAME:
case DNS_R_CNAME:
case DNS_R_NXDOMAIN:
case DNS_R_NXRRSET:
case DNS_R_EMPTYNAME:
case DNS_R_COVERINGNSEC:
break;
default:
break;
}
return (result);
}
/*%
* Context struct and helper function for name_exists().
*/
static isc_result_t
return (ISC_R_EXISTS);
}
/*%
* Set '*exists' to true iff the given name exists, to false otherwise.
*/
static isc_result_t
{
}
/**************************************************************************/
/*
* Checking of "RRset exists (value dependent)" prerequisites.
*
* In the RFC2136 section 3.2.5, this is the pseudocode involving
* a variable called "temp", a mapping of <name, type> tuples to rrsets.
*
* Here, we represent the "temp" data structure as (non-minimal) "dns_diff_t"
* where each tuple has op==DNS_DIFFOP_EXISTS.
*/
/*%
* A comparison function defining the sorting order for the entries
* in the "temp" data structure. The major sort key is the owner name,
* followed by the type and rdata.
*/
static int
dns_difftuple_t const *a = *ap;
dns_difftuple_t const *b = *bp;
int r;
if (r != 0)
return (r);
if (r != 0)
return (r);
return (r);
}
/**************************************************************************/
/*
* Conditional deletion of RRs.
*/
/*%
* Context structure for delete_if().
*/
typedef struct {
/*%
* Predicate functions for delete_if().
*/
/*%
* Return true always.
*/
static isc_boolean_t
return (ISC_TRUE);
}
/*%
* Return true if the record is a RRSIG.
*/
static isc_boolean_t
}
/*%
* Internal helper function for delete_if().
*/
static isc_result_t
return (result);
} else {
return (ISC_R_SUCCESS);
}
}
/*%
* Conditionally delete RRs. Apply 'predicate' to the RRs
* specified by 'db', 'ver', 'name', and 'type' (which can
* be dns_rdatatype_any to match any type). Delete those
* RRs for which the predicate returns true, and log the
* deletions in 'diff'.
*/
static isc_result_t
{
delete_if_action, &ctx));
}
/**************************************************************************/
/*
* Incremental updating of NSECs and RRSIGs.
*/
/*%
* We abuse the dns_diff_t type to represent a set of domain names
* affected by the update.
*/
static isc_result_t
&dummy_rdata, &tuple));
return (result);
}
static isc_result_t
{
result == ISC_R_SUCCESS;
{
break;
}
if (result == ISC_R_NOMORE)
return (result);
}
/*%
* Helper function for non_nsec_rrset_exists().
*/
static isc_result_t
return (ISC_R_EXISTS);
return (ISC_R_SUCCESS);
}
/*%
* Check whether there is an rrset other than a NSEC or RRSIG NSEC,
* i.e., anything that justifies the continued existence of a name
* after a secure update.
*
* If such an rrset exists, set '*exists' to ISC_TRUE.
* Otherwise, set it to ISC_FALSE.
*/
static isc_result_t
{
}
/*%
* A comparison function for sorting dns_diff_t:s by name.
*/
static int
dns_difftuple_t const *a = *ap;
dns_difftuple_t const *b = *bp;
}
static isc_result_t
dns_difftuple_t *p, *q;
while (p != NULL) {
do {
q = ISC_LIST_NEXT(p, link);
break;
dns_difftuple_free(&q);
} while (1);
p = ISC_LIST_NEXT(p, link);
}
return (result);
}
static isc_result_t
{
(isc_stdtime_t) 0, NULL,
return (ISC_R_SUCCESS);
} else if (result == DNS_R_ZONECUT) {
/*
* We are at the zonecut. Check to see if there
* is a DS RRset.
*/
(isc_stdtime_t) 0, NULL,
else
}
return (ISC_R_SUCCESS);
return (ISC_R_SUCCESS);
} else {
/*
* Silence compiler.
*/
return (result);
}
}
/*%
* In other words, skip empty database nodes and names that
* have had their NSECs removed because they are obscured by
* a zone cut.
*/
static isc_result_t
{
unsigned int wraps = 0;
do {
if (forward)
else
if (result == ISC_R_NOMORE) {
/*
* Wrap around.
*/
if (forward)
else
wraps++;
if (wraps == 2) {
"secure zone with no NSECs");
goto failure;
}
}
/*
* The iterator may hold the tree lock, and
* rrset_exists() calls dns_db_findnode() which
* may try to reacquire it. To avoid deadlock
* we must pause the iterator first.
*/
if (secure) {
dns_rdatatype_nsec, 0, &has_nsec));
} else {
if (result == ISC_R_SUCCESS ||
result == DNS_R_EMPTYNAME ||
result == DNS_R_NXRRSET ||
result == DNS_R_CNAME ||
(result == DNS_R_DELEGATION &&
} else if (result != DNS_R_NXDOMAIN)
break;
}
} while (! has_nsec);
return (result);
}
/*%
* Add a NSEC record for "name", recording the change in "diff".
* The existing NSEC is removed.
*/
static isc_result_t
{
unsigned char buffer[DNS_NSEC_BUFFERSIZE];
/*
* Find the successor name, aka NSEC target.
*/
/*
* Create the NSEC RDATA.
*/
/*
* Delete the old NSEC and record the change.
*/
/*
* Add the new NSEC and record the change.
*/
return (result);
}
/*%
* Add a placeholder NSEC record for "name", recording the change in "diff".
*/
static isc_result_t
{
isc_region_t r;
return (result);
}
static isc_result_t
{
return (result);
}
/*%
* Add RRSIG records for an RRset, recording the change in "diff".
*/
static isc_result_t
{
unsigned int i, j;
/* Get the rdataset to sign. */
if (type == dns_rdatatype_nsec3)
else
#define ALG(x) dst_key_alg(x)
/*
* If we are honoring KSK flags then we need to check that we
* have both KSK and non-KSK keys that are not revoked per
* algorithm.
*/
for (i = 0; i < nkeys; i++) {
if (!dst_key_isprivate(keys[i]))
continue;
} else {
}
for (j = 0; j < nkeys; j++) {
continue;
continue;
else
if (both)
break;
}
}
if (both) {
if (type == dns_rdatatype_dnskey) {
continue;
continue;
continue;
/* Calculate the signature, creating a RRSIG RDATA. */
/* Update the database and journal with the RRSIG. */
/* XXX inefficient - will cause dataset merging */
}
if (!added_sig) {
"found no active private keys, "
"unable to generate any signatures");
}
return (result);
}
/*
* Delete expired RRsigs and any RRsigs we are about to re-sign.
* See also zone.c:del_sigs().
*/
static isc_result_t
{
unsigned int i;
if (result == ISC_R_NOTFOUND)
return (ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS)
goto failure;
if (result == ISC_R_NOTFOUND)
return (ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS)
goto failure;
result == ISC_R_SUCCESS;
for (i = 0; i < nkeys; i++) {
if (!dst_key_isprivate(keys[i]) &&
!dst_key_inactive(keys[i]))
{
/*
* The re-signing code in zone.c
* will mark this as offline.
* Just skip the record for now.
*/
break;
}
break;
}
}
/*
* If there is not a matching DNSKEY then delete the RRSIG.
*/
if (!found)
if (result != ISC_R_SUCCESS)
break;
}
if (result == ISC_R_NOMORE)
return (result);
}
static isc_result_t
unsigned int *sigs)
{
if (result == ISC_R_NOTFOUND)
return (ISC_R_SUCCESS);
if (result != ISC_R_SUCCESS)
return (result);
(isc_stdtime_t) 0, &iter);
if (result != ISC_R_SUCCESS)
goto cleanup_node;
result == ISC_R_SUCCESS;
{
/*
* We don't need to sign unsigned NSEC records at the cut
* as they are handled elsewhere.
*/
if ((type == dns_rdatatype_rrsig) ||
continue;
if (result != ISC_R_SUCCESS)
goto cleanup_iterator;
if (flag)
continue;;
if (result != ISC_R_SUCCESS)
goto cleanup_iterator;
(*sigs)++;
}
if (result == ISC_R_NOMORE)
return (result);
}
/*%
* Update RRSIG, NSEC and NSEC3 records affected by an update. The original
* update, including the SOA serial update but excluding the RRSIG & NSEC
* changes, is in "diff" and has already been applied to "newver" of "db".
* The database version prior to the update is "oldver".
*
* The necessary RRSIG, NSEC and NSEC3 changes will be applied to "newver"
* and added (as a minimal diff) to "diff".
*
* The RRSIGs generated will be valid for 'sigvalidityinterval' seconds.
*/
{
}
struct dns_update_state {
unsigned int magic;
unsigned int nkeys;
};
{
dns_difftuple_t *t, *next;
unsigned int i;
unsigned int sigs = 0;
} else {
return (ISC_R_NOMEMORY);
}
if (result != ISC_R_SUCCESS) {
"could not get zone keys for secure "
"dynamic update");
goto failure;
}
/*
* Do we look at the KSK flag on the DNSKEY to determining which
* keys sign which RRsets? First check the zone option then
* check the keys flags to make sure at least one has a ksk set
* and one doesn't.
*/
DNS_ZONEOPT_UPDATECHECKKSK) != 0);
DNS_ZONEOPT_DNSKEYKSKONLY) != 0);
/*
*/
0, (isc_stdtime_t) 0, &rdataset,
NULL));
/*
* Find all RRsets directly affected by the update, and
* update their RRSIGs. Also build a list of names affected
* by the update in "diffnames".
*/
} else {
}
case sign_updates:
while (t != NULL) {
/*
* Now "name" is a new, unique name affected by the
* update.
*/
/*
* Now "name" and "type" denote a new unique
* RRset affected by the update.
*/
/* Don't sign RRSIGs. */
if (type == dns_rdatatype_rrsig)
goto skip;
/*
* Delete all old RRSIGs covering this type,
* since they are all invalid when the signed
* RRset has changed. We may not be able to
* recreate all of them - tough.
* Special case changes to the zone's DNSKEY
* records to support offline KSKs.
*/
if (type == dns_rdatatype_dnskey)
else
name,
/*
* If this RRset is still visible after the
* update, add a new signature for it.
*/
&flag));
if (flag) {
state->keyset_kskonly));
sigs++;
}
skip:
/* Skip any other updates to the same RRset. */
while (t != NULL &&
{
link);
t = next;
}
}
return (DNS_R_CONTINUE);
}
"updated data signatures");
/*FALLTHROUGH*/
case remove_orphaned:
/* Remove orphaned NSECs and RRSIG NSECs. */
t != NULL;
t = ISC_LIST_NEXT(t, link))
{
if (!flag) {
}
}
"removed any orphaned NSEC records");
/*
* See if we need to build NSEC or NSEC3 chains.
*/
&build_nsec3));
if (!build_nsec) {
goto next_state;
}
"rebuilding NSEC chain");
/*FALLTHROUGH*/
case build_chain:
/*
* When a name is created or deleted, its predecessor needs to
* have its NSEC updated.
*/
t != NULL;
t = ISC_LIST_NEXT(t, link))
{
&existed));
else
continue;
/*
* Find the predecessor.
* When names become obscured or unobscured in this
* update transaction, we may find the wrong
* predecessor because the NSECs have not yet been
* updated to reflect the delegation change. This
* should not matter because in this case, the correct
* predecessor is either the delegation node or a
* newly unobscured node, and those nodes are on the
* "affected" list in any case.
*/
}
/*
* Find names potentially affected by delegation changes
* (obscured by adding an NS or DNAME, or unobscured by
* removing one).
*/
t != NULL;
t = ISC_LIST_NEXT(t, link))
{
dns_rdatatype_ns, 0,
&ns_existed));
else
&dname_existed));
else
dns_rdatatype_ns, 0, &ns_exists));
&dname_exists));
if ((ns_exists || dname_exists) ==
(ns_existed || dname_existed))
continue;
/*
* There was a delegation change. Mark all subdomains
* of t->name as potentially needing a NSEC update.
*/
}
/*FALLTHROUGH*/
case process_nsec:
/*
* NSECs to make it so. We don't know the final NSEC targets
* yet, so we just create placeholder NSECs with arbitrary
* contents to indicate that their respective owner names
* should be part of the NSEC chain.
*/
if (! exists)
goto unlink;
if (!flag) {
/*
* This name is obscured. Delete any
* existing NSEC record.
*/
dns_rdatatype_any, 0, NULL,
diff));
} else {
/*
* This name is not obscured. It needs to have
* a NSEC unless it is the at the origin, in
* which case it should already exist if there
* is a complete NSEC chain and if there isn't
* a complete NSEC chain we don't want to add
* one as that would signal that there is a
* complete NSEC chain.
*/
0, &flag));
if (!flag)
}
&sigs));
}
return (DNS_R_CONTINUE);
}
/*
* Now we know which names are part of the NSEC chain.
* Make them all point at their correct targets.
*/
t != NULL;
t = ISC_LIST_NEXT(t, link))
{
dns_rdatatype_nsec, 0, &flag));
if (flag) {
/*
* There is a NSEC, but we don't know if it
* is correct. Delete it and create a correct
* one to be sure. If the update was
* unnecessary, the diff minimization
* will take care of eliminating it from the
* journal, IXFRs, etc.
*
* The RRSIG bit should always be set in the
* NSECs we generate, because they will all
* get RRSIG NSECs.
* (XXX what if the zone keys are missing?).
* Because the RRSIG NSECs have not necessarily
* been created yet, the correctness of the
* bit mask relies on the assumption that NSECs
* are only created if there is other data, and
* if there is other data, there are other
* RRSIGs.
*/
}
}
/*
* Minimize the set of NSEC updates so that we don't
* have to regenerate the RRSIG NSECs for NSECs that were
* replaced with identical ones.
*/
}
"signing rebuilt NSEC chain");
/*FALLTHROUGH*/
case sign_nsec:
/* Update RRSIG NSECs. */
{
if (t->op == DNS_DIFFOP_DEL) {
} else if (t->op == DNS_DIFFOP_ADD) {
state->keyset_kskonly));
sigs++;
} else {
INSIST(0);
}
return (DNS_R_CONTINUE);
}
/*FALLTHROUGH*/
case update_nsec3:
/* Record our changes for the journal. */
dns_diff_appendminimal(diff, &t);
}
{
dns_diff_appendminimal(diff, &t);
}
if (!build_nsec3) {
"no NSEC3 chains to rebuild");
goto failure;
}
"rebuilding NSEC3 chains");
/*
* Find names potentially affected by delegation changes
* (obscured by adding an NS or DNAME, or unobscured by
* removing one).
*/
while (t != NULL) {
t = ISC_LIST_NEXT(t, link);
continue;
}
0, &ns_existed));
else
&dname_existed));
else
0, &ns_exists));
&dname_exists));
goto nextname;
/*
* There was a delegation change. Mark all subdomains
* of t->name as potentially needing a NSEC3 update.
*/
t = ISC_LIST_NEXT(t, link);
}
/*FALLTHROUGH*/
case process_nsec3:
&unsecure));
if (!flag) {
dns_rdatatype_any, 0, NULL,
diff));
} else {
&sigs));
}
return (DNS_R_CONTINUE);
}
/*
* Minimize the set of NSEC3 updates so that we don't
* have to regenerate the RRSIG NSEC3s for NSEC3s that were
* replaced with identical ones.
*/
}
"signing rebuilt NSEC3 chain");
/*FALLTHROUGH*/
case sign_nsec3:
/* Update RRSIG NSEC3s. */
{
if (t->op == DNS_DIFFOP_DEL) {
} else if (t->op == DNS_DIFFOP_ADD) {
state->keyset_kskonly));
sigs++;
} else {
INSIST(0);
}
return (DNS_R_CONTINUE);
}
/* Record our changes for the journal. */
dns_diff_appendminimal(diff, &t);
}
{
dns_diff_appendminimal(diff, &t);
}
break;
default:
INSIST(0);
}
}
return (result);
}
static isc_stdtime_t
}
switch (method) {
case dns_updatemethod_none:
return (serial);
return (now);
break;
case dns_updatemethod_date:
return (new_serial);
break;
}
/* RFC1982 */
if (serial == 0)
serial = 1;
return (serial);
}