update.c revision 2c35c682362049f823248542e07e7dca4008b986
2N/A * Copyright (C) 2004-2011 Internet Systems Consortium, Inc. ("ISC") 2N/A * Copyright (C) 1999-2003 Internet Software Consortium. 2N/A * Permission to use, copy, modify, and/or distribute this software for any 2N/A * purpose with or without fee is hereby granted, provided that the above 2N/A * copyright notice and this permission notice appear in all copies. 2N/A * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 2N/A * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 2N/A * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 2N/A * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 2N/A * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 2N/A * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 2N/A * PERFORMANCE OF THIS SOFTWARE. 2N/A/* $Id: update.c,v 1.197 2011/08/31 06:49:09 marka Exp $ */ * 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". * Return TRUE if NS_CLIENTATTR_TCP is set in the attributes other FALSE. /**************************************************************************/ /**************************************************************************/ /**************************************************************************/ level,
"updating zone '%s/%s': %s",
* Increment updated-related statistics counters. * Check if we could have queried for the contents of this zone or * if the zone is potentially updateable. * If the zone can potentially be updated and the check failed then * log a error otherwise we log a informational message. "update '%s/%s' denied due to allow-query",
* Override the default acl logging when checking whether a client * can update the zone or whether we can forward the request to the * master based on IP address. * 'message' contains the type of operation that is being attempted. * 'slave' indicates if this is a slave zone. If 'acl' is NULL then * If the zone has no access controls configured ('acl' == NULL && * 'has_ssutable == ISC_FALS) log the attempt at info, otherwise * If the request was signed log that we received it. 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. * 'ssu_check_t' is used to pass the arguments to * dns_ssutable_checkrules() to the callback function /* The ownername of the record to be updated. */ /* The signature's name if the request was signed. */ /* The address of the client if the request was received via TCP. */ /* The ssu table to check against. */ /* the key used for TKEY requests */ * 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-minimal) "dns_diff_t" * where each tuple 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 * an RRSIG nor an NSEC3PARAM nor a NSEC. * 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. * Additionally replace RRSIG that have been generated by the same key * for the same type. This simplifies refreshing a offline KSK by not * requiring that the old RRSIG be deleted. It also simplifies key * rollover by only requiring that the new RRSIG be added. * Replace existing RRSIG with the same keyid, * 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. * Replace NSEC3PARAM records that only differ by the * 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. /**************************************************************************/ * 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");
* If there is a raw (unsigned) zone associated with this * zone then it processes the UPDATE request. * 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, * RFC 3658, 2.2 Protocol Change, * "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)",
* Prevent the zone entering a inconsistent state where * NSEC only DNSKEYs are present with NSEC3 chains. /* Scan the tuples for an NSEC-only DNSKEY or an NSEC3PARAM */ /* Check existing DB for NSEC-only DNSKEY */ * An NSEC3PARAM update can proceed without a DNSKEY (it * will trigger a delayed change), so we can ignore /* Check existing DB for NSEC3 */ /* Refuse to allow NSEC3 with NSEC-only keys */ "NSEC only DNSKEYs and NSEC3 chains not allowed");
/* Verify NSEC3 params */ "too many NSEC3 iterations (%u) for " * Delay NSEC3PARAM changes as they need to be applied to the whole zone. "checking for NSEC3PARAM changes");
* Extract NSEC3PARAM tuples from list. * Extract TTL changes pairs, we don't need to convert these to * Any adds here will contain the final * Walk the temp_diff list looking for the * If we have not found a pair move onto the next * Find the next tuple to be processed before * unlinking then complete moving the pair to 'diff'. * Preserve any ongoing changes from a BIND 9.6.x upgrade. * Any NSEC3PARAM records with flags other than OPTOUT named * in managing and should not be touched so revert such changes * taking into account any TTL change of the NSEC3PARAM RRset. * If we havn't had any adds then the tuple->ttl must * be the original ttl and should be used for any * We now have just the actual changes to the NSEC3PARAM RRset. * Convert the adds to delayed adds and the deletions into delayed * If we havn't had any adds then the tuple->ttl must be the * original ttl and should be used for any future changes. * Look for any deletes which match this ADD ignoring * flags. We don't need to explictly remove them as * they will be removed a side effect of processing * Create a private-type record to signal that * If the zone is not currently capable of * supporting an NSEC3 chain, then we set the * INITIAL flag to indicate that these parameters * See if this CREATE request already exists. * Remove any existing CREATE request to add an * otherwise indentical chain with a reversed * Find the next tuple to be processed and remove the * See if we already have a REMOVE request in progress. * Extract the changes to be rolled back. * Allow records which indicate that a zone has been * signed with a DNSKEY to be removed. * Add records to cause the delayed signing of the zone by added DNSKEY * to remove the RRSIG records generated by a deleted DNSKEY. * Extract the DNSKEY tuples from the list. * Extract TTL changes pairs, we don't need signing records for these. * Walk the temp_diff list looking for the * If we have not found a pair move onto the next * Find the next tuple to be processed before * unlinking then complete moving the pair to 'diff'. * Process the remaining DNSKEY entries. * Remove any record which says this operation has already * Update message processing can leak record existance information * so check that we are allowed to query this zone. Additionally * if we would refuse all updates for this zone we bail out here. * Get old and new versions now that queryacl has been checked. "prerequisite TTL is not zero");
"prerequisite name is out of zone");
"class ANY prerequisite " /* RRset does not exist. */ "'rrset exists (value independent)' " "prerequisite not satisfied");
"class NONE prerequisite " "'rrset does not exist' " /* "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. "because the zone is frozen. Use " "'rndc thaw' to re-enable updates.");
* 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 NSEC3 updates are not allowed " "explicit NSEC updates are not allowed " "explicit RRSIG updates are currently " "not supported in secure zones except " * If this is a TCP connection then pass the * address of the client through for tcp-self * and 6to4-self otherwise pass NULL. This * provides weak address based authentication. "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 %s record " "attempt to add non-CNAME " "alongside CNAME ignored");
"attempt to add a private type " "(%u) record rejected internal " * Ignore attempts to add NSEC3PARAM records * with any flags other than OPTOUT. "attempt to add NSEC3PARAM " "record with non OPTOUT " "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. "update rejected: post update name server " * 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. "update rejected: all DNSKEY " "'dnssec-secure-to-insecure' " * We are transitioning from secure to insecure. * Cause all NSEC3 chains to be deleted. When the * the last signature for the DNSKEY records are * remove any NSEC chain present will also be removed. * 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. * Cause the zone to be signed with the key that we * have just added or have the corresponding signatures * Note: we are already committed to this course of action. "dns_zone_signwithkey failed: %s",
* Cause the zone to add/delete NSEC3 chains for the * deferred NSEC3PARAM changes. * Note: we are already committed to this course of action. "dns_zone_addnsec3chain failed: %s",
* The reason for failure should have been logged at this point. * Update forwarding support. * This may take some time so replace this client.