/* * query.c -- nsd(8) the resolver. * * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. * * See LICENSE for the license. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "answer.h" #include "axfr.h" #include "dns.h" #include "dname.h" #include "nsd.h" #include "namedb.h" #include "query.h" #include "util.h" #include "options.h" #include "nsec3.h" #include "tsig.h" /* [Bug #253] Adding unnecessary NS RRset may lead to undesired truncation. * This function determines if the final response packet needs the NS RRset * included. Currently, it will only return negative if QTYPE == DNSKEY|DS. * This way, resolvers won't fallback to TCP unnecessarily when priming * trust anchors. */ static int answer_needs_ns(struct query *query); static int add_rrset(struct query *query, answer_type *answer, rr_section_type section, domain_type *owner, rrset_type *rrset); static void answer_authoritative(struct nsd *nsd, struct query *q, answer_type *answer, uint32_t domain_number, int exact, domain_type *closest_match, domain_type *closest_encloser, const dname_type *qname); static void answer_lookup_zone(struct nsd *nsd, struct query *q, answer_type *answer, uint32_t domain_number, int exact, domain_type *closest_match, domain_type *closest_encloser, const dname_type *qname); void query_put_dname_offset(struct query *q, domain_type *domain, uint16_t offset) { assert(q); assert(domain); assert(domain->number > 0); if (offset > MAX_COMPRESSION_OFFSET) return; if (q->compressed_dname_count >= MAX_COMPRESSED_DNAMES) return; q->compressed_dname_offsets[domain->number] = offset; q->compressed_dnames[q->compressed_dname_count] = domain; ++q->compressed_dname_count; } void query_clear_dname_offsets(struct query *q, size_t max_offset) { while (q->compressed_dname_count > 0 && (q->compressed_dname_offsets[q->compressed_dnames[q->compressed_dname_count - 1]->number] >= max_offset)) { q->compressed_dname_offsets[q->compressed_dnames[q->compressed_dname_count - 1]->number] = 0; --q->compressed_dname_count; } } void query_clear_compression_tables(struct query *q) { uint16_t i; for (i = 0; i < q->compressed_dname_count; ++i) { assert(q->compressed_dnames); q->compressed_dname_offsets[q->compressed_dnames[i]->number] = 0; } q->compressed_dname_count = 0; } void query_add_compression_domain(struct query *q, domain_type *domain, uint16_t offset) { while (domain->parent) { DEBUG(DEBUG_NAME_COMPRESSION, 2, (LOG_INFO, "query dname: %s, number: %lu, offset: %u\n", dname_to_string(domain_dname(domain), NULL), (unsigned long) domain->number, offset)); query_put_dname_offset(q, domain, offset); offset += label_length(dname_name(domain_dname(domain))) + 1; domain = domain->parent; } } /* * Generate an error response with the specified RCODE. */ query_state_type query_error (struct query *q, nsd_rc_type rcode) { if (rcode == NSD_RC_DISCARD) { return QUERY_DISCARDED; } buffer_clear(q->packet); QR_SET(q->packet); /* This is an answer. */ RCODE_SET(q->packet, (int) rcode); /* Error code. */ /* Truncate the question as well... */ QDCOUNT_SET(q->packet, 0); ANCOUNT_SET(q->packet, 0); NSCOUNT_SET(q->packet, 0); ARCOUNT_SET(q->packet, 0); buffer_set_position(q->packet, QHEADERSZ); return QUERY_PROCESSED; } static query_state_type query_formerr (struct query *query) { int opcode = OPCODE(query->packet); FLAGS_SET(query->packet, FLAGS(query->packet) & 0x0100U); /* Preserve the RD flag. Clear the rest. */ OPCODE_SET(query->packet, opcode); return query_error(query, NSD_RC_FORMAT); } static void query_cleanup(void *data) { query_type *query = (query_type *) data; region_destroy(query->region); } query_type * query_create(region_type *region, uint16_t *compressed_dname_offsets, uint32_t compressed_dname_size) { query_type *query = (query_type *) region_alloc_zero(region, sizeof(query_type)); /* create region with large block size, because the initial chunk saves many mallocs in the server */ query->region = region_create_custom(xalloc, free, 16384, 16384/8, 32, 0); query->compressed_dname_offsets = compressed_dname_offsets; query->packet = buffer_create(region, QIOBUFSZ); region_add_cleanup(region, query_cleanup, query); query->compressed_dname_offsets_size = compressed_dname_size; tsig_create_record(&query->tsig, region); query->tsig_prepare_it = 1; query->tsig_update_it = 1; query->tsig_sign_it = 1; return query; } void query_reset(query_type *q, size_t maxlen, int is_tcp) { /* * As long as less than 4Kb (region block size) has been used, * this call to free_all is free, the block is saved for re-use, * so no malloc() or free() calls are done. * at present use of the region is for: * o query qname dname_type (255 max). * o wildcard expansion domain_type (7*ptr+u32+2bytes)+(5*ptr nsec3) * o wildcard expansion for additional section domain_type. * o nsec3 hashed name(s) (3 dnames for a nonexist_proof, * one proof per wildcard and for nx domain). */ region_free_all(q->region); q->addrlen = sizeof(q->addr); q->maxlen = maxlen; q->reserved_space = 0; buffer_clear(q->packet); edns_init_record(&q->edns); tsig_init_record(&q->tsig, NULL, NULL); q->tsig_prepare_it = 1; q->tsig_update_it = 1; q->tsig_sign_it = 1; q->tcp = is_tcp; q->qname = NULL; q->qtype = 0; q->qclass = 0; q->zone = NULL; q->domain = NULL; q->opcode = 0; q->cname_count = 0; q->delegation_domain = NULL; q->delegation_rrset = NULL; q->compressed_dname_count = 0; q->number_temporary_domains = 0; q->axfr_is_done = 0; q->axfr_zone = NULL; q->axfr_current_domain = NULL; q->axfr_current_rrset = NULL; q->axfr_current_rr = 0; } /* get a temporary domain number (or 0=failure) */ static domain_type* query_get_tempdomain(struct query *q) { static domain_type d[EXTRA_DOMAIN_NUMBERS]; if(q->number_temporary_domains >= EXTRA_DOMAIN_NUMBERS) return 0; q->number_temporary_domains ++; memset(&d[q->number_temporary_domains-1], 0, sizeof(domain_type)); d[q->number_temporary_domains-1].number = q->compressed_dname_offsets_size + q->number_temporary_domains - 1; return &d[q->number_temporary_domains-1]; } static void query_addtxt(struct query *q, const uint8_t *dname, uint16_t klass, uint32_t ttl, const char *txt) { size_t txt_length = strlen(txt); uint8_t len = (uint8_t) txt_length; assert(txt_length <= UCHAR_MAX); /* Add the dname */ if (dname >= buffer_begin(q->packet) && dname <= buffer_current(q->packet)) { buffer_write_u16(q->packet, 0xc000 | (dname - buffer_begin(q->packet))); } else { buffer_write(q->packet, dname + 1, *dname); } buffer_write_u16(q->packet, TYPE_TXT); buffer_write_u16(q->packet, klass); buffer_write_u32(q->packet, ttl); buffer_write_u16(q->packet, len + 1); buffer_write_u8(q->packet, len); buffer_write(q->packet, txt, len); } /* * Parse the question section of a query. The normalized query name * is stored in QUERY->name, the class in QUERY->klass, and the type * in QUERY->type. */ static int process_query_section(query_type *query) { uint8_t qnamebuf[MAXDOMAINLEN]; buffer_set_position(query->packet, QHEADERSZ); /* Lets parse the query name and convert it to lower case. */ if(!packet_read_query_section(query->packet, qnamebuf, &query->qtype, &query->qclass)) return 0; query->qname = dname_make(query->region, qnamebuf, 1); query->opcode = OPCODE(query->packet); return 1; } /* * Process an optional EDNS OPT record. Sets QUERY->EDNS to 0 if * there was no EDNS record, to -1 if there was an invalid or * unsupported EDNS record, and to 1 otherwise. Updates QUERY->MAXLEN * if the EDNS record specifies a maximum supported response length. * * Return NSD_RC_FORMAT on failure, NSD_RC_OK on success. */ static nsd_rc_type process_edns(nsd_type* nsd, struct query *q) { if (q->edns.status == EDNS_ERROR) { return NSD_RC_FORMAT; } if (q->edns.status == EDNS_OK) { /* Only care about UDP size larger than normal... */ if (!q->tcp && q->edns.maxlen > UDP_MAX_MESSAGE_LEN) { size_t edns_size; #if defined(INET6) if (q->addr.ss_family == AF_INET6) { edns_size = nsd->ipv6_edns_size; } else #endif edns_size = nsd->ipv4_edns_size; if (q->edns.maxlen < edns_size) { q->maxlen = q->edns.maxlen; } else { q->maxlen = edns_size; } #if defined(INET6) && !defined(IPV6_USE_MIN_MTU) && !defined(IPV6_MTU) /* * Use IPv6 minimum MTU to avoid sending * packets that are too large for some links. * IPv6 will not automatically fragment in * this case (unlike IPv4). */ if (q->addr.ss_family == AF_INET6 && q->maxlen > IPV6_MIN_MTU) { q->maxlen = IPV6_MIN_MTU; } #endif } /* Strip the OPT resource record off... */ buffer_set_position(q->packet, q->edns.position); buffer_set_limit(q->packet, q->edns.position); ARCOUNT_SET(q->packet, ARCOUNT(q->packet) - 1); } return NSD_RC_OK; } /* * Processes TSIG. * Sets error when tsig does not verify on the query. */ static nsd_rc_type process_tsig(struct query* q) { if(q->tsig.status == TSIG_ERROR) return NSD_RC_FORMAT; if(q->tsig.status == TSIG_OK) { if(!tsig_from_query(&q->tsig)) { log_msg(LOG_ERR, "query tsig unknown key/algorithm"); return NSD_RC_REFUSE; } buffer_set_limit(q->packet, q->tsig.position); ARCOUNT_SET(q->packet, ARCOUNT(q->packet) - 1); tsig_prepare(&q->tsig); tsig_update(&q->tsig, q->packet, buffer_limit(q->packet)); if(!tsig_verify(&q->tsig)) { log_msg(LOG_ERR, "query: bad tsig signature for key %s", dname_to_string(q->tsig.key->name, NULL)); return NSD_RC_REFUSE; } DEBUG(DEBUG_XFRD,1, (LOG_INFO, "query good tsig signature for %s", dname_to_string(q->tsig.key->name, NULL))); } return NSD_RC_OK; } /* * Check notify acl and forward to xfrd (or return an error). */ static query_state_type answer_notify(struct nsd* nsd, struct query *query) { int acl_num; acl_options_t *why; nsd_rc_type rc; zone_options_t* zone_opt; DEBUG(DEBUG_XFRD,1, (LOG_INFO, "got notify %s processing acl", dname_to_string(query->qname, NULL))); zone_opt = zone_options_find(nsd->options, query->qname); if(!zone_opt) return query_error(query, NSD_RC_NXDOMAIN); if(!nsd->this_child) /* we are in debug mode or something */ return query_error(query, NSD_RC_SERVFAIL); if(!tsig_find_rr(&query->tsig, query->packet)) { DEBUG(DEBUG_XFRD,2, (LOG_ERR, "bad tsig RR format")); return query_error(query, NSD_RC_FORMAT); } rc = process_tsig(query); if(rc != NSD_RC_OK) return query_error(query, rc); /* check if it passes acl */ if((acl_num = acl_check_incoming(zone_opt->allow_notify, query, &why)) != -1) { sig_atomic_t mode = NSD_PASS_TO_XFRD; int s = nsd->this_child->parent_fd; uint16_t sz; uint32_t acl_send = htonl(acl_num); size_t pos; assert(why); DEBUG(DEBUG_XFRD,1, (LOG_INFO, "got notify %s passed acl %s %s", dname_to_string(query->qname, NULL), why->ip_address_spec, why->nokey?"NOKEY": (why->blocked?"BLOCKED":why->key_name))); sz = buffer_limit(query->packet); if(buffer_limit(query->packet) > MAX_PACKET_SIZE) return query_error(query, NSD_RC_SERVFAIL); /* forward to xfrd for processing Note. Blocking IPC I/O, but acl is OK. */ sz = htons(sz); if(!write_socket(s, &mode, sizeof(mode)) || !write_socket(s, &sz, sizeof(sz)) || !write_socket(s, buffer_begin(query->packet), buffer_limit(query->packet)) || !write_socket(s, &acl_send, sizeof(acl_send))) { log_msg(LOG_ERR, "error in IPC notify server2main, %s", strerror(errno)); return query_error(query, NSD_RC_SERVFAIL); } /* create notify reply - keep same query contents */ QR_SET(query->packet); /* This is an answer. */ AA_SET(query->packet); /* we are authoritative. */ ANCOUNT_SET(query->packet, 0); NSCOUNT_SET(query->packet, 0); ARCOUNT_SET(query->packet, 0); RCODE_SET(query->packet, RCODE_OK); /* Error code. */ /* position is right after the query */ pos = buffer_position(query->packet); buffer_clear(query->packet); buffer_set_position(query->packet, pos); VERBOSITY(2, (LOG_INFO, "Notify received and accepted, forward to xfrd")); /* tsig is added in add_additional later (if needed) */ return QUERY_PROCESSED; } if (verbosity > 1) { char address[128]; if (addr2ip(query->addr, address, sizeof(address))) { DEBUG(DEBUG_XFRD,1, (LOG_INFO, "addr2ip failed")); strlcpy(address, "[unknown]", sizeof(address)); } VERBOSITY(1, (LOG_INFO, "notify for zone %s from client %s refused, %s%s", dname_to_string(query->qname, NULL), address, why?why->key_name:"no acl matches", why?why->ip_address_spec:".")); } return query_error(query, NSD_RC_REFUSE); } /* * Answer a query in the CHAOS class. */ static query_state_type answer_chaos(struct nsd *nsd, query_type *q) { AA_CLR(q->packet); switch (q->qtype) { case TYPE_ANY: case TYPE_TXT: if ((q->qname->name_size == 11 && memcmp(dname_name(q->qname), "\002id\006server", 11) == 0) || (q->qname->name_size == 15 && memcmp(dname_name(q->qname), "\010hostname\004bind", 15) == 0)) { /* Add ID */ query_addtxt(q, buffer_begin(q->packet) + QHEADERSZ, CLASS_CH, 0, nsd->identity); ANCOUNT_SET(q->packet, ANCOUNT(q->packet) + 1); } else if ((q->qname->name_size == 16 && memcmp(dname_name(q->qname), "\007version\006server", 16) == 0) || (q->qname->name_size == 14 && memcmp(dname_name(q->qname), "\007version\004bind", 14) == 0)) { if(!nsd->options->hide_version) { /* Add version */ query_addtxt(q, buffer_begin(q->packet) + QHEADERSZ, CLASS_CH, 0, nsd->version); ANCOUNT_SET(q->packet, ANCOUNT(q->packet) + 1); } else { RCODE_SET(q->packet, RCODE_REFUSE); } } break; default: RCODE_SET(q->packet, RCODE_REFUSE); break; } return QUERY_PROCESSED; } /* * Find the covering NSEC for a non-existent domain name. Normally * the NSEC will be located at CLOSEST_MATCH, except when it is an * empty non-terminal. In this case the NSEC may be located at the * previous domain name (in canonical ordering). */ static domain_type * find_covering_nsec(domain_type *closest_match, zone_type *zone, rrset_type **nsec_rrset) { assert(closest_match); assert(nsec_rrset); /* loop away temporary created domains. For real ones it is &RBTREE_NULL */ while (closest_match->node.parent == NULL) closest_match = closest_match->parent; while (closest_match) { *nsec_rrset = domain_find_rrset(closest_match, zone, TYPE_NSEC); if (*nsec_rrset) { return closest_match; } if (closest_match == zone->apex) { /* Don't look outside the current zone. */ return NULL; } closest_match = domain_previous(closest_match); } return NULL; } struct additional_rr_types { uint16_t rr_type; rr_section_type rr_section; }; struct additional_rr_types default_additional_rr_types[] = { { TYPE_A, ADDITIONAL_A_SECTION }, { TYPE_AAAA, ADDITIONAL_AAAA_SECTION }, { 0, (rr_section_type) 0 } }; struct additional_rr_types rt_additional_rr_types[] = { { TYPE_A, ADDITIONAL_A_SECTION }, { TYPE_AAAA, ADDITIONAL_AAAA_SECTION }, { TYPE_X25, ADDITIONAL_OTHER_SECTION }, { TYPE_ISDN, ADDITIONAL_OTHER_SECTION }, { 0, (rr_section_type) 0 } }; static void add_additional_rrsets(struct query *query, answer_type *answer, rrset_type *master_rrset, size_t rdata_index, int allow_glue, struct additional_rr_types types[]) { size_t i; assert(query); assert(answer); assert(master_rrset); assert(rdata_atom_is_domain(rrset_rrtype(master_rrset), rdata_index)); for (i = 0; i < master_rrset->rr_count; ++i) { int j; domain_type *additional = rdata_atom_domain(master_rrset->rrs[i].rdatas[rdata_index]); domain_type *match = additional; assert(additional); if (!allow_glue && domain_is_glue(match, query->zone)) continue; /* * Check to see if we need to generate the dependent * based on a wildcard domain. */ while (!match->is_existing) { match = match->parent; } if (additional != match && domain_wildcard_child(match)) { domain_type *wildcard_child = domain_wildcard_child(match); domain_type *temp = (domain_type *) region_alloc( query->region, sizeof(domain_type)); memcpy(&temp->node, &additional->node, sizeof(rbnode_t)); temp->number = additional->number; temp->parent = match; temp->wildcard_child_closest_match = temp; temp->rrsets = wildcard_child->rrsets; temp->is_existing = wildcard_child->is_existing; additional = temp; } for (j = 0; types[j].rr_type != 0; ++j) { rrset_type *rrset = domain_find_rrset( additional, query->zone, types[j].rr_type); if (rrset) { answer_add_rrset(answer, types[j].rr_section, additional, rrset); } } } } static int answer_needs_ns(struct query* query) { assert(query); /* Currently, only troublesome for DNSKEY and DS, * cuz their RRSETs are quite large. */ return (query->qtype != TYPE_DNSKEY && query->qtype != TYPE_DS); } static int add_rrset(struct query *query, answer_type *answer, rr_section_type section, domain_type *owner, rrset_type *rrset) { int result; assert(query); assert(answer); assert(owner); assert(rrset); assert(rrset_rrclass(rrset) == CLASS_IN); result = answer_add_rrset(answer, section, owner, rrset); switch (rrset_rrtype(rrset)) { case TYPE_NS: add_additional_rrsets(query, answer, rrset, 0, 1, default_additional_rr_types); break; case TYPE_MB: add_additional_rrsets(query, answer, rrset, 0, 0, default_additional_rr_types); break; case TYPE_MX: case TYPE_KX: add_additional_rrsets(query, answer, rrset, 1, 0, default_additional_rr_types); break; case TYPE_RT: add_additional_rrsets(query, answer, rrset, 1, 0, rt_additional_rr_types); break; default: break; } return result; } /* returns 0 on error, or the domain number for to_name. from_name is changes to to_name by the DNAME rr. DNAME rr is from src to dest. closest encloser encloses the to_name. */ static uint32_t query_synthesize_cname(struct query* q, struct answer* answer, const dname_type* from_name, const dname_type* to_name, domain_type* src, domain_type* to_closest_encloser, domain_type** to_closest_match) { /* add temporary domains for from_name and to_name and all their (not allocated yet) parents */ /* any domains below src are not_existing (because of DNAME at src) */ int i; domain_type* cname_domain; domain_type* cname_dest; rrset_type* rrset; /* allocate source part */ domain_type* lastparent = src; assert(q && answer && from_name && to_name && src && to_closest_encloser); assert(to_closest_match); for(i=0; i < from_name->label_count - domain_dname(src)->label_count; i++) { domain_type* newdom = query_get_tempdomain(q); if(!newdom) return 0; newdom->is_existing = 1; newdom->parent = lastparent; newdom->node.key = dname_partial_copy(q->region, from_name, domain_dname(src)->label_count + i + 1); if(dname_compare(domain_dname(newdom), q->qname) == 0) { /* 0 good for query name, otherwise new number */ newdom->number = 0; } DEBUG(DEBUG_QUERY,2, (LOG_INFO, "created temp domain src %d. %s nr %d", i, dname_to_string(domain_dname(newdom), NULL), newdom->number)); lastparent = newdom; } cname_domain = lastparent; /* allocate dest part */ lastparent = to_closest_encloser; for(i=0; i < to_name->label_count - domain_dname(to_closest_encloser)->label_count; i++) { domain_type* newdom = query_get_tempdomain(q); if(!newdom) return 0; newdom->is_existing = 0; newdom->parent = lastparent; newdom->node.key = dname_partial_copy(q->region, to_name, domain_dname(to_closest_encloser)->label_count + i + 1); DEBUG(DEBUG_QUERY,2, (LOG_INFO, "created temp domain dest %d. %s nr %d", i, dname_to_string(domain_dname(newdom), NULL), newdom->number)); lastparent = newdom; } cname_dest = lastparent; *to_closest_match = cname_dest; /* allocate the CNAME RR */ rrset = (rrset_type*) region_alloc(q->region, sizeof(rrset_type)); memset(rrset, 0, sizeof(rrset_type)); rrset->zone = q->zone; rrset->rr_count = 1; rrset->rrs = (rr_type*) region_alloc(q->region, sizeof(rr_type)); memset(rrset->rrs, 0, sizeof(rr_type)); rrset->rrs->owner = cname_domain; rrset->rrs->ttl = 0; rrset->rrs->type = TYPE_CNAME; rrset->rrs->klass = CLASS_IN; rrset->rrs->rdata_count = 1; rrset->rrs->rdatas = (rdata_atom_type*)region_alloc(q->region, sizeof(rdata_atom_type)); rrset->rrs->rdatas->domain = cname_dest; if(!add_rrset(q, answer, ANSWER_SECTION, cname_domain, rrset)) { log_msg(LOG_ERR, "could not add synthesized CNAME rrset to packet"); } return cname_dest->number; } /* * Answer delegation information. * * DNSSEC: Include the DS RRset if present. Otherwise include an NSEC * record proving the DS RRset does not exist. */ static void answer_delegation(query_type *query, answer_type *answer) { assert(answer); assert(query->delegation_domain); assert(query->delegation_rrset); AA_CLR(query->packet); add_rrset(query, answer, AUTHORITY_SECTION, query->delegation_domain, query->delegation_rrset); if (query->edns.dnssec_ok && zone_is_secure(query->zone)) { rrset_type *rrset; if ((rrset = domain_find_rrset(query->delegation_domain, query->zone, TYPE_DS))) { add_rrset(query, answer, AUTHORITY_SECTION, query->delegation_domain, rrset); #ifdef NSEC3 } else if (query->zone->nsec3_soa_rr) { nsec3_answer_delegation(query, answer); #endif } else if ((rrset = domain_find_rrset(query->delegation_domain, query->zone, TYPE_NSEC))) { add_rrset(query, answer, AUTHORITY_SECTION, query->delegation_domain, rrset); } } query->domain = query->delegation_domain; } /* * Answer SOA information. */ static void answer_soa(struct query *query, answer_type *answer) { query->domain = query->zone->apex; if (query->qclass != CLASS_ANY) { add_rrset(query, answer, AUTHORITY_SECTION, query->zone->apex, query->zone->soa_nx_rrset); } } /* * Answer that the domain name exists but there is no RRset with the * requested type. * * DNSSEC: Include the correct NSEC record proving that the type does * not exist. In the wildcard no data (3.1.3.4) case the wildcard IS * NOT expanded, so the ORIGINAL parameter must point to the original * wildcard entry, not to the generated entry. */ static void answer_nodata(struct query *query, answer_type *answer, domain_type *original) { if (query->cname_count == 0) { answer_soa(query, answer); } #ifdef NSEC3 if (query->edns.dnssec_ok && query->zone->nsec3_soa_rr) { nsec3_answer_nodata(query, answer, original); } else #endif if (query->edns.dnssec_ok && zone_is_secure(query->zone)) { domain_type *nsec_domain; rrset_type *nsec_rrset; nsec_domain = find_covering_nsec(original, query->zone, &nsec_rrset); if (nsec_domain) { add_rrset(query, answer, AUTHORITY_SECTION, nsec_domain, nsec_rrset); } } } static void answer_nxdomain(query_type *query, answer_type *answer) { if (query->cname_count == 0) { RCODE_SET(query->packet, RCODE_NXDOMAIN); answer_soa(query, answer); } } /* * Answer domain information (or SOA if we do not have an RRset for * the type specified by the query). */ static void answer_domain(struct nsd* nsd, struct query *q, answer_type *answer, domain_type *domain, domain_type *original) { rrset_type *rrset; if (q->qtype == TYPE_ANY) { int added = 0; for (rrset = domain_find_any_rrset(domain, q->zone); rrset; rrset = rrset->next) { if (rrset->zone == q->zone #ifdef NSEC3 && rrset_rrtype(rrset) != TYPE_NSEC3 #endif /* * Don't include the RRSIG RRset when * DNSSEC is used, because it is added * automatically on an per-RRset basis. */ && !(q->edns.dnssec_ok && zone_is_secure(q->zone) && rrset_rrtype(rrset) == TYPE_RRSIG)) { add_rrset(q, answer, ANSWER_SECTION, domain, rrset); ++added; } } if (added == 0) { answer_nodata(q, answer, original); return; } #ifdef NSEC3 } else if (q->qtype == TYPE_NSEC3) { answer_nodata(q, answer, original); return; #endif } else if ((rrset = domain_find_rrset(domain, q->zone, q->qtype))) { add_rrset(q, answer, ANSWER_SECTION, domain, rrset); } else if ((rrset = domain_find_rrset(domain, q->zone, TYPE_CNAME))) { int added; /* * If the CNAME is not added it is already in the * answer, so we have a CNAME loop. Don't follow the * CNAME target in this case. */ added = add_rrset(q, answer, ANSWER_SECTION, domain, rrset); assert(rrset->rr_count > 0); if (added) { /* only process first CNAME record */ domain_type *closest_match = rdata_atom_domain(rrset->rrs[0].rdatas[0]); domain_type *closest_encloser = closest_match; zone_type* origzone = q->zone; ++q->cname_count; while (!closest_encloser->is_existing) closest_encloser = closest_encloser->parent; answer_lookup_zone(nsd, q, answer, closest_match->number, closest_match == closest_encloser, closest_match, closest_encloser, domain_dname(closest_match)); q->zone = origzone; } } else { answer_nodata(q, answer, original); return; } q->domain = domain; if (q->qclass != CLASS_ANY && q->zone->ns_rrset && answer_needs_ns(q)) { add_rrset(q, answer, AUTHORITY_SECTION, q->zone->apex, q->zone->ns_rrset); } } /* * Answer with authoritative data. If a wildcard is matched the owner * name will be expanded to the domain name specified by * DOMAIN_NUMBER. DOMAIN_NUMBER 0 (zero) is reserved for the original * query name. * * DNSSEC: Include the necessary NSEC records in case the request * domain name does not exist and/or a wildcard match does not exist. */ static void answer_authoritative(struct nsd *nsd, struct query *q, answer_type *answer, uint32_t domain_number, int exact, domain_type *closest_match, domain_type *closest_encloser, const dname_type *qname) { domain_type *match; domain_type *original = closest_match; rrset_type *rrset; #ifdef NSEC3 if(exact && domain_has_only_NSEC3(closest_match, q->zone)) { exact = 0; /* pretend it does not exist */ if(closest_encloser->parent) closest_encloser = closest_encloser->parent; } #endif /* NSEC3 */ if (exact) { match = closest_match; } else if ((rrset=domain_find_rrset(closest_encloser, q->zone, TYPE_DNAME))) { /* process DNAME */ const dname_type* name = qname; domain_type *dest = rdata_atom_domain(rrset->rrs[0].rdatas[0]); int added; assert(rrset->rr_count > 0); if(domain_number != 0) /* we followed CNAMEs or DNAMEs */ name = domain_dname(closest_match); DEBUG(DEBUG_QUERY,2, (LOG_INFO, "expanding DNAME for q=%s", dname_to_string(name, NULL))); DEBUG(DEBUG_QUERY,2, (LOG_INFO, "->src is %s", dname_to_string(domain_dname(closest_encloser), NULL))); DEBUG(DEBUG_QUERY,2, (LOG_INFO, "->dest is %s", dname_to_string(domain_dname(dest), NULL))); /* if the DNAME set is not added we have a loop, do not follow */ added = add_rrset(q, answer, ANSWER_SECTION, closest_encloser, rrset); if(added) { domain_type* src = closest_encloser; const dname_type* newname = dname_replace(q->region, name, domain_dname(src), domain_dname(dest)); uint32_t newnum = 0; zone_type* origzone = q->zone; ++q->cname_count; if(!newname) { /* newname too long */ RCODE_SET(q->packet, RCODE_YXDOMAIN); return; } DEBUG(DEBUG_QUERY,2, (LOG_INFO, "->result is %s", dname_to_string(newname, NULL))); /* follow the DNAME */ exact = namedb_lookup(nsd->db, newname, &closest_match, &closest_encloser); /* synthesize CNAME record */ newnum = query_synthesize_cname(q, answer, name, newname, src, closest_encloser, &closest_match); if(!newnum) { /* could not synthesize the CNAME. */ /* return previous CNAMEs to make resolver recurse for us */ return; } while (closest_encloser && !closest_encloser->is_existing) closest_encloser = closest_encloser->parent; answer_lookup_zone(nsd, q, answer, newnum, closest_match == closest_encloser, closest_match, closest_encloser, newname); q->zone = origzone; } if(!added) /* log the error so operator can find looping recursors */ log_msg(LOG_INFO, "DNAME processing stopped due to loop, qname %s", dname_to_string(q->qname, NULL)); return; } else if (domain_wildcard_child(closest_encloser)) { /* Generate the domain from the wildcard. */ domain_type *wildcard_child = domain_wildcard_child(closest_encloser); match = (domain_type *) region_alloc(q->region, sizeof(domain_type)); memcpy(&match->node, &wildcard_child->node, sizeof(rbnode_t)); match->parent = closest_encloser; match->wildcard_child_closest_match = match; match->number = domain_number; match->rrsets = wildcard_child->rrsets; match->is_existing = wildcard_child->is_existing; #ifdef NSEC3 match->nsec3_is_exact = wildcard_child->nsec3_is_exact; match->nsec3_cover = wildcard_child->nsec3_cover; match->nsec3_wcard_child_cover = wildcard_child->nsec3_wcard_child_cover; match->nsec3_ds_parent_is_exact = wildcard_child->nsec3_ds_parent_is_exact; match->nsec3_ds_parent_cover = wildcard_child->nsec3_ds_parent_cover; if (q->edns.dnssec_ok && q->zone->nsec3_soa_rr) { /* Only add nsec3 wildcard data when do bit is set */ nsec3_answer_wildcard(q, answer, wildcard_child, nsd->db, qname); } #endif /* * Remember the original domain in case a Wildcard No * Data (3.1.3.4) response needs to be generated. In * this particular case the wildcard IS NOT * expanded. */ original = wildcard_child; } else { match = NULL; } /* Authorative zone. */ #ifdef NSEC3 if (q->edns.dnssec_ok && q->zone->nsec3_soa_rr) { nsec3_answer_authoritative(&match, q, answer, closest_encloser, nsd->db, qname); } else #endif if (q->edns.dnssec_ok && zone_is_secure(q->zone)) { if (match != closest_encloser) { domain_type *nsec_domain; rrset_type *nsec_rrset; /* * No match found or generated from wildcard, * include NSEC record. */ nsec_domain = find_covering_nsec(closest_match, q->zone, &nsec_rrset); if (nsec_domain) { add_rrset(q, answer, AUTHORITY_SECTION, nsec_domain, nsec_rrset); } } if (!match) { domain_type *nsec_domain; rrset_type *nsec_rrset; /* * No match and no wildcard. Include NSEC * proving there is no wildcard. */ nsec_domain = find_covering_nsec(closest_encloser->wildcard_child_closest_match, q->zone, &nsec_rrset); if (nsec_domain) { add_rrset(q, answer, AUTHORITY_SECTION, nsec_domain, nsec_rrset); } } } #ifdef NSEC3 if (RCODE(q->packet)!=RCODE_OK) { return; /* nsec3 collision failure */ } #endif if (match) { answer_domain(nsd, q, answer, match, original); } else { answer_nxdomain(q, answer); } } /* * qname may be different after CNAMEs have been followed from query->qname. */ static void answer_lookup_zone(struct nsd *nsd, struct query *q, answer_type *answer, uint32_t domain_number, int exact, domain_type *closest_match, domain_type *closest_encloser, const dname_type *qname) { q->zone = domain_find_zone(closest_encloser); if (!q->zone) { if(q->cname_count == 0) RCODE_SET(q->packet, RCODE_SERVFAIL); return; } /* * See RFC 4035 (DNSSEC protocol) section 3.1.4.1 Responding * to Queries for DS RRs. */ if (exact && q->qtype == TYPE_DS && closest_encloser == q->zone->apex) { /* * Type DS query at a zone cut, use the responsible * parent zone to generate the answer if we are * authoritative for the parent zone. */ zone_type *zone = domain_find_parent_zone(q->zone); if (zone) q->zone = zone; } /* see if the zone has expired (for secondary zones) */ if(q->zone && q->zone->opts && zone_is_slave(q->zone->opts) && !q->zone->is_ok) { if(q->cname_count == 0) RCODE_SET(q->packet, RCODE_SERVFAIL); return; } if (exact && q->qtype == TYPE_DS && closest_encloser == q->zone->apex) { /* * Type DS query at the zone apex (and the server is * not authoratitive for the parent zone). */ if (q->qclass == CLASS_ANY) { AA_CLR(q->packet); } else { AA_SET(q->packet); } answer_nodata(q, answer, closest_encloser); } else { q->delegation_domain = domain_find_ns_rrsets( closest_encloser, q->zone, &q->delegation_rrset); if (!q->delegation_domain || (exact && q->qtype == TYPE_DS && closest_encloser == q->delegation_domain)) { if (q->qclass == CLASS_ANY) { AA_CLR(q->packet); } else { AA_SET(q->packet); } answer_authoritative(nsd, q, answer, domain_number, exact, closest_match, closest_encloser, qname); } else { answer_delegation(q, answer); } } } static void answer_query(struct nsd *nsd, struct query *q) { domain_type *closest_match; domain_type *closest_encloser; int exact; uint16_t offset; answer_type answer; answer_init(&answer); exact = namedb_lookup(nsd->db, q->qname, &closest_match, &closest_encloser); if (!closest_encloser->is_existing) { exact = 0; while (closest_encloser != NULL && !closest_encloser->is_existing) closest_encloser = closest_encloser->parent; } if(!closest_encloser) { RCODE_SET(q->packet, RCODE_SERVFAIL); return; } q->domain = closest_encloser; answer_lookup_zone(nsd, q, &answer, 0, exact, closest_match, closest_encloser, q->qname); offset = dname_label_offsets(q->qname)[domain_dname(closest_encloser)->label_count - 1] + QHEADERSZ; query_add_compression_domain(q, closest_encloser, offset); encode_answer(q, &answer); query_clear_compression_tables(q); } void query_prepare_response(query_type *q) { uint16_t flags; /* * Preserve the data up-to the current packet's limit. */ buffer_set_position(q->packet, buffer_limit(q->packet)); buffer_set_limit(q->packet, buffer_capacity(q->packet)); /* * Reserve space for the EDNS records if required. */ q->reserved_space = edns_reserved_space(&q->edns); q->reserved_space += tsig_reserved_space(&q->tsig); /* Update the flags. */ flags = FLAGS(q->packet); flags &= 0x0100U; /* Preserve the RD flag. */ /* CD flag must be cleared for auth answers */ flags |= 0x8000U; /* Set the QR flag. */ FLAGS_SET(q->packet, flags); } /* * Processes the query. * */ query_state_type query_process(query_type *q, nsd_type *nsd) { /* The query... */ nsd_rc_type rc; query_state_type query_state; uint16_t arcount; /* Sanity checks */ if (buffer_limit(q->packet) < QHEADERSZ) { /* packet too small to contain DNS header. Now packet investigation macros will work without problems. */ return QUERY_DISCARDED; } if (QR(q->packet)) { /* Not a query? Drop it on the floor. */ return QUERY_DISCARDED; } if(!process_query_section(q)) { return query_formerr(q); } /* Update statistics. */ STATUP2(nsd, opcode, q->opcode); STATUP2(nsd, qtype, q->qtype); STATUP2(nsd, qclass, q->qclass); if (q->opcode != OPCODE_QUERY) { if (q->opcode == OPCODE_NOTIFY) { return answer_notify(nsd, q); } else { return query_error(q, NSD_RC_IMPL); } } /* Dont bother to answer more than one question at once... */ if (QDCOUNT(q->packet) != 1 || TC(q->packet)) { FLAGS_SET(q->packet, 0); return query_formerr(q); } /* Dont allow any records in the answer or authority section... except for IXFR queries. */ if (ANCOUNT(q->packet) != 0 || (q->qtype!=TYPE_IXFR && NSCOUNT(q->packet) != 0)) { return query_formerr(q); } if(q->qtype==TYPE_IXFR && NSCOUNT(q->packet) > 0) { int i; /* skip ixfr soa information data here */ for(i=0; i< NSCOUNT(q->packet); i++) if(!packet_skip_rr(q->packet, 0)) return query_formerr(q); } arcount = ARCOUNT(q->packet); if (arcount > 0) { /* see if tsig is before edns record */ if (!tsig_parse_rr(&q->tsig, q->packet)) return query_formerr(q); if(q->tsig.status != TSIG_NOT_PRESENT) --arcount; } if (arcount > 0) { if (edns_parse_record(&q->edns, q->packet)) --arcount; } if (arcount > 0 && q->tsig.status == TSIG_NOT_PRESENT) { /* see if tsig is after the edns record */ if (!tsig_parse_rr(&q->tsig, q->packet)) return query_formerr(q); if(q->tsig.status != TSIG_NOT_PRESENT) --arcount; } if (arcount > 0) { return query_formerr(q); } /* Do we have any trailing garbage? */ #ifdef STRICT_MESSAGE_PARSE if (buffer_remaining(q->packet) > 0) { /* If we're strict.... */ return query_formerr(q); } #endif /* Remove trailing garbage. */ buffer_set_limit(q->packet, buffer_position(q->packet)); rc = process_tsig(q); if (rc != NSD_RC_OK) { return query_error(q, rc); } rc = process_edns(nsd, q); if (rc != NSD_RC_OK) { /* We should not return FORMERR, but BADVERS (=16). * BADVERS is created with Ext. RCODE, followed by RCODE. * Ext. RCODE is set to 1, RCODE must be 0 (getting 0x10 = 16). * Thus RCODE = NOERROR = NSD_RC_OK. */ return query_error(q, NSD_RC_OK); } query_prepare_response(q); if (q->qclass != CLASS_IN && q->qclass != CLASS_ANY) { if (q->qclass == CLASS_CH) { return answer_chaos(nsd, q); } else { return query_error(q, NSD_RC_REFUSE); } } query_state = answer_axfr_ixfr(nsd, q); if (query_state == QUERY_PROCESSED || query_state == QUERY_IN_AXFR) { return query_state; } answer_query(nsd, q); return QUERY_PROCESSED; } void query_add_optional(query_type *q, nsd_type *nsd) { struct edns_data *edns = &nsd->edns_ipv4; #if defined(INET6) if (q->addr.ss_family == AF_INET6) { edns = &nsd->edns_ipv6; } #endif switch (q->edns.status) { case EDNS_NOT_PRESENT: break; case EDNS_OK: buffer_write(q->packet, edns->ok, OPT_LEN); if (nsd->nsid_len > 0 && q->edns.nsid == 1 && !query_overflow_nsid(q, nsd->nsid_len)) { /* rdata length */ buffer_write(q->packet, edns->rdata_nsid, OPT_RDATA); /* nsid opt header */ buffer_write(q->packet, edns->nsid, OPT_HDR); /* nsid payload */ buffer_write(q->packet, nsd->nsid, nsd->nsid_len); } else { /* fill with NULLs */ buffer_write(q->packet, edns->rdata_none, OPT_RDATA); } ARCOUNT_SET(q->packet, ARCOUNT(q->packet) + 1); STATUP(nsd, edns); break; case EDNS_ERROR: buffer_write(q->packet, edns->error, OPT_LEN); buffer_write(q->packet, edns->rdata_none, OPT_RDATA); ARCOUNT_SET(q->packet, ARCOUNT(q->packet) + 1); STATUP(nsd, ednserr); break; } if (q->tsig.status != TSIG_NOT_PRESENT) { if (q->tsig.status == TSIG_ERROR || q->tsig.error_code != TSIG_ERROR_NOERROR) { tsig_error_reply(&q->tsig); tsig_append_rr(&q->tsig, q->packet); ARCOUNT_SET(q->packet, ARCOUNT(q->packet) + 1); } else if(q->tsig.status == TSIG_OK && q->tsig.error_code == TSIG_ERROR_NOERROR) { if(q->tsig_prepare_it) tsig_prepare(&q->tsig); if(q->tsig_update_it) tsig_update(&q->tsig, q->packet, buffer_position(q->packet)); if(q->tsig_sign_it) { tsig_sign(&q->tsig); tsig_append_rr(&q->tsig, q->packet); ARCOUNT_SET(q->packet, ARCOUNT(q->packet) + 1); } } } }