update.c revision ec5347e2c775f027573ce5648b910361aa926c01
* This module implements dynamic update as in RFC2136. * - document strict minimality /**************************************************************************/ * Log level for tracing dynamic update protocol requests. * 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' * Fail unconditionally with result 'code', which must not * be ISC_R_SUCCESS. The reason for failure presumably has * The test against ISC_R_SUCCESS is there to keep the Solaris compiler * from complaining about "end-of-loop code not reached". * 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". const char *
_what =
"failed"; \
_what =
"unsuccessful"; \
"update %s: %s (%s)",
_what, \
const char *
_what =
"failed"; \
_what =
"unsuccessful"; \
const char *
_what =
"failed"; \
_what =
"unsuccessful"; \
"update %s: %s/%s: %s (%s)", \
* 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". /**************************************************************************/ /**************************************************************************/ /**************************************************************************/ level,
"updating zone '%s/%s': %s",
const char *
msg =
"denied";
* Update a single RR in version 'ver' of 'db' and log the * \li '*tuple' == NULL. Either the tuple is freed, or its * ownership has been transferred to the diff. * Create a singleton diff. * Apply it to the database. * Merge it into the current pending journal entry. * Do not clear temp_diff. * Perform the updates in 'updates' in version 'ver' of 'db' and log the * \li 'updates' is empty. /**************************************************************************/ * 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(). * Internal helper function for foreach_node_rr(). * 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. * 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 * 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, * If 'action' returns an error, abort iteration and return the error. /**************************************************************************/ * 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(). * 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. * Set '*exists' to true iff an rrset of the given type exists, * Helper function for cname_incompatible_rrset_exists. * Check whether there is an rrset incompatible with adding a CNAME RR, * i.e., anything but another CNAME (which can be replaced) or a * DNSSEC RR (which can coexist). * If such an incompatible rrset exists, set '*exists' to ISC_TRUE. * Otherwise, set it to ISC_FALSE. * Helper function for rr_count(). * Count the number of RRs of 'type' belonging to 'name' in 'ver' of 'db'. * Context struct and helper function for name_exists(). * Set '*exists' to true iff the given name exists, to false otherwise. * If we're deleting all records, it's ok to delete RRSIG and NSEC even * if we're normally not allowed to. /**************************************************************************/ * 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-minimial) "dns_diff_t" * where each typle has op==DNS_DIFFOP_EXISTS. * Append a tuple asserting the existence of the RR with * 'name' and 'rdata' to 'diff'. * Compare two rdatasets represented as sorted lists of tuples. * All list elements must have the same owner name and type. * Return ISC_R_SUCCESS if the rdatasets are equal, rcode(dns_rcode_nxrrset) * 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. * Check the "RRset exists (value dependent)" prerequisite information * in 'temp' against the contents of the database 'db'. * Return ISC_R_SUCCESS if the prerequisites are satisfied, * rcode(dns_rcode_nxrrset) if not. * 'temp' must be pre-sorted. * For each name and type in the prerequisites, * construct a sorted rdata list of the corresponding * database contents, and compare the lists. /* A new unique name begins here. */ /* A new unique type begins here. */ * Collect all database RRs for this name and type * onto d_rrs and sort them. * Collect all update RRs for this name and type * onto u_rrs. No need to sort them here - * they are already sorted. /* Compare the two sorted lists. */ * We are done with the tuples, but we can't free * them yet because "name" still points into one * of them. Move them on a temporary list. /**************************************************************************/ * Conditional deletion of RRs. * Context structure for delete_if(). * Predicate functions for delete_if(). * Return true iff 'db_rr' is neither a SOA nor an NS RR nor * Return true iff 'db_rr' is neither a RRSIG nor a NSEC. * Return true iff the two RRs have identical rdata. * XXXRTH This is not a problem, but we should consider creating * dns_rdata_equal() (that used dns_name_equal()), since it * would be faster. Not a priority. * Return true iff 'update_rr' should replace 'db_rr' according * to the special RFC2136 rules for CNAME, SOA, and WKS records. * RFC2136 does not mention NSEC or DNAME, but multiple NSECs or DNAMEs * make little sense, so we replace those, too. * Compare the address and protocol fields only. These * form the first five bytes of the RR data. Do a * raw binary comparison; unpacking the WKS RRs using * dns_rdata_tostruct() might be cleaner in some ways, * but it would require us to pass around an mctx. * Internal helper function for delete_if(). * 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 /**************************************************************************/ * Prepare an RR for the addition of the new RR 'ctx->update_rr', * with TTL 'ctx->update_rr_ttl', to its rdataset, by deleting * the RRs if it is replaced by the new RR or has a conflicting TTL. * The necessary changes are appended to ctx->del_diff and ctx->add_diff; * we need to do all deletions before any additions so that we don't run * into transient states with conflicting TTLs. * If the update RR is a "duplicate" of the update RR, * the update should be silently ignored. * If this RR is "equal" to the update RR, it should * be deleted before the update RR is added. * If this RR differs in TTL from the update RR, * its TTL must be adjusted. /**************************************************************************/ * Miscellaneous subroutines. * Extract a single update RR from 'section' of dynamic update message * 'msg', with consistency checking. * Stores the owner name, rdata, and TTL of the update RR at 'name', * 'rdata', and 'ttl', respectively. * Increment the SOA serial number of database 'db', version 'ver'. * Replace the SOA record in the database, and log the * XXXRTH Failures in this routine will be worth logging, when * we have a logging system. Failure to find the zonename * or the SOA rdataset warrant at least an UNEXPECTED_ERROR(). * Check that the new SOA record at 'update_rdata' does not * illegally cause the SOA serial number to decrease or stay * unchanged relative to the existing SOA in 'db'. * Sets '*ok' to ISC_TRUE if the update is legal, ISC_FALSE if not. * William King points out that RFC2136 is inconsistent about * the case where the serial number stays unchanged: * section 3.4.2.2 requires a server to ignore a SOA update request * if the serial number on the update SOA is less_than_or_equal to * section 3.6 requires a server to ignore a SOA update request if * the serial is less_than the zone SOA serial. * Paul says 3.4.2.2 is correct. /**************************************************************************/ * Incremental updating of NSECs and RRSIGs. #
define MAXZONEKEYS 32 /*%< Maximum number of zone keys supported. */ * We abuse the dns_diff_t type to represent a set of domain names * affected by the update. * Helper function for non_nsec_rrset_exists(). * Check whether there is an rrset other than a NSEC or RRSIG NSEC, * i.e., anything that justifies the continued existence of a name * If such an rrset exists, set '*exists' to ISC_TRUE. * Otherwise, set it to ISC_FALSE. * A comparison function for sorting dns_diff_t:s by name. * We are at the zonecut. The name will have an NSEC, but * non-delegation will be omitted from the type bit map. * In other words, skip empty database nodes and names that * have had their NSECs removed because they are obscured by "secure zone with no NSECs");
* 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. * Add a NSEC record for "name", recording the change in "diff". * The existing NSEC is removed. * Find the successor name, aka NSEC target. * Delete the old NSEC and record the change. * Add the new NSEC and record the change. * Add a placeholder NSEC record for "name", recording the change in "diff". unsigned char data[
1] = { 0 };
/* The root domain, no bits. */ * Add RRSIG records for an RRset, recording the change in "diff". unsigned char data[
1024];
/* XXX */ /* Get the rdataset to sign. */ for (i = 0; i <
nkeys; i++) {
/* Calculate the signature, creating a RRSIG RDATA. */ /* Update the database and journal with the RRSIG. */ /* XXX inefficient - will cause dataset merging */ * Update RRSIG and NSEC records affected by an update. The original * update, including the SOA serial update but exluding 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 and NSEC changes will be applied to "newver" * and added (as a minimal diff) to "diff". * The RRSIGs generated will be valid for 'sigvalidityinterval' seconds. "could not get zone keys for secure dynamic update");
* 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 atleast one has a ksk set * Get the NSEC's TTL from the SOA MINIMUM field. * Find all RRsets directly affected by the update, and * update their RRSIGs. Also build a list of names affected * by the update in "diffnames". /* Now "name" is a new, unique name affected by the update. */ * Now "name" and "type" denote a new unique RRset * affected by the update. * 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. * If this RRset still exists after the update, * add a new signature for it. /* Skip any other updates to the same RRset. */ /* Remove orphaned NSECs and RRSIG NSECs. */ * When a name is created or deleted, its predecessor needs to * 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 * There was a delegation change. Mark all subdomains * of t->name as potentially needing a NSEC update. * Determine which names should have NSECs, and delete/create * 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 * This name is obscured. Delete any * This name is not obscured. It should have a NSEC. * Now we know which names are part of the NSEC chain. * Make them all point at their correct targets. * 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, * 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. /* Update RRSIG NSECs. */ /* Record our changes for the journal. */ for (i = 0; i <
nkeys; i++)
/**************************************************************************/ * The actual update code in all its glory. We try to follow * the RFC2136 pseudocode as closely as possible. "could not create update response message: %s",
* Interpret the zone section. * The zone section must contain exactly one "question", and * it must be of type SOA. "update zone section contains non-SOA");
"update zone section contains multiple RRs");
/* The zone section must have exactly one name. */ "update zone section contains multiple RRs");
* We can now fail due to a bad signature as we now know * that we are the master. * We failed without having sent an update event to the zone. * We are still in the client task context, so we can * simply give an error response without switching tasks. * DS records are not allowed to exist without corresponding NS records, * "DS RRsets MUST NOT appear at non-delegation points or at a zone's apex". * This implements the post load integrity checks for mx records. char tmp[
sizeof(
"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:123.123.123.123.")];
* Check if we will error out if we attempt to reload the "%s/MX: warning: '%s': %s",
* Check zone integrity checks. "%s/MX '%s' has no address records " "%s/MX '%s' is a CNAME (illegal)",
"%s/MX '%s' is below a DNAME '%s' (illegal)",
"prerequisite name is out of zone");
"class ANY prerequisite " "'name in use' prerequisite " /* RRset does not exist. */ "'rrset exists (value independent)' " "prerequisite not satisfied");
"class NONE prerequisite " "'name not in use' prerequisite " "'rrset does not exist' " "prerequisite not satisfied");
/* "temp<rr.name, rr.type> += rr;" */ "temp entry creation failed: %s",
* Perform the final check of the "rrset exists (value dependent)" * Sort the prerequisite records by owner name, "prerequisite not satisfied");
"'RRset exists (value dependent)' " "prerequisite not satisfied");
* Check Requestor's Permissions. It seems a bit silly to do this * only after prerequisite testing, but that is what RFC2136 says. * Perform the Update Section Prescan. "update RR is outside zone");
* Check for meta-RRs. The RFC2136 pseudocode says * check for ANY|AXFR|MAILA|MAILB, but the text adds * "or any other QUERY metatype" "update RR has incorrect class %d",
* draft-ietf-dnsind-simple-secure-update-01 says * "Unlike traditional dynamic update, the client * is forbidden from updating NSEC records." "explicit NSEC updates are not allowed " "explicit RRSIG updates are currently not " "supported in secure zones");
"rejected by secure update");
"rejected by secure update");
"update section prescan OK");
* Process the Update Section. * RFC1123 doesn't allow MF and MD in master zones. */ "attempt to add %s ignored",
"attempt to add wildcard NS record" "attempt to add non-CNAME " "alongside CNAME ignored");
"warning: ownername '%s' contains " "a non-terminal wildcard",
namestr);
"adding an RR at '%s' %s",
/* Prepare the affected RRset for the addition. */ "delete all rrsets from " "attempt to delete all SOA " "or NS records ignored");
"deleting rrset at '%s' %s",
* The (name == zonename) condition appears in * RFC2136 3.4.2.4 but is missing from the pseudocode. * If any changes were made, increment the SOA serial number, * update RRSIGs and NSECs (if zone is secure), and write the update * Increment the SOA serial, but only if it was not * changed as a result of an update operation. * XXXRTH Just a note that this committing code will have * to change to handle databases that need two-phase * commit, but this isn't a priority. "committing update transaction");
* Mark the zone as dirty so that it will be written to disk. * Notify slaves of the change we just made. * The reason for failure should have been logged at this point. * Update forwarding support.