diff --git a/bin/named/include/named/query.h b/bin/named/include/named/query.h index 83a5529de0..29b4c74301 100644 --- a/bin/named/include/named/query.h +++ b/bin/named/include/named/query.h @@ -28,14 +28,17 @@ struct ns_query { unsigned int attributes; dns_name_t * qname; + dns_name_t * origqname; unsigned int dboptions; ISC_LIST(isc_dynbuffer_t) namebufs; + ISC_LIST(dns_name_t) tmpnames; + ISC_LIST(dns_rdataset_t) tmprdatasets; }; #define NS_QUERYATTR_RECURSIONOK 0x01 #define NS_QUERYATTR_CACHEOK 0x02 #define NS_QUERYATTR_PARTIALANSWER 0x04 -#define NS_QUERYATTR_ALIASCHAIN 0x08 +#define NS_QUERYATTR_NAMEBUFUSED 0x08 isc_result_t ns_query_init(ns_client_t *client); diff --git a/bin/named/query.c b/bin/named/query.c index 4d5b94ddf2..0e9fe558c4 100644 --- a/bin/named/query.c +++ b/bin/named/query.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -59,9 +60,16 @@ query_reset(ns_client_t *client, isc_boolean_t everything) { isc_dynbuffer_free(client->mctx, &dbuf); } } + /* + * We don't need to free items from these two lists because they + * will be taken care of when the message is reset. + */ + ISC_LIST_INIT(client->query.tmpnames); + ISC_LIST_INIT(client->query.tmprdatasets); client->query.attributes = (NS_QUERYATTR_RECURSIONOK| NS_QUERYATTR_CACHEOK); client->query.qname = NULL; + client->query.origqname = NULL; client->query.dboptions = 0; } @@ -119,6 +127,80 @@ query_getnamebuf(ns_client_t *client) { return (dbuf); } +static inline void +query_keepname(ns_client_t *client, dns_name_t *name, isc_dynbuffer_t *dbuf) { + isc_region_t r; + + /* + * 'name' is using space in 'dbuf', but 'dbuf' has not yet been + * adjusted to take account of that. We do the adjustment. + */ + + REQUIRE((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) != 0); + + dns_name_toregion(name, &r); + isc_buffer_add(&dbuf->buffer, r.length); + dns_name_setbuffer(name, NULL); + client->query.attributes &= ~NS_QUERYATTR_NAMEBUFUSED; +} + +static inline void +query_releasename(ns_client_t *client, dns_name_t **namep) { + dns_name_t *name = *namep; + + ISC_LIST_APPEND(client->query.tmpnames, name, link); + if (dns_name_hasbuffer(name)) { + INSIST((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) + != 0); + client->query.attributes &= ~NS_QUERYATTR_NAMEBUFUSED; + } + *namep = NULL; +} + +static inline dns_name_t * +query_newname(ns_client_t *client, isc_dynbuffer_t *dbuf, + isc_buffer_t *nbuf) +{ + dns_name_t *name; + isc_region_t r; + isc_result_t result; + + REQUIRE((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) == 0); + + name = ISC_LIST_HEAD(client->query.tmpnames); + if (name == NULL) { + result = dns_message_gettempname(client->message, &name); + if (result != ISC_R_SUCCESS) + return (NULL); + } else + ISC_LIST_UNLINK(client->query.tmpnames, name, link); + isc_buffer_available(&dbuf->buffer, &r); + isc_buffer_init(nbuf, r.base, r.length, ISC_BUFFERTYPE_BINARY); + dns_name_init(name, NULL); + dns_name_setbuffer(name, nbuf); + client->query.attributes |= NS_QUERYATTR_NAMEBUFUSED; + + return (name); +} + +static inline dns_rdataset_t * +query_newrdataset(ns_client_t *client) { + dns_rdataset_t *rdataset; + isc_result_t result; + + rdataset = ISC_LIST_HEAD(client->query.tmprdatasets); + if (rdataset == NULL) { + result = dns_message_gettemprdataset(client->message, + &rdataset); + if (result != ISC_R_SUCCESS) + return (NULL); + } else + ISC_LIST_UNLINK(client->query.tmprdatasets, rdataset, link); + dns_rdataset_init(rdataset); + + return (rdataset); +} + isc_result_t ns_query_init(ns_client_t *client) { ISC_LIST_INIT(client->query.namebufs); @@ -129,14 +211,13 @@ ns_query_init(ns_client_t *client) { static isc_result_t query_addadditional(void *arg, dns_name_t *name, dns_rdatatype_t type) { ns_client_t *client = arg; - isc_result_t result; + isc_result_t result, eresult; dns_dbnode_t *node; dns_db_t *db; dns_name_t *fname, *mname; dns_rdataset_t *rdataset; dns_section_t section; isc_dynbuffer_t *dbuf; - isc_region_t r; isc_buffer_t b; REQUIRE(NS_CLIENT_VALID(client)); @@ -149,33 +230,31 @@ query_addadditional(void *arg, dns_name_t *name, dns_rdatatype_t type) { */ /* - * Get the resources we'll need. + * Initialization. + */ + eresult = ISC_R_SUCCESS; + fname = NULL; + rdataset = NULL; + db = NULL; + node = NULL; + + /* + * Get some resources... */ dbuf = query_getnamebuf(client); if (dbuf == NULL) - return (ISC_R_NOMEMORY); - isc_buffer_available(&dbuf->buffer, &r); - isc_buffer_init(&b, r.base, r.length, ISC_BUFFERTYPE_BINARY); - fname = NULL; - result = dns_message_gettempname(client->message, &fname); - if (result != ISC_R_SUCCESS) - return (result); - dns_name_init(fname, NULL); - dns_name_setbuffer(fname, &b); - rdataset = NULL; - result = dns_message_gettemprdataset(client->message, - &rdataset); - if (result != ISC_R_SUCCESS) - return (result); - dns_rdataset_init(rdataset); + goto cleanup; + fname = query_newname(client, dbuf, &b); + rdataset = query_newrdataset(client); + if (fname == NULL || rdataset == NULL) + goto cleanup; /* * Find a database to answer the query. */ - db = NULL; result = dns_dbtable_find(client->view->dbtable, name, &db); if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) - return (ISC_R_SUCCESS); + goto cleanup; /* * Now look for an answer in the database. @@ -183,43 +262,15 @@ query_addadditional(void *arg, dns_name_t *name, dns_rdatatype_t type) { node = NULL; result = dns_db_find(db, name, NULL, type, client->query.dboptions, 0, &node, fname, rdataset); - switch (result) { case DNS_R_SUCCESS: case DNS_R_GLUE: - dns_db_detachnode(db, &node); - dns_db_detach(&db); break; - case DNS_R_NXRDATASET: - case DNS_R_ZONECUT: - dns_db_detachnode(db, &node); - dns_db_detach(&db); - return (ISC_R_SUCCESS); - case DNS_R_CNAME: - case DNS_R_DNAME: - case DNS_R_DELEGATION: - dns_rdataset_disassociate(rdataset); - dns_db_detachnode(db, &node); - dns_db_detach(&db); - return (ISC_R_SUCCESS); default: - dns_db_detach(&db); - return (ISC_R_SUCCESS); + goto cleanup; } - /* - * Record the space we consumed from the namebuf. - */ - dns_name_toregion(fname, &r); - isc_buffer_add(&dbuf->buffer, r.length); - - /* - * This is not strictly necessary, but is done to emphasize - * that the name's dedicated buffer, which is on our stack, - * is no longer used. It also prevents any later accidental - * use of the dedicated buffer. - */ - dns_name_setbuffer(fname, NULL); + query_keepname(client, fname, dbuf); /* * Suppress duplicates. @@ -234,13 +285,14 @@ query_addadditional(void *arg, dns_name_t *name, dns_rdatatype_t type) { /* * We've already got this RRset in the response. */ - dns_rdataset_disassociate(rdataset); - return (ISC_R_SUCCESS); + goto cleanup; } } ISC_LIST_APPEND(fname->list, rdataset, link); + rdataset = NULL; dns_message_addname(client->message, fname, DNS_SECTION_ADDITIONAL); + fname = NULL; /* * In a few cases, we want to add additional data for additional @@ -264,19 +316,32 @@ query_addadditional(void *arg, dns_name_t *name, dns_rdatatype_t type) { * XXXRTH We should lower the priority here. Alternatively, * we could raise the priority of glue records. */ - return (query_addadditional(client, name, dns_rdatatype_key)); + eresult = query_addadditional(client, name, dns_rdatatype_key); } else if (type == dns_rdatatype_srv) { /* * If we're adding SRV records to the additional data * section, it's helpful if we add the SRV additional data * as well. */ - return (dns_rdataset_additionaldata(rdataset, - query_addadditional, - client)); + eresult = dns_rdataset_additionaldata(rdataset, + query_addadditional, + client); } - return (ISC_R_SUCCESS); + cleanup: + if (rdataset != NULL) { + if (rdataset->methods != NULL) + dns_rdataset_disassociate(rdataset); + ISC_LIST_APPEND(client->query.tmprdatasets, rdataset, link); + } + if (fname != NULL) + query_releasename(client, &fname); + if (node != NULL) + dns_db_detachnode(db, &node); + if (db != NULL) + dns_db_detach(&db); + + return (eresult); } static inline void @@ -310,282 +375,612 @@ query_addrdataset(ns_client_t *client, dns_name_t *fname, } } -#if 0 -static isc_result_t -newfind(ns_client_t *client) { - isc_boolean_t cache_ok = ISC_FALSE; - isc_boolean_t recursion_ok = ISC_FALSE; - dns_db_t *db; +#define ANSWERED(rds) (((rds)->attributes & DNS_RDATASETATTR_ANSWERED) != 0) + +static inline void +query_addrrset(ns_client_t *client, dns_name_t **namep, + dns_rdataset_t **rdatasetp, isc_dynbuffer_t *dbuf, + dns_section_t section) +{ + dns_name_t *name, *mname; + dns_rdataset_t *rdataset, *mrdataset; + isc_result_t result; + + name = *namep; + rdataset = *rdatasetp; + mname = NULL; + mrdataset = NULL; + result = dns_message_findname(client->message, section, + name, rdataset->type, + &mname, &mrdataset); + if (result == ISC_R_SUCCESS) { + /* + * We've already got an RRset of the given name and type. + * There's nothing else to do; + */ + return; + } + + if (result == DNS_R_NXDOMAIN) { + /* + * The name doesn't exist. + */ + if (dbuf != NULL) + query_keepname(client, name, dbuf); + dns_message_addname(client->message, name, section); + *namep = NULL; + mname = name; + } else + RUNTIME_CHECK(result == DNS_R_NXRDATASET); + + query_addrdataset(client, mname, rdataset); + *rdatasetp = NULL; +} + +static inline isc_result_t +query_addsoa(ns_client_t *client, dns_db_t *db) { + dns_name_t *name, *fname; + dns_dbnode_t *node; + isc_result_t result, eresult; + dns_fixedname_t foundname; + dns_rdataset_t *rdataset; /* - * First we must find the right database to search + * Initialization. */ - db = NULL; - result = dns_dbtable_find(client->view->dbtable, - client->query.qname, &db); - if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) { - if (PARTIALANSWER(client)) { - /* - * If we've already got an answer we can go with, - * use it. Otherwise there's nothing we can do. - */ - return (ISC_R_SUCCESS); - } - return (DNS_R_SERVFAIL); - } -} -#endif + eresult = ISC_R_SUCCESS; + name = NULL; + rdataset = NULL; + node = NULL; + dns_fixedname_init(&foundname); + fname = dns_fixedname_name(&foundname); -static isc_result_t -find(ns_client_t *client, dns_rdatatype_t type) { - isc_result_t result; - dns_dbnode_t *node; + /* + * Get resources and make 'name' be the database origin. + */ + result = dns_message_gettempname(client->message, &name); + if (result != ISC_R_SUCCESS) + return (result); + dns_name_init(name, NULL); + dns_name_clone(dns_db_origin(db), name); + rdataset = query_newrdataset(client); + if (rdataset == NULL) { + eresult = DNS_R_SERVFAIL; + goto cleanup; + } + + /* + * Find the SOA. + */ + result = dns_db_find(db, name, NULL, dns_rdatatype_soa, 0, 0, &node, + fname, rdataset); + if (result != ISC_R_SUCCESS) { + /* + * This is bad. We tried to get the SOA RR at the zone top + * and it didn't work! + * + * The note above about temporary leakage applies here too. + */ + eresult = DNS_R_SERVFAIL; + } else + query_addrrset(client, &name, &rdataset, NULL, + DNS_SECTION_AUTHORITY); + + cleanup: + if (rdataset != NULL) { + if (rdataset->methods != NULL) + dns_rdataset_disassociate(rdataset); + ISC_LIST_APPEND(client->query.tmprdatasets, rdataset, link); + } + if (name != NULL) + query_releasename(client, &name); + if (node != NULL) + dns_db_detachnode(db, &node); + + return (eresult); +} + +static inline isc_result_t +query_checktype(dns_rdatatype_t type) { + + /* + * XXXRTH SIG is here only temporarily. + * OPT still needs to be added. + * Should get help with this from rdata.c + */ + switch (type) { + case dns_rdatatype_sig: + return (DNS_R_NOTIMP); + case dns_rdatatype_tkey: + return (DNS_R_NOTIMP); + case dns_rdatatype_tsig: + return (DNS_R_FORMERR); + case dns_rdatatype_ixfr: + case dns_rdatatype_axfr: + case dns_rdatatype_mailb: + case dns_rdatatype_maila: + return (DNS_R_REFUSED); + default: + break; + } + + return (ISC_R_SUCCESS); +} + +#define MAX_RESTARTS 16 + +#define QUERY_ERROR(r) \ +do { \ + eresult = r; \ + want_restart = ISC_FALSE; \ +} while (0) + +static void +query_find(ns_client_t *client) { dns_db_t *db; - dns_name_t *fname, *tname; - dns_rdataset_t *rdataset; + dns_dbnode_t *node; + dns_rdatatype_t qtype, type; + dns_name_t *fname, *tname, *prefix; + dns_rdataset_t *rdataset, *qrdataset, *trdataset; + dns_rdata_t rdata; dns_rdatasetiter_t *rdsiter; - unsigned int section; + isc_boolean_t use_cache, recursion_ok, want_restart; + isc_boolean_t auth, is_zone; + unsigned int restarts, qcount, n, nlabels, nbits; + dns_namereln_t namereln; + int order; isc_dynbuffer_t *dbuf; isc_region_t r; isc_buffer_t b; - unsigned int cname_hops; - unsigned int dname_hops; - isc_boolean_t auth; - isc_boolean_t again; - isc_boolean_t first_time; - dns_rdata_t rdata; + isc_result_t result, eresult; + dns_fixedname_t fixed; - /* - * XXXRTH - * - * This is still jury rigged. - */ - - /* + /* * One-time initialization. + * + * It's especially important to initialize anything that the cleanup + * code might cleanup. */ - cname_hops = 0; - dname_hops = 0; + + eresult = ISC_R_SUCCESS; + restarts = 0; + use_cache = ISC_FALSE; + recursion_ok = ISC_FALSE; + fname = NULL; + rdataset = NULL; + node = NULL; + db = NULL; + + if (client->view->cachedb == NULL || + client->view->resolver == NULL) { + use_cache = ISC_FALSE; + recursion_ok = ISC_FALSE; + } + + restart: + want_restart = ISC_FALSE; auth = ISC_FALSE; - first_time = ISC_TRUE; /* - * Find answers to questions. + * First we must find the right database. */ - do { + result = dns_dbtable_find(client->view->dbtable, + client->query.qname, &db); + if (result == ISC_R_NOTFOUND) { /* - * Per iteration initialization. + * We're not directly authoritative for this query name, nor + * is it a subdomain of any zone for which we're + * authoritative. */ - section = DNS_SECTION_ANSWER; - again = ISC_FALSE; - - /* - * Get the resources we'll need. - */ - dbuf = query_getnamebuf(client); - if (dbuf == NULL) { - if (first_time) - return (DNS_R_SERVFAIL); - else - return (ISC_R_SUCCESS); - } - isc_buffer_available(&dbuf->buffer, &r); - isc_buffer_init(&b, r.base, r.length, ISC_BUFFERTYPE_BINARY); - fname = NULL; - result = dns_message_gettempname(client->message, &fname); - if (result != ISC_R_SUCCESS) { - if (first_time) - return (result); - else - return (ISC_R_SUCCESS); - } - dns_name_init(fname, NULL); - dns_name_setbuffer(fname, &b); - rdataset = NULL; - result = dns_message_gettemprdataset(client->message, - &rdataset); - if (result != ISC_R_SUCCESS) { - if (first_time) - return (result); - else - return (ISC_R_SUCCESS); - } - dns_rdataset_init(rdataset); - - /* - * XXXRTH Problem areas. - * - * If we're authoritative for both a parent and a child, the - * child is non-secure, and we are asked for the KEY of the - * nonsecure child, we need to get it from the parent. - * If we're not auth for the parent, then we have to go - * looking for it in the cache. How do we even know who - * the parent is? We probably won't find this KEY when doing - * additional data KEY retrievals, but that's probably OK, - * since it's a SHOULD not a MUST. We don't want to be doing - * tons of work just to fill out additional data. - * - * Similar problems occur with NXT queries, since there can - * be NXT records at a delegation point in both the parent - * and the child. RFC 2535 section 5.5 says that on explicit - * query we should return both, if available. That seems - * to imply we shouldn't recurse to get the missing one - * if only one is available. Is that right? - */ - - /* - * Find a database to answer the query. - */ - db = NULL; - result = dns_dbtable_find(client->view->dbtable, client->query.qname, - &db); - if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) { - if (first_time) - return (DNS_R_SERVFAIL); - else - return (ISC_R_SUCCESS); - } - - /* - * Now look for an answer in the database. - */ - if (dns_db_iszone(db)) - auth = ISC_TRUE; - node = NULL; - result = dns_db_find(db, client->query.qname, NULL, type, 0, 0, - &node, fname, rdataset); - switch (result) { - case DNS_R_SUCCESS: - case DNS_R_DNAME: - break; - case DNS_R_CNAME: - again = ISC_TRUE; - cname_hops++; - tname = NULL; - result = dns_message_gettempname(client->message, - &tname); - if (result != ISC_R_SUCCESS) - goto cleanup_rdataset; - result = dns_rdataset_first(rdataset); - if (result != ISC_R_SUCCESS) - goto cleanup_rdataset; - dns_rdataset_current(rdataset, &rdata); - r.base = rdata.data; - r.length = rdata.length; - dns_name_init(tname, NULL); - dns_name_fromregion(tname, &r); - client->query.qname = tname; - break; - case DNS_R_GLUE: - case DNS_R_ZONECUT: - case DNS_R_DELEGATION: - auth = ISC_FALSE; - break; - case DNS_R_NXRDATASET: - result = ISC_R_SUCCESS; - goto cleanup_node; - case DNS_R_NXDOMAIN: - if (first_time) - client->message->rcode = dns_rcode_nxdomain; - result = ISC_R_SUCCESS; - goto cleanup_db; - default: - result = DNS_R_SERVFAIL; - goto cleanup_db; - } - - /* - * Record the space we consumed from the namebuf. - */ - dns_name_toregion(fname, &r); - isc_buffer_add(&dbuf->buffer, r.length); - - /* - * This is not strictly necessary, but is done to emphasize - * that the name's dedicated buffer, which is on our stack, - * is no longer used. It also prevents any later accidental - * use of the dedicated buffer. - */ - dns_name_setbuffer(fname, NULL); - - if (result == DNS_R_DELEGATION) { + if (!use_cache) { /* - * XXXRTH This is where we'll set up a resolver - * fetch if recursion is allowed. We'll need - * to handle the glue case too. - * Also, we'll probably need to split find() - * up into a series of event callbacks. + * If we can't use the cache, either because we + * don't have one or because its use has been + * disallowed, there's no more progress we can make + * on this query. */ - /* - * The following is the "no recursion" case. - */ - section = DNS_SECTION_AUTHORITY; - /* - * We don't have to set DNS_DBFIND_VALIDDATEGLUE - * because since we'll be processing the NS records - * we know the glue is good. - */ - client->query.dboptions |= DNS_DBFIND_GLUEOK; - query_addrdataset(client, fname, rdataset); - } else if (type == dns_rdatatype_any) { - /* - * XXXRTH Need to handle zonecuts with special case - * code. - */ - rdsiter = NULL; - result = dns_db_allrdatasets(db, node, NULL, 0, - &rdsiter); - if (result == ISC_R_SUCCESS) - result = dns_rdatasetiter_first(rdsiter); - while (result == ISC_R_SUCCESS) { - dns_rdatasetiter_current(rdsiter, rdataset); - query_addrdataset(client, fname, rdataset); - rdataset = NULL; - result = dns_message_gettemprdataset( - client->message, - &rdataset); - if (result == ISC_R_SUCCESS) { - dns_rdataset_init(rdataset); - result = dns_rdatasetiter_next(rdsiter); - } + QUERY_ERROR(DNS_R_REFUSED); + goto cleanup; + } + INSIST(client->view->cachedb != NULL); + dns_db_attach(client->view->cachedb, &db); + } else if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) { + /* + * Something is broken. + */ + QUERY_ERROR(DNS_R_SERVFAIL); + goto cleanup; + } + + is_zone = dns_db_iszone(db); + if (is_zone) + auth = ISC_TRUE; + + /* + * Find the first unanswered type in the question section. + */ + qcount = 0; + qrdataset = NULL; + qtype = dns_rdatatype_null; + for (trdataset = ISC_LIST_HEAD(client->query.origqname->list); + trdataset != NULL; + trdataset = ISC_LIST_NEXT(trdataset, link)) { + if (!ANSWERED(trdataset)) { + if (qrdataset == NULL) { + qrdataset = trdataset; + qtype = trdataset->type; } - if (result != DNS_R_NOMORE) { - result = DNS_R_SERVFAIL; - goto cleanup_node; + qcount++; + } + } + /* + * We had better have found something! + */ + INSIST(qrdataset != NULL && qcount > 0); + /* + * If there's more than one question, we'll retrieve the node and + * iterate it, trying to find answers. + */ + if (qcount == 1) + type = qtype; + else { + type = dns_rdatatype_any; + /* XXXRTH */ + QUERY_ERROR(DNS_R_NOTIMP); + goto cleanup; + } + + /* + * See if the type is OK. + */ + result = query_checktype(qtype); + if (result != ISC_R_SUCCESS) { + QUERY_ERROR(result); + goto cleanup; + } + + /* + * We'll need some resources... + */ + dbuf = query_getnamebuf(client); + if (dbuf == NULL) { + QUERY_ERROR(DNS_R_SERVFAIL); + goto cleanup; + } + fname = query_newname(client, dbuf, &b); + rdataset = query_newrdataset(client); + if (fname == NULL || rdataset == NULL) { + QUERY_ERROR(DNS_R_SERVFAIL); + goto cleanup; + } + + db_find: + /* + * Now look for an answer in the database. + */ + result = dns_db_find(db, client->query.qname, NULL, type, 0, 0, &node, + fname, rdataset); + switch (result) { + case DNS_R_SUCCESS: + case DNS_R_ZONECUT: + /* + * These cases are handled in the main line below. + */ + break; + case DNS_R_DELEGATION: + if (is_zone) { + /* + * We're authoritative for an ancestor of QNAME. + */ + if (!use_cache) { + /* + * We don't have a cache, so this is the best + * answer. + */ + query_addrrset(client, &fname, &rdataset, dbuf, + DNS_SECTION_AUTHORITY); + } else { + /* + * We might have a better answer or delegation + * in the cache. We'll remember the current + * values of fname and rdataset, and then + * go looking for QNAME in the cache. If we + * find something better, we'll use it instead. + */ + QUERY_ERROR(DNS_R_NOTIMP); + goto cleanup; } } else { - query_addrdataset(client, fname, rdataset); + INSIST(recursion_ok); + + /* + * Recurse using the best delegation. + */ + QUERY_ERROR(DNS_R_NOTIMP); } + goto cleanup; + case DNS_R_GLUE: + auth = ISC_FALSE; + break; + case DNS_R_NXRDATASET: + INSIST(is_zone); + /* + * Add SOA. + */ + query_releasename(client, &fname); + result = query_addsoa(client, db); + if (result != ISC_R_SUCCESS) { + QUERY_ERROR(result); + goto cleanup; + } + /* + * XXXRTH Add NXT record here. + */ + goto cleanup; + case DNS_R_NXDOMAIN: + if (restarts > 0) { + /* + * We hit a dead end following a CNAME or DNAME. + */ + goto cleanup; + } + INSIST(is_zone); + /* + * Set message rcode. + */ + client->message->rcode = dns_rcode_nxdomain; + /* + * Add SOA. + */ + query_releasename(client, &fname); + result = query_addsoa(client, db); + if (result != ISC_R_SUCCESS) { + QUERY_ERROR(result); + goto cleanup; + } + /* + * XXXRTH Add NXT chain here. + */ + goto cleanup; + case DNS_R_NOTFOUND: + QUERY_ERROR(DNS_R_NOTIMP); + goto cleanup; + case DNS_R_CNAME: + /* + * Keep a copy of the rdataset. We have to do this because + * query_addrrset may clear 'rdataset' (to prevent the + * cleanup code from cleaning it up). + */ + trdataset = rdataset; + /* + * Add the CNAME to the answer section. + */ + query_addrrset(client, &fname, &rdataset, dbuf, + DNS_SECTION_ANSWER); + /* + * We set the PARTIALANSWER attribute so that if anything goes + * wrong later on, we'll return what we've got so far. + */ + client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; + /* + * Reset qname to be the target name of the CNAME and restart + * the query. + */ + tname = NULL; + result = dns_message_gettempname(client->message, &tname); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_rdataset_first(trdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_rdataset_current(trdataset, &rdata); + r.base = rdata.data; + r.length = rdata.length; + dns_name_init(tname, NULL); + dns_name_fromregion(tname, &r); + client->query.qname = tname; + want_restart = ISC_TRUE; + goto cleanup; + case DNS_R_DNAME: + /* + * Compare the current qname to the found name. We need + * to know how many labels and bits are in common because + * we're going to have to split qname later on. + */ + namereln = dns_name_fullcompare(client->query.qname, fname, + &order, &nlabels, &nbits); + INSIST(namereln == dns_namereln_subdomain); + /* + * Keep a copy of the rdataset. We have to do this because + * query_addrrset may clear 'rdataset' (to prevent the + * cleanup code from cleaning it up). + */ + trdataset = rdataset; + /* + * Add the DNAME to the answer section. + */ + query_addrrset(client, &fname, &rdataset, dbuf, + DNS_SECTION_ANSWER); + /* + * We set the PARTIALANSWER attribute so that if anything goes + * wrong later on, we'll return what we've got so far. + */ + client->query.attributes |= NS_QUERYATTR_PARTIALANSWER; + /* + * Get the target name of the DNAME. + */ + tname = NULL; + result = dns_message_gettempname(client->message, &tname); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_rdataset_first(trdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + dns_rdataset_current(trdataset, &rdata); + r.base = rdata.data; + r.length = rdata.length; + dns_name_init(tname, NULL); + dns_name_fromregion(tname, &r); + /* + * Construct the new qname and restart the query. + */ + dns_fixedname_init(&fixed); + prefix = dns_fixedname_name(&fixed); + result = dns_name_split(client->query.qname, nlabels, nbits, + prefix, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + if (fname != NULL) + query_releasename(client, &fname); + dbuf = query_getnamebuf(client); + if (dbuf == NULL) + goto cleanup; + fname = query_newname(client, dbuf, &b); + if (fname == NULL) + goto cleanup; + result = dns_name_concatenate(prefix, tname, fname, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + query_keepname(client, fname, dbuf); + client->query.qname = fname; + fname = NULL; + want_restart = ISC_TRUE; + goto cleanup; + default: + /* + * Something has gone wrong. + */ + QUERY_ERROR(DNS_R_SERVFAIL); + goto cleanup; + } - dns_message_addname(client->message, fname, section); + if (type == dns_rdatatype_any) { + /* + * XXXRTH Need to handle zonecuts with special case + * code. + */ + n = 0; + rdsiter = NULL; + result = dns_db_allrdatasets(db, node, NULL, 0, &rdsiter); + if (result != ISC_R_SUCCESS) { + QUERY_ERROR(DNS_R_SERVFAIL); + goto cleanup; + } + result = dns_rdatasetiter_first(rdsiter); + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, rdataset); + tname = fname; + query_addrrset(client, &tname, &rdataset, dbuf, + DNS_SECTION_ANSWER); + n++; + /* + * We shouldn't ever fail to add 'rdataset' because + * it's already in the answer. + */ + INSIST(rdataset == NULL); + /* + * We set dbuf to NULL because we only want the + * query_keepname() call in query_addrrset() to be + * called once. + */ + dbuf = NULL; + result = dns_message_gettemprdataset(client->message, + &rdataset); + if (result == ISC_R_SUCCESS) { + dns_rdataset_init(rdataset); + result = dns_rdatasetiter_next(rdsiter); + } + } + /* + * If we added at least one RRset, then we must clear fname, + * otherwise the cleanup code might cause it to be reused. + */ + if (n > 0) + fname = NULL; + dns_rdatasetiter_destroy(&rdsiter); + if (result != DNS_R_NOMORE) { + QUERY_ERROR(DNS_R_SERVFAIL); + goto cleanup; + } + } else { + /* + * This is the "normal" case -- an ordinary question to which + * we know the answer. + */ + query_addrrset(client, &fname, &rdataset, dbuf, + DNS_SECTION_ANSWER); + /* + * Remember that we've answered this question. + */ + qrdataset->attributes |= DNS_RDATASETATTR_ANSWERED; + } - if (!auth && first_time) - client->message->flags &= ~DNS_MESSAGEFLAG_AA; - - first_time = ISC_FALSE; + /* + * XXXRTH Handle additional questions above. Find all the question + * types we can from the node we found, and (if recursion is + * OK) launch queries for any types we don't have answers to. + * + * Special case: they make an ANY query as well as some + * other type. Perhaps ANY should be disallowed in a + * multiple question query? + */ + cleanup: + if (rdataset != NULL) { + if (rdataset->methods != NULL) + dns_rdataset_disassociate(rdataset); + ISC_LIST_APPEND(client->query.tmprdatasets, rdataset, link); + } + if (fname != NULL) + query_releasename(client, &fname); + if (node != NULL) dns_db_detachnode(db, &node); + if (db != NULL) dns_db_detach(&db); - } while (again && cname_hops < 8 && dname_hops < 16); - return (ISC_R_SUCCESS); + if (restarts == 0 && !auth) { + /* + * We're not authoritative, so we must ensure the AA bit + * isn't set. + */ + client->message->flags &= ~DNS_MESSAGEFLAG_AA; + } - cleanup_rdataset: - dns_rdataset_disassociate(rdataset); + if (want_restart && restarts < MAX_RESTARTS) { + restarts++; + goto restart; + } - cleanup_node: - dns_db_detachnode(db, &node); - - cleanup_db: - dns_db_detach(&db); - - return (result); + if (eresult != ISC_R_SUCCESS && !PARTIALANSWER(client)) + ns_client_error(client, eresult); + else + ns_client_send(client); } +/* + * XXXRTH Problem areas. + * + * If we're authoritative for both a parent and a child, the + * child is non-secure, and we are asked for the KEY of the + * nonsecure child, we need to get it from the parent. + * If we're not auth for the parent, then we have to go + * looking for it in the cache. How do we even know who + * the parent is? We probably won't find this KEY when doing + * additional data KEY retrievals, but that's probably OK, + * since it's a SHOULD not a MUST. We don't want to be doing + * tons of work just to fill out additional data. + * + * Similar problems occur with NXT queries, since there can + * be NXT records at a delegation point in both the parent + * and the child. RFC 2535 section 5.5 says that on explicit + * query we should return both, if available. That seems + * to imply we shouldn't recurse to get the missing one + * if only one is available. Is that right? + */ + void ns_query_start(ns_client_t *client) { isc_result_t result; - dns_rdataset_t *rdataset; dns_message_t *message = client->message; /* @@ -594,10 +989,7 @@ ns_query_start(ns_client_t *client) { client->next = query_next; /* - * XXXRTH Deal with allow-query and allow-recursion here. Also, - * If the view doesn't have a cache or a resolver, then - * NS_QUERYATTR_RECURSIONOK and NS_QUERYATTR_CACHEOK should - * be turned off. + * XXXRTH Deal with allow-query and allow-recursion here. */ result = dns_message_reply(message, ISC_TRUE); @@ -628,6 +1020,7 @@ ns_query_start(ns_client_t *client) { } dns_message_currentname(message, DNS_SECTION_QUESTION, &client->query.qname); + client->query.origqname = client->query.qname; result = dns_message_nextname(message, DNS_SECTION_QUESTION); if (result != ISC_R_NOMORE) { if (result == ISC_R_SUCCESS) { @@ -641,19 +1034,5 @@ ns_query_start(ns_client_t *client) { return; } - /* - * XXXRTH comment here - */ - - for (rdataset = ISC_LIST_HEAD(client->query.qname->list); - rdataset != NULL; - rdataset = ISC_LIST_NEXT(rdataset, link)) { - result = find(client, rdataset->type); - if (result != ISC_R_SUCCESS) { - ns_client_error(client, result); - return; - } - } - - ns_client_send(client); + query_find(client); }