diff options
Diffstat (limited to 'src/forward.c')
-rw-r--r-- | src/forward.c | 1787 |
1 files changed, 1514 insertions, 273 deletions
diff --git a/src/forward.c b/src/forward.c index 92bc6b0..2731b90 100644 --- a/src/forward.c +++ b/src/forward.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2011 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,19 +16,27 @@ #include "dnsmasq.h" -static struct frec *lookup_frec(unsigned short id, unsigned int crc); +static struct frec *lookup_frec(unsigned short id, void *hash); static struct frec *lookup_frec_by_sender(unsigned short id, union mysockaddr *addr, - unsigned int crc); -static unsigned short get_id(unsigned int crc); + void *hash); +static unsigned short get_id(void); static void free_frec(struct frec *f); -static struct randfd *allocate_rfd(int family); + +#ifdef HAVE_DNSSEC +static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, + int class, char *name, char *keyname, struct server *server, int *keycount); +static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname); +static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, + char *name, char *keyname); +#endif + /* Send a UDP packet with its source address set as "source" unless nowild is true, when we just send it with the kernel default */ -static void send_from(int fd, int nowild, char *packet, size_t len, - union mysockaddr *to, struct all_addr *source, - unsigned int iface) +int send_from(int fd, int nowild, char *packet, size_t len, + union mysockaddr *to, struct all_addr *source, + unsigned int iface) { struct msghdr msg; struct iovec iov[1]; @@ -70,7 +78,7 @@ static void send_from(int fd, int nowild, char *packet, size_t len, p.ipi_spec_dst = source->addr.addr4; memcpy(CMSG_DATA(cmptr), &p, sizeof(p)); msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - cmptr->cmsg_level = SOL_IP; + cmptr->cmsg_level = IPPROTO_IP; cmptr->cmsg_type = IP_PKTINFO; #elif defined(IP_SENDSRCADDR) memcpy(CMSG_DATA(cmptr), &(source->addr.addr4), sizeof(source->addr.addr4)); @@ -88,27 +96,23 @@ static void send_from(int fd, int nowild, char *packet, size_t len, memcpy(CMSG_DATA(cmptr), &p, sizeof(p)); msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cmptr->cmsg_type = daemon->v6pktinfo; - cmptr->cmsg_level = IPV6_LEVEL; + cmptr->cmsg_level = IPPROTO_IPV6; } #else - iface = 0; /* eliminate warning */ + (void)iface; /* eliminate warning */ #endif } - retry: - if (sendmsg(fd, &msg, 0) == -1) + while (retry_send(sendmsg(fd, &msg, 0))); + + /* If interface is still in DAD, EINVAL results - ignore that. */ + if (errno != 0 && errno != EINVAL) { - /* certain Linux kernels seem to object to setting the source address in the IPv6 stack - by returning EINVAL from sendmsg. In that case, try again without setting the - source address, since it will nearly alway be correct anyway. IPv6 stinks. */ - if (errno == EINVAL && msg.msg_controllen) - { - msg.msg_controllen = 0; - goto retry; - } - if (retry_send()) - goto retry; + my_syslog(LOG_ERR, _("failed to send packet: %s"), strerror(errno)); + return 0; } + + return 1; } static unsigned int search_servers(time_t now, struct all_addr **addrpp, @@ -207,10 +211,10 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, } } - if (flags == 0 && !(qtype & F_NSRR) && + if (flags == 0 && !(qtype & F_QUERY) && option_bool(OPT_NODOTS_LOCAL) && !strchr(qdomain, '.') && namelen != 0) - /* don't forward simple names, make exception for NS queries and empty name. */ - flags = F_NXDOMAIN; + /* don't forward A or AAAA queries for simple names, except the empty name */ + flags = F_NOERR; if (flags == F_NXDOMAIN && check_for_local_domain(qdomain, now)) flags = F_NOERR; @@ -234,24 +238,83 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp, static int forward_query(int udpfd, union mysockaddr *udpaddr, struct all_addr *dst_addr, unsigned int dst_iface, - struct dns_header *header, size_t plen, time_t now, struct frec *forward) + struct dns_header *header, size_t plen, time_t now, + struct frec *forward, int ad_reqd, int do_bit) { char *domain = NULL; int type = 0, norebind = 0; struct all_addr *addrp = NULL; - unsigned int crc = questions_crc(header, plen, daemon->namebuff); unsigned int flags = 0; - unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); struct server *start = NULL; - - /* RFC 4035: sect 4.6 para 2 */ - header->hb4 &= ~HB4_AD; - +#ifdef HAVE_DNSSEC + void *hash = hash_questions(header, plen, daemon->namebuff); +#else + unsigned int crc = questions_crc(header, plen, daemon->namebuff); + void *hash = &crc; +#endif + unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); + unsigned char *pheader; + + (void)do_bit; + /* may be no servers available. */ if (!daemon->servers) forward = NULL; - else if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, crc))) + else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) { + /* If we didn't get an answer advertising a maximal packet in EDNS, + fall back to 1280, which should work everywhere on IPv6. + If that generates an answer, it will become the new default + for this server */ + forward->flags |= FREC_TEST_PKTSZ; + +#ifdef HAVE_DNSSEC + /* If we've already got an answer to this query, but we're awaiting keys for validation, + there's no point retrying the query, retry the key query instead...... */ + if (forward->blocking_query) + { + int fd; + + forward->flags &= ~FREC_TEST_PKTSZ; + + while (forward->blocking_query) + forward = forward->blocking_query; + + forward->flags |= FREC_TEST_PKTSZ; + + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + plen = forward->stash_len; + + if (find_pseudoheader(header, plen, NULL, &pheader, NULL)) + PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : forward->sentto->edns_pktsz, pheader); + + if (forward->sentto->addr.sa.sa_family == AF_INET) + log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); +#ifdef HAVE_IPV6 + else + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); +#endif + + if (forward->sentto->sfd) + fd = forward->sentto->sfd->fd; + else + { +#ifdef HAVE_IPV6 + if (forward->sentto->addr.sa.sa_family == AF_INET6) + fd = forward->rfd6->fd; + else +#endif + fd = forward->rfd4->fd; + } + + while (retry_send( sendto(fd, (char *)header, plen, 0, + &forward->sentto->addr.sa, + sa_len(&forward->sentto->addr)))); + + return 1; + } +#endif + /* retry on existing query, send to all available servers */ domain = forward->sentto->domain; forward->sentto->failed_queries++; @@ -270,7 +333,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); - if (!flags && !(forward = get_new_frec(now, NULL))) + if (!flags && !(forward = get_new_frec(now, NULL, 0))) /* table full - server failure. */ flags = F_NEG; @@ -280,15 +343,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, forward->dest = *dst_addr; forward->iface = dst_iface; forward->orig_id = ntohs(header->id); - forward->new_id = get_id(crc); + forward->new_id = get_id(); forward->fd = udpfd; - forward->crc = crc; + memcpy(forward->hash, hash, HASH_SIZE); forward->forwardall = 0; + forward->flags = 0; if (norebind) forward->flags |= FREC_NOREBIND; if (header->hb4 & HB4_CD) forward->flags |= FREC_CHECKING_DISABLED; - + if (ad_reqd) + forward->flags |= FREC_AD_QUESTION; +#ifdef HAVE_DNSSEC + forward->work_counter = DNSSEC_WORK; + if (do_bit) + forward->flags |= FREC_DO_QUESTION; +#endif + header->id = htons(forward->new_id); /* In strict_order mode, always try servers in the order @@ -328,8 +399,38 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, struct server *firstsentto = start; int forwarded = 0; - if (udpaddr && option_bool(OPT_ADD_MAC)) - plen = add_mac(header, plen, ((char *) header) + PACKETSZ, udpaddr); + /* If a query is retried, use the log_id for the retry when logging the answer. */ + forward->log_id = daemon->log_id; + + if (option_bool(OPT_ADD_MAC)) + plen = add_mac(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source); + + if (option_bool(OPT_CLIENT_SUBNET)) + { + size_t new = add_source_addr(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source); + if (new != plen) + { + plen = new; + forward->flags |= FREC_HAS_SUBNET; + } + } + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + size_t new_plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz); + + /* For debugging, set Checking Disabled, otherwise, have the upstream check too, + this allows it to select auth servers when one is returning bad data. */ + if (option_bool(OPT_DNSSEC_DEBUG)) + header->hb4 |= HB4_CD; + + if (new_plen != plen) + forward->flags |= FREC_ADDED_PHEADER; + + plen = new_plen; + } +#endif while (1) { @@ -339,7 +440,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (type == (start->flags & SERV_TYPE) && (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && - !(start->flags & SERV_LITERAL_ADDRESS)) + !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) { int fd; @@ -366,16 +467,27 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, daemon->rfd_save = forward->rfd4; fd = forward->rfd4->fd; } + +#ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; + if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark)) + setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } +#endif } + + if (find_pseudoheader(header, plen, NULL, &pheader, NULL)) + PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : start->edns_pktsz, pheader); - if (sendto(fd, (char *)header, plen, 0, - &start->addr.sa, - sa_len(&start->addr)) == -1) - { - if (retry_send()) - continue; - } - else + if (retry_send(sendto(fd, (char *)header, plen, 0, + &start->addr.sa, + sa_len(&start->addr)))) + continue; + + if (errno == 0) { /* Keep info in case we want to re-send this packet */ daemon->srv_save = start; @@ -419,39 +531,80 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (udpfd != -1) { plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl); - send_from(udpfd, option_bool(OPT_NOWILD), (char *)header, plen, udpaddr, dst_addr, dst_iface); + send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface); } return 0; } -static size_t process_reply(struct dns_header *header, time_t now, - struct server *server, size_t n, int check_rebind, int checking_disabled) +static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind, + int no_cache, int cache_secure, int bogusanswer, int ad_reqd, int do_bit, int added_pheader, + int check_subnet, union mysockaddr *query_source) { unsigned char *pheader, *sizep; + char **sets = 0; int munged = 0, is_sign; size_t plen; + (void)ad_reqd; + (void)do_bit; + (void)bogusanswer; + +#ifdef HAVE_IPSET + if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL)) + { + /* Similar algorithm to search_servers. */ + struct ipsets *ipset_pos; + unsigned int namelen = strlen(daemon->namebuff); + unsigned int matchlen = 0; + for (ipset_pos = daemon->ipsets; ipset_pos; ipset_pos = ipset_pos->next) + { + unsigned int domainlen = strlen(ipset_pos->domain); + char *matchstart = daemon->namebuff + namelen - domainlen; + if (namelen >= domainlen && hostname_isequal(matchstart, ipset_pos->domain) && + (domainlen == 0 || namelen == domainlen || *(matchstart - 1) == '.' ) && + domainlen >= matchlen) + { + matchlen = domainlen; + sets = ipset_pos->sets; + } + } + } +#endif + /* If upstream is advertising a larger UDP packet size than we allow, trim it so that we don't get overlarge requests for the client. We can't do this for signed packets. */ - if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)) && !is_sign) + if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign))) { unsigned short udpsz; unsigned char *psave = sizep; GETSHORT(udpsz, sizep); - if (udpsz > daemon->edns_pktsz) + + if (!is_sign && udpsz > daemon->edns_pktsz) PUTSHORT(daemon->edns_pktsz, psave); + + if (check_subnet && !check_source(header, plen, pheader, query_source)) + { + my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch")); + return 0; + } + + if (added_pheader) + { + pheader = 0; + header->arcount = htons(0); + } } - + /* RFC 4035 sect 4.6 para 3 */ - if (!is_sign && !option_bool(OPT_DNSSEC)) + if (!is_sign && !option_bool(OPT_DNSSEC_PROXY)) header->hb4 &= ~HB4_AD; - + if (OPCODE(header) != QUERY || (RCODE(header) != NOERROR && RCODE(header) != NXDOMAIN)) - return n; + return resize_packet(header, n, pheader, plen); /* Complain loudly if the upstream server is non-recursive. */ if (!(header->hb4 & HB4_RA) && RCODE(header) == NOERROR && ntohs(header->ancount) == 0 && @@ -462,16 +615,19 @@ static size_t process_reply(struct dns_header *header, time_t now, if (!option_bool(OPT_LOG)) server->flags |= SERV_WARNED_RECURSIVE; } - + if (daemon->bogus_addr && RCODE(header) != NXDOMAIN && check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now)) { munged = 1; SET_RCODE(header, NXDOMAIN); header->hb3 &= ~HB3_AA; + cache_secure = 0; } else { + int doctored = 0; + if (RCODE(header) == NXDOMAIN && extract_request(header, n, daemon->namebuff, NULL) && check_for_local_domain(daemon->namebuff, now)) @@ -482,15 +638,42 @@ static size_t process_reply(struct dns_header *header, time_t now, munged = 1; header->hb3 |= HB3_AA; SET_RCODE(header, NOERROR); + cache_secure = 0; } - if (extract_addresses(header, n, daemon->namebuff, now, is_sign, check_rebind, checking_disabled)) + if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored)) { my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); munged = 1; + cache_secure = 0; } + + if (doctored) + cache_secure = 0; } +#ifdef HAVE_DNSSEC + if (bogusanswer && !(header->hb4 & HB4_CD)) + { + if (!option_bool(OPT_DNSSEC_DEBUG)) + { + /* Bogus reply, turn into SERVFAIL */ + SET_RCODE(header, SERVFAIL); + munged = 1; + } + } + + if (option_bool(OPT_DNSSEC_VALID)) + header->hb4 &= ~HB4_AD; + + if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure) + header->hb4 |= HB4_AD; + + /* If the requestor didn't set the DO bit, don't return DNSSEC info. */ + if (!do_bit) + n = filter_rrsigs(header, n); +#endif + /* do this after extract_addresses. Ensure NODATA reply and remove nameserver info. */ @@ -499,6 +682,7 @@ static size_t process_reply(struct dns_header *header, time_t now, header->ancount = htons(0); header->nscount = htons(0); header->arcount = htons(0); + header->hb3 &= ~HB3_TC; } /* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide @@ -516,10 +700,14 @@ void reply_query(int fd, int family, time_t now) union mysockaddr serveraddr; struct frec *forward; socklen_t addrlen = sizeof(serveraddr); - ssize_t n = recvfrom(fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen); + ssize_t n = recvfrom(fd, daemon->packet, daemon->packet_buff_sz, 0, &serveraddr.sa, &addrlen); size_t nn; struct server *server; - + void *hash; +#ifndef HAVE_DNSSEC + unsigned int crc; +#endif + /* packet buffer overwritten */ daemon->srv_save = NULL; @@ -530,22 +718,40 @@ void reply_query(int fd, int family, time_t now) serveraddr.in6.sin6_flowinfo = 0; #endif + header = (struct dns_header *)daemon->packet; + + if (n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR)) + return; + /* spoof check: answer must come from known server, */ for (server = daemon->servers; server; server = server->next) if (!(server->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR)) && sockaddr_isequal(&server->addr, &serveraddr)) break; - - header = (struct dns_header *)daemon->packet; - if (!server || - n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR) || - !(forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff)))) + if (!server) return; - - server = forward->sentto; - if ((RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) && +#ifdef HAVE_DNSSEC + hash = hash_questions(header, n, daemon->namebuff); +#else + hash = &crc; + crc = questions_crc(header, n, daemon->namebuff); +#endif + + if (!(forward = lookup_frec(ntohs(header->id), hash))) + return; + + /* log_query gets called indirectly all over the place, so + pass these in global variables - sorry. */ + daemon->log_display_id = forward->log_id; + daemon->log_source_addr = &forward->source; + + if (daemon->ignore_addr && RCODE(header) == NOERROR && + check_for_ignored_address(header, n, daemon->ignore_addr)) + return; + + if (RCODE(header) == REFUSED && !option_bool(OPT_ORDER) && forward->forwardall == 0) /* for broken servers, attempt to send to another one. */ @@ -563,16 +769,18 @@ void reply_query(int fd, int family, time_t now) header->arcount = htons(0); if ((nn = resize_packet(header, (size_t)n, pheader, plen))) { - header->hb3 &= ~(HB3_QR | HB3_TC); - forward_query(-1, NULL, NULL, 0, header, nn, now, forward); + header->hb3 &= ~(HB3_QR | HB3_AA | HB3_TC); + header->hb4 &= ~(HB4_RA | HB4_RCODE); + forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0, 0); return; } } } - + + server = forward->sentto; if ((forward->sentto->flags & SERV_TYPE) == 0) { - if (RCODE(header) == SERVFAIL || RCODE(header) == REFUSED) + if (RCODE(header) == REFUSED) server = NULL; else { @@ -590,24 +798,307 @@ void reply_query(int fd, int family, time_t now) if (!option_bool(OPT_ALL_SERVERS)) daemon->last_server = server; } + + /* We tried resending to this server with a smaller maximum size and got an answer. + Make that permanent. To avoid reduxing the packet size for an single dropped packet, + only do this when we get a truncated answer, or one larger than the safe size. */ + if (server && (forward->flags & FREC_TEST_PKTSZ) && + ((header->hb3 & HB3_TC) || n >= SAFE_PKTSZ)) + server->edns_pktsz = SAFE_PKTSZ; /* If the answer is an error, keep the forward record in place in case we get a good reply from another server. Kill it when we've had replies from all to avoid filling the forwarding table when everything is broken */ - if (forward->forwardall == 0 || --forward->forwardall == 1 || - (RCODE(header) != REFUSED && RCODE(header) != SERVFAIL)) + if (forward->forwardall == 0 || --forward->forwardall == 1 || RCODE(header) != SERVFAIL) { - int check_rebind = !(forward->flags & FREC_NOREBIND); + int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; - if (!option_bool(OPT_NO_REBIND)) - check_rebind = 0; + if (option_bool(OPT_NO_REBIND)) + check_rebind = !(forward->flags & FREC_NOREBIND); + + /* Don't cache replies where DNSSEC validation was turned off, either + the upstream server told us so, or the original query specified it. */ + if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED)) + no_cache_dnssec = 1; - if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, forward->flags & FREC_CHECKING_DISABLED))) +#ifdef HAVE_DNSSEC + if (server && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) + { + int status; + + /* We've had a reply already, which we're validating. Ignore this duplicate */ + if (forward->blocking_query) + return; + + if (header->hb3 & HB3_TC) + { + /* Truncated answer can't be validated. + If this is an answer to a DNSSEC-generated query, we still + need to get the client to retry over TCP, so return + an answer with the TC bit set, even if the actual answer fits. + */ + status = STAT_TRUNCATED; + } + else if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + { + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + /* Provably no DS, everything below is insecure, even if signatures are offered */ + if (status == STAT_NO_DS) + /* We only cache sigs when we've validated a reply. + Avoid caching a reply with sigs if there's a vaildated break in the + DS chain, so we don't return replies from cache missing sigs. */ + status = STAT_INSECURE_DS; + else if (status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + { + status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); + if (status == STAT_INSECURE) + status = STAT_INSECURE_DS; + } + else + status = STAT_INSECURE_DS; + } + else if (status == STAT_NO_NS) + status = STAT_BOGUS; + } + else if (forward->flags & FREC_CHECK_NOSIGN) + { + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + if (status != STAT_NEED_KEY) + status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname); + } + else + { + status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL); + if (status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); + else + status = STAT_INSECURE; + } + } + /* Can't validate, as we're missing key data. Put this + answer aside, whilst we get that. */ + if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY) + { + struct frec *new, *orig; + + /* Free any saved query */ + if (forward->stash) + blockdata_free(forward->stash); + + /* Now save reply pending receipt of key data */ + if (!(forward->stash = blockdata_alloc((char *)header, n))) + return; + forward->stash_len = n; + + anotherkey: + /* Find the original query that started it all.... */ + for (orig = forward; orig->dependent; orig = orig->dependent); + + if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) + status = STAT_INSECURE; + else + { + int fd; + struct frec *next = new->next; + *new = *forward; /* copy everything, then overwrite */ + new->next = next; + new->blocking_query = NULL; + new->sentto = server; + new->rfd4 = NULL; + new->orig_domain = NULL; +#ifdef HAVE_IPV6 + new->rfd6 = NULL; +#endif + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN); + + new->dependent = forward; /* to find query awaiting new one. */ + forward->blocking_query = new; /* for garbage cleaning */ + /* validate routines leave name of required record in daemon->keyname */ + if (status == STAT_NEED_KEY) + { + new->flags |= FREC_DNSKEY_QUERY; + nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz, + daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz); + } + else + { + if (status == STAT_NEED_DS_NEG) + new->flags |= FREC_CHECK_NOSIGN; + else + new->flags |= FREC_DS_QUERY; + nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz, + daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz); + } + if ((hash = hash_questions(header, nn, daemon->namebuff))) + memcpy(new->hash, hash, HASH_SIZE); + new->new_id = get_id(); + header->id = htons(new->new_id); + /* Save query for retransmission */ + if (!(new->stash = blockdata_alloc((char *)header, nn))) + return; + + new->stash_len = nn; + + /* Don't resend this. */ + daemon->srv_save = NULL; + + if (server->sfd) + fd = server->sfd->fd; + else + { + fd = -1; +#ifdef HAVE_IPV6 + if (server->addr.sa.sa_family == AF_INET6) + { + if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) + fd = new->rfd6->fd; + } + else +#endif + { + if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) + fd = new->rfd4->fd; + } + } + + if (fd != -1) + { + while (retry_send(sendto(fd, (char *)header, nn, 0, + &server->addr.sa, + sa_len(&server->addr)))); + server->queries++; + } + + return; + } + } + + /* Ok, we reached far enough up the chain-of-trust that we can validate something. + Now wind back down, pulling back answers which wouldn't previously validate + and validate them with the new data. Note that if an answer needs multiple + keys to validate, we may find another key is needed, in which case we set off + down another branch of the tree. Once we get to the original answer + (FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */ + while (forward->dependent) + { + struct frec *prev = forward->dependent; + free_frec(forward); + forward = prev; + forward->blocking_query = NULL; /* already gone */ + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + n = forward->stash_len; + + if (status == STAT_SECURE) + { + if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + { + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + /* Provably no DS, everything below is insecure, even if signatures are offered */ + if (status == STAT_NO_DS) + /* We only cache sigs when we've validated a reply. + Avoid caching a reply with sigs if there's a vaildated break in the + DS chain, so we don't return replies from cache missing sigs. */ + status = STAT_INSECURE_DS; + else if (status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + { + status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); + if (status == STAT_INSECURE) + status = STAT_INSECURE_DS; + } + else + status = STAT_INSECURE_DS; + } + else if (status == STAT_NO_NS) + status = STAT_BOGUS; + } + else if (forward->flags & FREC_CHECK_NOSIGN) + { + status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); + if (status != STAT_NEED_KEY) + status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname); + } + else + { + status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL); + if (status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); + else + status = STAT_INSECURE; + } + } + + if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY) + goto anotherkey; + } + } + + no_cache_dnssec = 0; + + if (status == STAT_INSECURE_DS) + { + /* We only cache sigs when we've validated a reply. + Avoid caching a reply with sigs if there's a vaildated break in the + DS chain, so we don't return replies from cache missing sigs. */ + status = STAT_INSECURE; + no_cache_dnssec = 1; + } + + if (status == STAT_TRUNCATED) + header->hb3 |= HB3_TC; + else + { + char *result, *domain = "result"; + + if (forward->work_counter == 0) + { + result = "ABANDONED"; + status = STAT_BOGUS; + } + else + result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + + if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) + domain = daemon->namebuff; + + log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); + } + + if (status == STAT_SECURE) + cache_secure = 1; + else if (status == STAT_BOGUS) + { + no_cache_dnssec = 1; + bogusanswer = 1; + } + } +#endif + + /* restore CD bit to the value in the query */ + if (forward->flags & FREC_CHECKING_DISABLED) + header->hb4 |= HB4_CD; + else + header->hb4 &= ~HB4_CD; + + if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, + forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, + forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source))) { header->id = htons(forward->orig_id); header->hb4 |= HB4_RA; /* recursion if available */ - send_from(forward->fd, option_bool(OPT_NOWILD), daemon->packet, nn, + send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, &forward->source, &forward->dest, forward->iface); } free_frec(forward); /* cancel */ @@ -624,7 +1115,10 @@ void receive_query(struct listener *listen, time_t now) struct in_addr netmask, dst_addr_4; size_t m; ssize_t n; - int if_index = 0; + int if_index = 0, auth_dns = 0; +#ifdef HAVE_AUTH + int local_auth = 0; +#endif struct iovec iov[1]; struct msghdr msg; struct cmsghdr *cmptr; @@ -643,21 +1137,30 @@ void receive_query(struct listener *listen, time_t now) CMSG_SPACE(sizeof(struct sockaddr_dl))]; #endif } control_u; - +#ifdef HAVE_IPV6 + /* Can always get recvd interface for IPv6 */ + int check_dst = !option_bool(OPT_NOWILD) || listen->family == AF_INET6; +#else + int check_dst = !option_bool(OPT_NOWILD); +#endif + /* packet buffer overwritten */ daemon->srv_save = NULL; - if (listen->family == AF_INET && option_bool(OPT_NOWILD)) - { - dst_addr_4 = listen->iface->addr.in.sin_addr; - netmask = listen->iface->netmask; - } - else + dst_addr_4.s_addr = dst_addr.addr.addr4.s_addr = 0; + netmask.s_addr = 0; + + if (option_bool(OPT_NOWILD) && listen->iface) { - dst_addr_4.s_addr = 0; - netmask.s_addr = 0; + auth_dns = listen->iface->dns_auth; + + if (listen->family == AF_INET) + { + dst_addr_4 = dst_addr.addr.addr4 = listen->iface->addr.in.sin_addr; + netmask = listen->iface->netmask; + } } - + iov[0].iov_base = daemon->packet; iov[0].iov_len = daemon->edns_pktsz; @@ -678,12 +1181,61 @@ void receive_query(struct listener *listen, time_t now) return; source_addr.sa.sa_family = listen->family; + + if (listen->family == AF_INET) + { + /* Source-port == 0 is an error, we can't send back to that. + http://www.ietf.org/mail-archive/web/dnsop/current/msg11441.html */ + if (source_addr.in.sin_port == 0) + return; + } #ifdef HAVE_IPV6 - if (listen->family == AF_INET6) - source_addr.in6.sin6_flowinfo = 0; + else + { + /* Source-port == 0 is an error, we can't send back to that. */ + if (source_addr.in6.sin6_port == 0) + return; + source_addr.in6.sin6_flowinfo = 0; + } #endif - - if (!option_bool(OPT_NOWILD)) + + /* We can be configured to only accept queries from at-most-one-hop-away addresses. */ + if (option_bool(OPT_LOCAL_SERVICE)) + { + struct addrlist *addr; +#ifdef HAVE_IPV6 + if (listen->family == AF_INET6) + { + for (addr = daemon->interface_addrs; addr; addr = addr->next) + if ((addr->flags & ADDRLIST_IPV6) && + is_same_net6(&addr->addr.addr.addr6, &source_addr.in6.sin6_addr, addr->prefixlen)) + break; + } + else +#endif + { + struct in_addr netmask; + for (addr = daemon->interface_addrs; addr; addr = addr->next) + { + netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen)); + if (!(addr->flags & ADDRLIST_IPV6) && + is_same_net(addr->addr.addr.addr4, source_addr.in.sin_addr, netmask)) + break; + } + } + if (!addr) + { + static int warned = 0; + if (!warned) + { + my_syslog(LOG_WARNING, _("Ignoring query from non-local network")); + warned = 1; + } + return; + } + } + + if (check_dst) { struct ifreq ifr; @@ -693,7 +1245,7 @@ void receive_query(struct listener *listen, time_t now) #if defined(HAVE_LINUX_NETWORK) if (listen->family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) - if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { union { unsigned char *c; @@ -733,7 +1285,7 @@ void receive_query(struct listener *listen, time_t now) if (listen->family == AF_INET6) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) - if (cmptr->cmsg_level == IPV6_LEVEL && cmptr->cmsg_type == daemon->v6pktinfo) + if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) { union { unsigned char *c; @@ -749,24 +1301,59 @@ void receive_query(struct listener *listen, time_t now) /* enforce available interface configuration */ - if (!indextoname(listen->fd, if_index, ifr.ifr_name) || - !iface_check(listen->family, &dst_addr, ifr.ifr_name, &if_index)) - return; - - if (listen->family == AF_INET && - option_bool(OPT_LOCALISE) && - ioctl(listen->fd, SIOCGIFNETMASK, &ifr) == -1) + if (!indextoname(listen->fd, if_index, ifr.ifr_name)) return; - netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + if (!iface_check(listen->family, &dst_addr, ifr.ifr_name, &auth_dns)) + { + if (!option_bool(OPT_CLEVERBIND)) + enumerate_interfaces(0); + if (!loopback_exception(listen->fd, listen->family, &dst_addr, ifr.ifr_name) && + !label_exception(if_index, listen->family, &dst_addr)) + return; + } + + if (listen->family == AF_INET && option_bool(OPT_LOCALISE)) + { + struct irec *iface; + + /* get the netmask of the interface whch has the address we were sent to. + This is no neccessarily the interface we arrived on. */ + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->addr.sa.sa_family == AF_INET && + iface->addr.in.sin_addr.s_addr == dst_addr_4.s_addr) + break; + + /* interface may be new */ + if (!iface && !option_bool(OPT_CLEVERBIND)) + enumerate_interfaces(0); + + for (iface = daemon->interfaces; iface; iface = iface->next) + if (iface->addr.sa.sa_family == AF_INET && + iface->addr.in.sin_addr.s_addr == dst_addr_4.s_addr) + break; + + /* If we failed, abandon localisation */ + if (iface) + netmask = iface->netmask; + else + dst_addr_4.s_addr = 0; + } } + + /* log_query gets called indirectly all over the place, so + pass these in global variables - sorry. */ + daemon->log_display_id = ++daemon->log_id; + daemon->log_source_addr = &source_addr; if (extract_request(header, (size_t)n, daemon->namebuff, &type)) { - char types[20]; - - querystr(types, type); - +#ifdef HAVE_AUTH + struct auth_zone *zone; +#endif + char *types = querystr(auth_dns ? "auth" : "query", type); + if (listen->family == AF_INET) log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, (struct all_addr *)&source_addr.in.sin_addr, types); @@ -775,206 +1362,801 @@ void receive_query(struct listener *listen, time_t now) log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, (struct all_addr *)&source_addr.in6.sin6_addr, types); #endif - } - m = answer_request (header, ((char *) header) + PACKETSZ, (size_t)n, - dst_addr_4, netmask, now); - if (m >= 1) +#ifdef HAVE_AUTH + /* find queries for zones we're authoritative for, and answer them directly */ + if (!auth_dns) + for (zone = daemon->auth_zones; zone; zone = zone->next) + if (in_zone(zone, daemon->namebuff, NULL)) + { + auth_dns = 1; + local_auth = 1; + break; + } +#endif + +#ifdef HAVE_LOOP + /* Check for forwarding loop */ + if (detect_loop(daemon->namebuff, type)) + return; +#endif + } + +#ifdef HAVE_AUTH + if (auth_dns) { - send_from(listen->fd, option_bool(OPT_NOWILD), (char *)header, - m, &source_addr, &dst_addr, if_index); - daemon->local_answer++; + m = answer_auth(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, now, &source_addr, local_auth); + if (m >= 1) + { + send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), + (char *)header, m, &source_addr, &dst_addr, if_index); + daemon->auth_answer++; + } } - else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, - header, (size_t)n, now, NULL)) - daemon->queries_forwarded++; else - daemon->local_answer++; +#endif + { + int ad_reqd, do_bit; + m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, + dst_addr_4, netmask, now, &ad_reqd, &do_bit); + + if (m >= 1) + { + send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), + (char *)header, m, &source_addr, &dst_addr, if_index); + daemon->local_answer++; + } + else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, + header, (size_t)n, now, NULL, ad_reqd, do_bit)) + daemon->queries_forwarded++; + else + daemon->local_answer++; + } +} + +#ifdef HAVE_DNSSEC + +/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS + and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or + STAT_NEED_DS_NEG and keyname if we need to do the query. */ +static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen, + char *name, char *keyname) +{ + int status = dnssec_chase_cname(now, header, plen, name, keyname); + + if (status != STAT_INSECURE) + return status; + + /* Store the domain we're trying to check. */ + forward->name_start = strlen(name); + forward->name_len = forward->name_start + 1; + if (!(forward->orig_domain = blockdata_alloc(name, forward->name_len))) + return STAT_BOGUS; + + return do_check_sign(forward, 0, now, name, keyname); } + +/* We either have a a reply (header non-NULL, or we need to start by looking in the cache */ +static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname) +{ + /* get domain we're checking back from blockdata store, it's stored on the original query. */ + while (forward->dependent && !forward->orig_domain) + forward = forward->dependent; + + blockdata_retrieve(forward->orig_domain, forward->name_len, name); + + while (1) + { + char *p; + + if (status == 0) + { + struct crec *crecp; + + /* Haven't received answer, see if in cache */ + if (!(crecp = cache_find_by_name(NULL, &name[forward->name_start], now, F_DS))) + { + /* put name of DS record we're missing into keyname */ + strcpy(keyname, &name[forward->name_start]); + /* and wait for reply to arrive */ + return STAT_NEED_DS_NEG; + } + + /* F_DNSSECOK misused in DS cache records to non-existance of NS record */ + if (!(crecp->flags & F_NEG)) + status = STAT_SECURE; + else if (crecp->flags & F_DNSSECOK) + status = STAT_NO_DS; + else + status = STAT_NO_NS; + } + + /* Have entered non-signed part of DNS tree. */ + if (status == STAT_NO_DS) + return forward->dependent ? STAT_INSECURE_DS : STAT_INSECURE; + + if (status == STAT_BOGUS) + return STAT_BOGUS; + + if (status == STAT_NO_SIG && *keyname != 0) + { + /* There is a validated CNAME chain that doesn't end in a DS record. Start + the search again in that domain. */ + blockdata_free(forward->orig_domain); + forward->name_start = strlen(keyname); + forward->name_len = forward->name_start + 1; + if (!(forward->orig_domain = blockdata_alloc(keyname, forward->name_len))) + return STAT_BOGUS; + + strcpy(name, keyname); + status = 0; /* force to cache when we iterate. */ + continue; + } + + /* There's a proven DS record, or we're within a zone, where there doesn't need + to be a DS record. Add a name and try again. + If we've already tried the whole name, then fail */ + + if (forward->name_start == 0) + return STAT_BOGUS; + + for (p = &name[forward->name_start-2]; (*p != '.') && (p != name); p--); + + if (p != name) + p++; + + forward->name_start = p - name; + status = 0; /* force to cache when we iterate. */ + } +} + +/* Move down from the root, until we find a signed non-existance of a DS, in which case + an unsigned answer is OK, or we find a signed DS, in which case there should be + a signature, and the answer is BOGUS */ +static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name, + char *keyname, struct server *server, int *keycount) +{ + size_t m; + unsigned char *packet, *payload; + u16 *length; + int status, name_len; + struct blockdata *block; + + char *name_start; + + /* Get first insecure entry in CNAME chain */ + status = tcp_key_recurse(now, STAT_CHASE_CNAME, header, plen, class, name, keyname, server, keycount); + if (status == STAT_BOGUS) + return STAT_BOGUS; + + if (!(packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)))) + return STAT_BOGUS; + + payload = &packet[2]; + header = (struct dns_header *)payload; + length = (u16 *)packet; + + /* Stash the name away, since the buffer will be trashed when we recurse */ + name_len = strlen(name) + 1; + name_start = name + name_len - 1; + + if (!(block = blockdata_alloc(name, name_len))) + { + free(packet); + return STAT_BOGUS; + } + + while (1) + { + unsigned char c1, c2; + struct crec *crecp; + + if (--(*keycount) == 0) + { + free(packet); + blockdata_free(block); + return STAT_BOGUS; + } + + while ((crecp = cache_find_by_name(NULL, name_start, now, F_DS))) + { + if ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK)) + { + /* Found a secure denial of DS - delegation is indeed insecure */ + free(packet); + blockdata_free(block); + return STAT_INSECURE; + } + + /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation. + Add another label and continue. */ + + if (name_start == name) + { + free(packet); + blockdata_free(block); + return STAT_BOGUS; /* run out of labels */ + } + + name_start -= 2; + while (*name_start != '.' && name_start != name) + name_start--; + if (name_start != name) + name_start++; + } + + /* Can't find it in the cache, have to send a query */ + + m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz); + + *length = htons(m); + + if (read_write(server->tcpfd, packet, m + sizeof(u16), 0) && + read_write(server->tcpfd, &c1, 1, 1) && + read_write(server->tcpfd, &c2, 1, 1) && + read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) + { + m = (c1 << 8) | c2; + + /* Note this trashes all three name workspaces */ + status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount); + + if (status == STAT_NO_DS) + { + /* Found a secure denial of DS - delegation is indeed insecure */ + free(packet); + blockdata_free(block); + return STAT_INSECURE; + } + + if (status == STAT_NO_SIG && *keyname != 0) + { + /* There is a validated CNAME chain that doesn't end in a DS record. Start + the search again in that domain. */ + blockdata_free(block); + name_len = strlen(keyname) + 1; + name_start = name + name_len - 1; + + if (!(block = blockdata_alloc(keyname, name_len))) + return STAT_BOGUS; + + strcpy(name, keyname); + continue; + } + + if (status == STAT_BOGUS) + { + free(packet); + blockdata_free(block); + return STAT_BOGUS; + } + + /* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation. + Add another label and continue. */ + + /* Get name we're checking back. */ + blockdata_retrieve(block, name_len, name); + + if (name_start == name) + { + free(packet); + blockdata_free(block); + return STAT_BOGUS; /* run out of labels */ + } + + name_start -= 2; + while (*name_start != '.' && name_start != name) + name_start--; + if (name_start != name) + name_start++; + } + else + { + /* IO failure */ + free(packet); + blockdata_free(block); + return STAT_BOGUS; /* run out of labels */ + } + } +} + +static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, + int class, char *name, char *keyname, struct server *server, int *keycount) +{ + /* Recurse up the key heirarchy */ + int new_status; + + /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ + if (--(*keycount) == 0) + return STAT_INSECURE; + + if (status == STAT_NEED_KEY) + new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); + else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG) + { + new_status = dnssec_validate_ds(now, header, n, name, keyname, class); + if (status == STAT_NEED_DS) + { + if (new_status == STAT_NO_DS) + new_status = STAT_INSECURE_DS; + if (new_status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + { + new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); + if (new_status == STAT_INSECURE) + new_status = STAT_INSECURE_DS; + } + else + new_status = STAT_INSECURE_DS; + } + else if (new_status == STAT_NO_NS) + new_status = STAT_BOGUS; + } + } + else if (status == STAT_CHASE_CNAME) + new_status = dnssec_chase_cname(now, header, n, name, keyname); + else + { + new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL); + + if (new_status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); + else + new_status = STAT_INSECURE; + } + } + + /* Can't validate because we need a key/DS whose name now in keyname. + Make query for same, and recurse to validate */ + if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY) + { + size_t m; + unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); + unsigned char *payload = &packet[2]; + struct dns_header *new_header = (struct dns_header *)payload; + u16 *length = (u16 *)packet; + unsigned char c1, c2; + + if (!packet) + return STAT_INSECURE; + + another_tcp_key: + m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class, + new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz); + + *length = htons(m); + + if (!read_write(server->tcpfd, packet, m + sizeof(u16), 0) || + !read_write(server->tcpfd, &c1, 1, 1) || + !read_write(server->tcpfd, &c2, 1, 1) || + !read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) + new_status = STAT_INSECURE; + else + { + m = (c1 << 8) | c2; + + new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount); + + if (new_status == STAT_SECURE) + { + /* Reached a validated record, now try again at this level. + Note that we may get ANOTHER NEED_* if an answer needs more than one key. + If so, go round again. */ + + if (status == STAT_NEED_KEY) + new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); + else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG) + { + new_status = dnssec_validate_ds(now, header, n, name, keyname, class); + if (status == STAT_NEED_DS) + { + if (new_status == STAT_NO_DS) + new_status = STAT_INSECURE_DS; + else if (new_status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + { + new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); + if (new_status == STAT_INSECURE) + new_status = STAT_INSECURE_DS; + } + else + new_status = STAT_INSECURE_DS; + } + else if (new_status == STAT_NO_NS) + new_status = STAT_BOGUS; + } + } + else if (status == STAT_CHASE_CNAME) + new_status = dnssec_chase_cname(now, header, n, name, keyname); + else + { + new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL); + + if (new_status == STAT_NO_SIG) + { + if (option_bool(OPT_DNSSEC_NO_SIGN)) + new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); + else + new_status = STAT_INSECURE; + } + } + + if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY) + goto another_tcp_key; + } + } + + free(packet); + } + return new_status; +} +#endif + /* The daemon forks before calling this: it should deal with one connection, blocking as neccessary, and then return. Note, need to be a bit careful about resources for debug mode, when the fork is suppressed: that's done by the caller. */ unsigned char *tcp_request(int confd, time_t now, - struct in_addr local_addr, struct in_addr netmask) + union mysockaddr *local_addr, struct in_addr netmask, int auth_dns) { size_t size = 0; int norebind = 0; - int checking_disabled; +#ifdef HAVE_AUTH + int local_auth = 0; +#endif + int checking_disabled, ad_question, do_bit, added_pheader = 0; + int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; size_t m; - unsigned short qtype, gotname; + unsigned short qtype; + unsigned int gotname; unsigned char c1, c2; - /* Max TCP packet + slop */ - unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ); - struct dns_header *header; + /* Max TCP packet + slop + size */ + unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); + unsigned char *payload = &packet[2]; + /* largest field in header is 16-bits, so this is still sufficiently aligned */ + struct dns_header *header = (struct dns_header *)payload; + u16 *length = (u16 *)packet; struct server *last_server; + struct in_addr dst_addr_4; + union mysockaddr peer_addr; + socklen_t peer_len = sizeof(union mysockaddr); + int query_count = 0; + + if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1) + return packet; + /* We can be configured to only accept queries from at-most-one-hop-away addresses. */ + if (option_bool(OPT_LOCAL_SERVICE)) + { + struct addrlist *addr; +#ifdef HAVE_IPV6 + if (peer_addr.sa.sa_family == AF_INET6) + { + for (addr = daemon->interface_addrs; addr; addr = addr->next) + if ((addr->flags & ADDRLIST_IPV6) && + is_same_net6(&addr->addr.addr.addr6, &peer_addr.in6.sin6_addr, addr->prefixlen)) + break; + } + else +#endif + { + struct in_addr netmask; + for (addr = daemon->interface_addrs; addr; addr = addr->next) + { + netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen)); + if (!(addr->flags & ADDRLIST_IPV6) && + is_same_net(addr->addr.addr.addr4, peer_addr.in.sin_addr, netmask)) + break; + } + } + if (!addr) + { + my_syslog(LOG_WARNING, _("Ignoring query from non-local network")); + return packet; + } + } + while (1) { - if (!packet || + if (query_count == TCP_MAX_QUERIES || + !packet || !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) || !(size = c1 << 8 | c2) || - !read_write(confd, packet, size, 1)) + !read_write(confd, payload, size, 1)) return packet; if (size < (int)sizeof(struct dns_header)) continue; - header = (struct dns_header *)packet; + query_count++; + + /* log_query gets called indirectly all over the place, so + pass these in global variables - sorry. */ + daemon->log_display_id = ++daemon->log_id; + daemon->log_source_addr = &peer_addr; + + check_subnet = 0; /* save state of "cd" flag in query */ - checking_disabled = header->hb4 & HB4_CD; + if ((checking_disabled = header->hb4 & HB4_CD)) + no_cache_dnssec = 1; - /* RFC 4035: sect 4.6 para 2 */ - header->hb4 &= ~HB4_AD; - if ((gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) { - union mysockaddr peer_addr; - socklen_t peer_len = sizeof(union mysockaddr); +#ifdef HAVE_AUTH + struct auth_zone *zone; +#endif + char *types = querystr(auth_dns ? "auth" : "query", qtype); - if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) != -1) - { - char types[20]; - - querystr(types, qtype); - - if (peer_addr.sa.sa_family == AF_INET) - log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&peer_addr.in.sin_addr, types); + if (peer_addr.sa.sa_family == AF_INET) + log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&peer_addr.in.sin_addr, types); #ifdef HAVE_IPV6 - else - log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&peer_addr.in6.sin6_addr, types); + else + log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&peer_addr.in6.sin6_addr, types); +#endif + +#ifdef HAVE_AUTH + /* find queries for zones we're authoritative for, and answer them directly */ + if (!auth_dns) + for (zone = daemon->auth_zones; zone; zone = zone->next) + if (in_zone(zone, daemon->namebuff, NULL)) + { + auth_dns = 1; + local_auth = 1; + break; + } #endif - } } - /* m > 0 if answered from cache */ - m = answer_request(header, ((char *) header) + 65536, (unsigned int)size, - local_addr, netmask, now); - - /* Do this by steam now we're not in the select() loop */ - check_log_writer(NULL); + if (local_addr->sa.sa_family == AF_INET) + dst_addr_4 = local_addr->in.sin_addr; + else + dst_addr_4.s_addr = 0; - if (m == 0) +#ifdef HAVE_AUTH + if (auth_dns) + m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, local_auth); + else +#endif { - unsigned int flags = 0; - struct all_addr *addrp = NULL; - int type = 0; - char *domain = NULL; - - if (option_bool(OPT_ADD_MAC)) + /* m > 0 if answered from cache */ + m = answer_request(header, ((char *) header) + 65536, (size_t)size, + dst_addr_4, netmask, now, &ad_question, &do_bit); + + /* Do this by steam now we're not in the select() loop */ + check_log_writer(1); + + if (m == 0) { - union mysockaddr peer_addr; - socklen_t peer_len = sizeof(union mysockaddr); + unsigned int flags = 0; + struct all_addr *addrp = NULL; + int type = 0; + char *domain = NULL; - if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) != -1) + if (option_bool(OPT_ADD_MAC)) size = add_mac(header, size, ((char *) header) + 65536, &peer_addr); - } - - if (gotname) - flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); - - if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) - last_server = daemon->servers; - else - last_server = daemon->last_server; - - if (!flags && last_server) - { - struct server *firstsendto = NULL; - unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); - - /* Loop round available servers until we succeed in connecting to one. - Note that this code subtley ensures that consecutive queries on this connection - which can go to the same server, do so. */ - while (1) - { - if (!firstsendto) - firstsendto = last_server; - else + + if (option_bool(OPT_CLIENT_SUBNET)) + { + size_t new = add_source_addr(header, size, ((char *) header) + 65536, &peer_addr); + if (size != new) { - if (!(last_server = last_server->next)) - last_server = daemon->servers; - - if (last_server == firstsendto) - break; + size = new; + check_subnet = 1; } + } + + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); - /* server for wrong domain */ - if (type != (last_server->flags & SERV_TYPE) || - (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain))) - continue; - - if ((last_server->tcpfd == -1) && - (last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) != -1 && - (!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 1) || - connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) + if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) + last_server = daemon->servers; + else + last_server = daemon->last_server; + + if (!flags && last_server) + { + struct server *firstsendto = NULL; +#ifdef HAVE_DNSSEC + unsigned char *newhash, hash[HASH_SIZE]; + if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff))) + memcpy(hash, newhash, HASH_SIZE); + else + memset(hash, 0, HASH_SIZE); +#else + unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); +#endif + /* Loop round available servers until we succeed in connecting to one. + Note that this code subtley ensures that consecutive queries on this connection + which can go to the same server, do so. */ + while (1) { - close(last_server->tcpfd); - last_server->tcpfd = -1; - } - - if (last_server->tcpfd == -1) - continue; + if (!firstsendto) + firstsendto = last_server; + else + { + if (!(last_server = last_server->next)) + last_server = daemon->servers; + + if (last_server == firstsendto) + break; + } + + /* server for wrong domain */ + if (type != (last_server->flags & SERV_TYPE) || + (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) || + (last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) + continue; + + if (last_server->tcpfd == -1) + { + if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) + continue; + +#ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; + struct all_addr local; +#ifdef HAVE_IPV6 + if (local_addr->sa.sa_family == AF_INET6) + local.addr.addr6 = local_addr->in6.sin6_addr; + else +#endif + local.addr.addr4 = local_addr->in.sin_addr; + + if (get_incoming_mark(&peer_addr, &local, 1, &mark)) + setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } +#endif + + if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 1) || + connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + continue; + } + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + size_t new_size = add_do_bit(header, size, ((char *) header) + 65536); + + /* For debugging, set Checking Disabled, otherwise, have the upstream check too, + this allows it to select auth servers when one is returning bad data. */ + if (option_bool(OPT_DNSSEC_DEBUG)) + header->hb4 |= HB4_CD; + + if (size != new_size) + added_pheader = 1; + + size = new_size; + } +#endif + } + + *length = htons(size); - c1 = size >> 8; - c2 = size; - - if (!read_write(last_server->tcpfd, &c1, 1, 0) || - !read_write(last_server->tcpfd, &c2, 1, 0) || - !read_write(last_server->tcpfd, packet, size, 0) || - !read_write(last_server->tcpfd, &c1, 1, 1) || - !read_write(last_server->tcpfd, &c2, 1, 1)) - { - close(last_server->tcpfd); - last_server->tcpfd = -1; - continue; - } - - m = (c1 << 8) | c2; - if (!read_write(last_server->tcpfd, packet, m, 1)) - return packet; - - if (!gotname) - strcpy(daemon->namebuff, "query"); - if (last_server->addr.sa.sa_family == AF_INET) - log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&last_server->addr.in.sin_addr, NULL); + /* get query name again for logging - may have been overwritten */ + if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) + strcpy(daemon->namebuff, "query"); + + if (!read_write(last_server->tcpfd, packet, size + sizeof(u16), 0) || + !read_write(last_server->tcpfd, &c1, 1, 1) || + !read_write(last_server->tcpfd, &c2, 1, 1) || + !read_write(last_server->tcpfd, payload, (c1 << 8) | c2, 1)) + { + close(last_server->tcpfd); + last_server->tcpfd = -1; + continue; + } + + m = (c1 << 8) | c2; + + if (last_server->addr.sa.sa_family == AF_INET) + log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&last_server->addr.in.sin_addr, NULL); #ifdef HAVE_IPV6 - else - log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, - (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); + else + log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, + (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); #endif - - /* There's no point in updating the cache, since this process will exit and - lose the information after a few queries. We make this call for the alias and - bogus-nxdomain side-effects. */ - /* If the crc of the question section doesn't match the crc we sent, then - someone might be attempting to insert bogus values into the cache by - sending replies containing questions and bogus answers. */ - if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff)) - m = process_reply(header, now, last_server, (unsigned int)m, - option_bool(OPT_NO_REBIND) && !norebind, checking_disabled); - - break; + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled) + { + int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ + int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount); + char *result, *domain = "result"; + + if (status == STAT_INSECURE_DS) + { + /* We only cache sigs when we've validated a reply. + Avoid caching a reply with sigs if there's a vaildated break in the + DS chain, so we don't return replies from cache missing sigs. */ + status = STAT_INSECURE; + no_cache_dnssec = 1; + } + + if (keycount == 0) + { + result = "ABANDONED"; + status = STAT_BOGUS; + } + else + result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + + if (status == STAT_BOGUS && extract_request(header, m, daemon->namebuff, NULL)) + domain = daemon->namebuff; + + log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); + + if (status == STAT_BOGUS) + { + no_cache_dnssec = 1; + bogusanswer = 1; + } + + if (status == STAT_SECURE) + cache_secure = 1; + } +#endif + + /* restore CD bit to the value in the query */ + if (checking_disabled) + header->hb4 |= HB4_CD; + else + header->hb4 &= ~HB4_CD; + + /* There's no point in updating the cache, since this process will exit and + lose the information after a few queries. We make this call for the alias and + bogus-nxdomain side-effects. */ + /* If the crc of the question section doesn't match the crc we sent, then + someone might be attempting to insert bogus values into the cache by + sending replies containing questions and bogus answers. */ +#ifdef HAVE_DNSSEC + newhash = hash_questions(header, (unsigned int)m, daemon->namebuff); + if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0) + { + m = 0; + break; + } +#else + if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff)) + { + m = 0; + break; + } +#endif + + m = process_reply(header, now, last_server, (unsigned int)m, + option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, + ad_question, do_bit, added_pheader, check_subnet, &peer_addr); + + break; + } } + + /* In case of local answer or no connections made. */ + if (m == 0) + m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); } - - /* In case of local answer or no connections made. */ - if (m == 0) - m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); } - - check_log_writer(NULL); + + check_log_writer(1); - c1 = m>>8; - c2 = m; - if (!read_write(confd, &c1, 1, 0) || - !read_write(confd, &c2, 1, 0) || - !read_write(confd, packet, m, 0)) + *length = htons(m); + + if (m == 0 || !read_write(confd, packet, m + sizeof(u16), 0)) return packet; } } @@ -993,13 +2175,19 @@ static struct frec *allocate_frec(time_t now) #ifdef HAVE_IPV6 f->rfd6 = NULL; #endif +#ifdef HAVE_DNSSEC + f->dependent = NULL; + f->blocking_query = NULL; + f->stash = NULL; + f->orig_domain = NULL; +#endif daemon->frec_list = f; } return f; } -static struct randfd *allocate_rfd(int family) +struct randfd *allocate_rfd(int family) { static int finger = 0; int i; @@ -1035,28 +2223,52 @@ static struct randfd *allocate_rfd(int family) return NULL; /* doom */ } +void free_rfd(struct randfd *rfd) +{ + if (rfd && --(rfd->refcount) == 0) + close(rfd->fd); +} + static void free_frec(struct frec *f) { - if (f->rfd4 && --(f->rfd4->refcount) == 0) - close(f->rfd4->fd); - + free_rfd(f->rfd4); f->rfd4 = NULL; f->sentto = NULL; f->flags = 0; #ifdef HAVE_IPV6 - if (f->rfd6 && --(f->rfd6->refcount) == 0) - close(f->rfd6->fd); - + free_rfd(f->rfd6); f->rfd6 = NULL; #endif + +#ifdef HAVE_DNSSEC + if (f->stash) + { + blockdata_free(f->stash); + f->stash = NULL; + } + + if (f->orig_domain) + { + blockdata_free(f->orig_domain); + f->orig_domain = NULL; + } + + /* Anything we're waiting on is pointless now, too */ + if (f->blocking_query) + free_frec(f->blocking_query); + f->blocking_query = NULL; + f->dependent = NULL; +#endif } /* if wait==NULL return a free or older than TIMEOUT record. else return *wait zero if one available, or *wait is delay to when the oldest in-use record will expire. Impose an absolute - limit of 4*TIMEOUT before we wipe things (for random sockets) */ -struct frec *get_new_frec(time_t now, int *wait) + limit of 4*TIMEOUT before we wipe things (for random sockets). + If force is set, always return a result, even if we have + to allocate above the limit. */ +struct frec *get_new_frec(time_t now, int *wait, int force) { struct frec *f, *oldest, *target; int count; @@ -1105,10 +2317,19 @@ struct frec *get_new_frec(time_t now, int *wait) } /* none available, calculate time 'till oldest record expires */ - if (count > daemon->ftabsize) + if (!force && count > daemon->ftabsize) { + static time_t last_log = 0; + if (oldest && wait) *wait = oldest->time + (time_t)TIMEOUT - now; + + if ((int)difftime(now, last_log) > 5) + { + last_log = now; + my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize); + } + return NULL; } @@ -1120,13 +2341,13 @@ struct frec *get_new_frec(time_t now, int *wait) } /* crc is all-ones if not known. */ -static struct frec *lookup_frec(unsigned short id, unsigned int crc) +static struct frec *lookup_frec(unsigned short id, void *hash) { struct frec *f; for(f = daemon->frec_list; f; f = f->next) if (f->sentto && f->new_id == id && - (f->crc == crc || crc == 0xffffffff)) + (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0)) return f; return NULL; @@ -1134,19 +2355,39 @@ static struct frec *lookup_frec(unsigned short id, unsigned int crc) static struct frec *lookup_frec_by_sender(unsigned short id, union mysockaddr *addr, - unsigned int crc) + void *hash) { struct frec *f; for(f = daemon->frec_list; f; f = f->next) if (f->sentto && f->orig_id == id && - f->crc == crc && + memcmp(hash, f->hash, HASH_SIZE) == 0 && sockaddr_isequal(&f->source, addr)) return f; return NULL; } + +/* Send query packet again, if we can. */ +void resend_query() +{ + if (daemon->srv_save) + { + int fd; + + if (daemon->srv_save->sfd) + fd = daemon->srv_save->sfd->fd; + else if (daemon->rfd_save && daemon->rfd_save->refcount != 0) + fd = daemon->rfd_save->fd; + else + return; + + while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0, + &daemon->srv_save->addr.sa, + sa_len(&daemon->srv_save->addr)))); + } +} /* A server record is going away, remove references to it */ void server_gone(struct server *server) @@ -1165,13 +2406,13 @@ void server_gone(struct server *server) } /* return unique random ids. */ -static unsigned short get_id(unsigned int crc) +static unsigned short get_id(void) { unsigned short ret = 0; do ret = rand16(); - while (lookup_frec(ret, crc)); + while (lookup_frec(ret, NULL)); return ret; } |