diff options
Diffstat (limited to 'src/rfc1035.c')
-rw-r--r-- | src/rfc1035.c | 1242 |
1 files changed, 882 insertions, 360 deletions
diff --git a/src/rfc1035.c b/src/rfc1035.c index 889c1f0..56647b0 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.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,22 +16,11 @@ #include "dnsmasq.h" -static int add_resource_record(struct dns_header *header, char *limit, int *truncp, - unsigned int nameoffset, unsigned char **pp, - unsigned long ttl, unsigned int *offset, unsigned short type, - unsigned short class, char *format, ...); - -#define CHECK_LEN(header, pp, plen, len) \ - ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) - -#define ADD_RDLEN(header, pp, plen, len) \ - (!CHECK_LEN(header, pp, plen, len) ? 0 : (long)((pp) += (len)), 1) - -static int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, - char *name, int isExtract, int extrabytes) +int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, + char *name, int isExtract, int extrabytes) { unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL; - unsigned int j, l, hops = 0; + unsigned int j, l, namelen = 0, hops = 0; int retvalue = 1; if (isExtract) @@ -88,49 +77,10 @@ static int extract_name(struct dns_header *header, size_t plen, unsigned char ** p = l + (unsigned char *)header; } - else if (label_type == 0x80) - return 0; /* reserved */ - else if (label_type == 0x40) - { /* ELT */ - unsigned int count, digs; - - if ((l & 0x3f) != 1) - return 0; /* we only understand bitstrings */ - - if (!isExtract) - return 0; /* Cannot compare bitsrings */ - - count = *p++; - if (count == 0) - count = 256; - digs = ((count-1)>>2)+1; - - /* output is \[x<hex>/siz]. which is digs+9 chars */ - if (cp - (unsigned char *)name + digs + 9 >= MAXDNAME) - return 0; - if (!CHECK_LEN(header, p, plen, (count-1)>>3)) - return 0; - - *cp++ = '\\'; - *cp++ = '['; - *cp++ = 'x'; - for (j=0; j<digs; j++) - { - unsigned int dig; - if (j%2 == 0) - dig = *p >> 4; - else - dig = *p++ & 0x0f; - - *cp++ = dig < 10 ? dig + '0' : dig + 'A' - 10; - } - cp += sprintf((char *)cp, "/%d]", count); - /* do this here to overwrite the zero char from sprintf */ - *cp++ = '.'; - } - else + else if (label_type == 0x00) { /* label_type = 0 -> label. */ - if (cp - (unsigned char *)name + l + 1 >= MAXDNAME) + namelen += l + 1; /* include period */ + if (namelen >= MAXDNAME) return 0; if (!CHECK_LEN(header, p, plen, l)) return 0; @@ -139,8 +89,21 @@ static int extract_name(struct dns_header *header, size_t plen, unsigned char ** if (isExtract) { unsigned char c = *p; - if (isascii(c) && !iscntrl(c) && c != '.') - *cp++ = *p; +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID)) + { + if (c == 0 || c == '.' || c == NAME_ESCAPE) + { + *cp++ = NAME_ESCAPE; + *cp++ = c+1; + } + else + *cp++ = c; + } + else +#endif + if (c != 0 && c != '.') + *cp++ = c; else return 0; } @@ -155,25 +118,32 @@ static int extract_name(struct dns_header *header, size_t plen, unsigned char ** cp++; if (c1 >= 'A' && c1 <= 'Z') c1 += 'a' - 'A'; +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && c1 == NAME_ESCAPE) + c1 = (*cp++)-1; +#endif + if (c2 >= 'A' && c2 <= 'Z') c2 += 'a' - 'A'; - + if (c1 != c2) retvalue = 2; } } - + if (isExtract) *cp++ = '.'; else if (*cp != 0 && *cp++ != '.') retvalue = 2; } + else + return 0; /* label types 0x40 and 0x80 not supported */ } } /* Max size of input string (for IPv6) is 75 chars.) */ #define MAXARPANAME 75 -static int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) +int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) { int j; char name[MAXARPANAME+1], *cp1; @@ -278,7 +248,7 @@ static int in_arpa_name_2_addr(char *namein, struct all_addr *addrp) return 0; } -static unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes) +unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes) { while(1) { @@ -333,7 +303,7 @@ static unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, return ansp; } -static unsigned char *skip_questions(struct dns_header *header, size_t plen) +unsigned char *skip_questions(struct dns_header *header, size_t plen) { int q; unsigned char *ansp = (unsigned char *)(header+1); @@ -348,7 +318,7 @@ static unsigned char *skip_questions(struct dns_header *header, size_t plen) return ansp; } -static unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen) +unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen) { int i, rdlen; @@ -371,6 +341,7 @@ static unsigned char *skip_section(unsigned char *ansp, int count, struct dns_he than CRC the raw bytes, since replies might be compressed differently. We ignore case in the names for the same reason. Return all-ones if there is not question section. */ +#ifndef HAVE_DNSSEC unsigned int questions_crc(struct dns_header *header, size_t plen, char *name) { int q; @@ -411,7 +382,7 @@ unsigned int questions_crc(struct dns_header *header, size_t plen, char *name) return crc; } - +#endif size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen) { @@ -504,7 +475,7 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t else if (is_sign && i == arcount - 1 && class == C_ANY && - (type == T_SIG || type == T_TSIG)) + type == T_TSIG) *is_sign = 1; } @@ -517,94 +488,117 @@ struct macparm { size_t plen; union mysockaddr *l3; }; - -static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) -{ - struct macparm *parm = parmv; - int match = 0; - unsigned short rdlen; - struct dns_header *header = parm->header; - unsigned char *lenp, *datap, *p; - - if (family == parm->l3->sa.sa_family) - { - if (family == AF_INET && memcmp (&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0) - match = 1; -#ifdef HAVE_IPV6 - else - if (family == AF_INET6 && memcmp (&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0) - match = 1; -#endif - } - if (!match) - return 1; /* continue */ +static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit, + int optno, unsigned char *opt, size_t optlen, int set_do) +{ + unsigned char *lenp, *datap, *p; + int rdlen, is_sign; - if (ntohs(header->arcount) == 0) + if (!(p = find_pseudoheader(header, plen, NULL, NULL, &is_sign))) { + if (is_sign) + return plen; + /* We are adding the pseudoheader */ - if (!(p = skip_questions(header, parm->plen)) || + if (!(p = skip_questions(header, plen)) || !(p = skip_section(p, - ntohs(header->ancount) + ntohs(header->nscount), - header, parm->plen))) - return 0; + ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount), + header, plen))) + return plen; *p++ = 0; /* empty name */ PUTSHORT(T_OPT, p); - PUTSHORT(PACKETSZ, p); /* max packet length - is 512 suitable default for non-EDNS0 resolvers? */ - PUTLONG(0, p); /* extended RCODE */ + PUTSHORT(SAFE_PKTSZ, p); /* max packet length, this will be overwritten */ + PUTSHORT(0, p); /* extended RCODE and version */ + PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */ lenp = p; PUTSHORT(0, p); /* RDLEN */ rdlen = 0; - if (((ssize_t)maclen) > (parm->limit - (p + 4))) - return 0; /* Too big */ - header->arcount = htons(1); + if (((ssize_t)optlen) > (limit - (p + 4))) + return plen; /* Too big */ + header->arcount = htons(ntohs(header->arcount) + 1); datap = p; } else { - int i, is_sign; - unsigned short code, len; + int i; + unsigned short code, len, flags; + /* Must be at the end, if exists */ if (ntohs(header->arcount) != 1 || - !(p = find_pseudoheader(header, parm->plen, NULL, NULL, &is_sign)) || is_sign || - (!(p = skip_name(p, header, parm->plen, 10)))) - return 0; - - p += 8; /* skip UDP length and RCODE */ + (!(p = skip_name(p, header, plen, 10)))) + return plen; + p += 6; /* skip UDP length and RCODE */ + GETSHORT(flags, p); + if (set_do) + { + p -=2; + PUTSHORT(flags | 0x8000, p); + } + lenp = p; GETSHORT(rdlen, p); - if (!CHECK_LEN(header, p, parm->plen, rdlen)) - return 0; /* bad packet */ + if (!CHECK_LEN(header, p, plen, rdlen)) + return plen; /* bad packet */ datap = p; + /* no option to add */ + if (optno == 0) + return plen; + /* check if option already there */ for (i = 0; i + 4 < rdlen; i += len + 4) { GETSHORT(code, p); GETSHORT(len, p); - if (code == EDNS0_OPTION_MAC) - return 0; + if (code == optno) + return plen; p += len; } - if (((ssize_t)maclen) > (parm->limit - (p + 4))) - return 0; /* Too big */ + if (((ssize_t)optlen) > (limit - (p + 4))) + return plen; /* Too big */ } - PUTSHORT(EDNS0_OPTION_MAC, p); - PUTSHORT(maclen, p); - memcpy(p, mac, maclen); - p += maclen; + if (optno != 0) + { + PUTSHORT(optno, p); + PUTSHORT(optlen, p); + memcpy(p, opt, optlen); + p += optlen; + } PUTSHORT(p - datap, lenp); - parm->plen = p - (unsigned char *)header; + return p - (unsigned char *)header; + +} + +static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) +{ + struct macparm *parm = parmv; + int match = 0; + + if (family == parm->l3->sa.sa_family) + { + if (family == AF_INET && memcmp(&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0) + match = 1; +#ifdef HAVE_IPV6 + else + if (family == AF_INET6 && memcmp(&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0) + match = 1; +#endif + } + + if (!match) + return 1; /* continue */ + + parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen, 0); return 0; /* done */ } - size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3) { struct macparm parm; @@ -625,9 +619,111 @@ size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysock return parm.plen; } - +struct subnet_opt { + u16 family; + u8 source_netmask, scope_netmask; +#ifdef HAVE_IPV6 + u8 addr[IN6ADDRSZ]; +#else + u8 addr[INADDRSZ]; +#endif +}; + +static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) +{ + /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ + + int len; + void *addrp; + +#ifdef HAVE_IPV6 + if (source->sa.sa_family == AF_INET6) + { + opt->family = htons(2); + opt->source_netmask = daemon->addr6_netmask; + addrp = &source->in6.sin6_addr; + } + else +#endif + { + opt->family = htons(1); + opt->source_netmask = daemon->addr4_netmask; + addrp = &source->in.sin_addr; + } + + opt->scope_netmask = 0; + len = 0; + + if (opt->source_netmask != 0) + { + len = ((opt->source_netmask - 1) >> 3) + 1; + memcpy(opt->addr, addrp, len); + if (opt->source_netmask & 7) + opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7)); + } + + return len + 4; +} + +size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source) +{ + /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ + + int len; + struct subnet_opt opt; + + len = calc_subnet_opt(&opt, source); + return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0); +} + +#ifdef HAVE_DNSSEC +size_t add_do_bit(struct dns_header *header, size_t plen, char *limit) +{ + return add_pseudoheader(header, plen, (unsigned char *)limit, 0, NULL, 0, 1); +} +#endif + +int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer) +{ + /* Section 9.2, Check that subnet option in reply matches. */ + + + int len, calc_len; + struct subnet_opt opt; + unsigned char *p; + int code, i, rdlen; + + calc_len = calc_subnet_opt(&opt, peer); + + if (!(p = skip_name(pseudoheader, header, plen, 10))) + return 1; + + p += 8; /* skip UDP length and RCODE */ + + GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, plen, rdlen)) + return 1; /* bad packet */ + + /* check if option there */ + for (i = 0; i + 4 < rdlen; i += len + 4) + { + GETSHORT(code, p); + GETSHORT(len, p); + if (code == EDNS0_OPTION_CLIENT_SUBNET) + { + /* make sure this doesn't mismatch. */ + opt.scope_netmask = p[3]; + if (len != calc_len || memcmp(p, &opt, len) != 0) + return 0; + } + p += len; + } + + return 1; +} + /* is addr in the non-globally-routed IP space? */ -static int private_net(struct in_addr addr, int ban_localhost) +int private_net(struct in_addr addr, int ban_localhost) { in_addr_t ip_addr = ntohl(addr.s_addr); @@ -639,10 +735,9 @@ static int private_net(struct in_addr addr, int ban_localhost) ((ip_addr & 0xFFFF0000) == 0xA9FE0000) /* 169.254.0.0/16 (zeroconf) */ ; } -static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, char *name) +static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, char *name, int *doctored) { int i, qtype, qclass, rdlen; - unsigned long ttl; for (i = count; i != 0; i--) { @@ -656,7 +751,7 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * GETSHORT(qtype, p); GETSHORT(qclass, p); - GETLONG(ttl, p); + p += 4; /* ttl */ GETSHORT(rdlen, p); if (qclass == C_IN && qtype == T_A) @@ -685,6 +780,7 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * addr.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr); /* Since we munged the data, the server it came from is no longer authoritative */ header->hb3 &= ~HB3_AA; + *doctored = 1; memcpy(p, &addr, INADDRSZ); break; } @@ -700,15 +796,17 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * unsigned char *p2 = p1; /* make counted string zero-term and sanitise */ for (i = 0; i < len; i++) - if (isprint(*(p2+1))) - { - *p2 = *(p2+1); - p2++; - } + { + if (!isprint((int)*(p2+1))) + break; + + *p2 = *(p2+1); + p2++; + } *p2 = 0; my_syslog(LOG_INFO, "reply %s is %s", name, p1); /* restore */ - memmove(p1 + 1, p1, len); + memmove(p1 + 1, p1, i); *p1 = len; p1 += len+1; } @@ -721,7 +819,7 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * return p; } -static int find_soa(struct dns_header *header, size_t qlen, char *name) +static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doctored) { unsigned char *p; int qtype, qclass, rdlen; @@ -730,7 +828,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) /* first move to NS section and find TTL from any SOA section */ if (!(p = skip_questions(header, qlen)) || - !(p = do_doctor(p, ntohs(header->ancount), header, qlen, name))) + !(p = do_doctor(p, ntohs(header->ancount), header, qlen, name, doctored))) return 0; /* bad packet */ for (i = ntohs(header->nscount); i != 0; i--) @@ -765,8 +863,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) return 0; /* bad packet */ } - /* rewrite addresses in additioal section too */ - if (!do_doctor(p, ntohs(header->arcount), header, qlen, NULL)) + /* rewrite addresses in additional section too */ + if (!do_doctor(p, ntohs(header->arcount), header, qlen, NULL, doctored)) return 0; if (!found_soa) @@ -780,20 +878,29 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name) expired and cleaned out that way. Return 1 if we reject an address because it look like part of dns-rebinding attack. */ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, - int is_sign, int check_rebind, int checking_disabled) + char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure, int *doctored) { unsigned char *p, *p1, *endrr, *namep; int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; unsigned long ttl = 0; struct all_addr addr; - +#ifdef HAVE_IPSET + char **ipsets_cur; +#else + (void)ipsets; /* unused */ +#endif + cache_start_insert(); /* find_soa is needed for dns_doctor and logging side-effects, so don't call it lazily if there are any. */ - if (daemon->doctors || option_bool(OPT_LOG)) + if (daemon->doctors || option_bool(OPT_LOG) || option_bool(OPT_DNSSEC_VALID)) { searched_soa = 1; - ttl = find_soa(header, qlen, name); + ttl = find_soa(header, qlen, name, doctored); +#ifdef HAVE_DNSSEC + if (*doctored && secure) + return 0; +#endif } /* go through the questions. */ @@ -801,11 +908,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t for (i = ntohs(header->qdcount); i != 0; i--) { - int found = 0, cname_count = 5; + int found = 0, cname_count = CNAME_CHAIN; struct crec *cpp = NULL; int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; + int secflag = secure ? F_DNSSECOK : 0; unsigned long cttl = ULONG_MAX, attl; - + namep = p; if (!extract_name(header, qlen, &p, name, 1, 4)) return 0; /* bad packet */ @@ -861,12 +969,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (aqtype == T_CNAME) { - if (!cname_count--) - return 0; /* looped CNAMES */ + if (!cname_count-- || secure) + return 0; /* looped CNAMES, or DNSSEC, which we can't cache. */ goto cname_loop; } - cache_insert(name, &addr, now, cttl, name_encoding | F_REVERSE); + cache_insert(name, &addr, now, cttl, name_encoding | secflag | F_REVERSE); found = 1; } @@ -881,10 +989,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (!searched_soa) { searched_soa = 1; - ttl = find_soa(header, qlen, NULL); + ttl = find_soa(header, qlen, NULL, doctored); } if (ttl) - cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags); + cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | secflag); } } else @@ -908,78 +1016,106 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t else continue; - if (!(flags & F_NXDOMAIN)) + cname_loop1: + if (!(p1 = skip_questions(header, qlen))) + return 0; + + for (j = ntohs(header->ancount); j != 0; j--) { - cname_loop1: - if (!(p1 = skip_questions(header, qlen))) - return 0; + if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) + return 0; /* bad packet */ - for (j = ntohs(header->ancount); j != 0; j--) + GETSHORT(aqtype, p1); + GETSHORT(aqclass, p1); + GETLONG(attl, p1); + if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) { - if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) - return 0; /* bad packet */ - - GETSHORT(aqtype, p1); - GETSHORT(aqclass, p1); - GETLONG(attl, p1); - if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) - { - (p1) -= 4; - PUTLONG(daemon->max_ttl, p1); - } - GETSHORT(ardlen, p1); - endrr = p1+ardlen; - - if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype)) + (p1) -= 4; + PUTLONG(daemon->max_ttl, p1); + } + GETSHORT(ardlen, p1); + endrr = p1+ardlen; + + if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype)) + { + if (aqtype == T_CNAME) { - if (aqtype == T_CNAME) + if (!cname_count--) + return 0; /* looped CNAMES */ + newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD | secflag); + if (newc) { - if (!cname_count--) - return 0; /* looped CNAMES */ - newc = cache_insert(name, NULL, now, attl, F_CNAME | F_FORWARD); - if (newc && cpp) + newc->addr.cname.target.cache = NULL; + /* anything other than zero, to avoid being mistaken for CNAME to interface-name */ + newc->addr.cname.uid = 1; + if (cpp) { - cpp->addr.cname.cache = newc; + cpp->addr.cname.target.cache = newc; cpp->addr.cname.uid = newc->uid; } - - cpp = newc; - if (attl < cttl) - cttl = attl; - - if (!extract_name(header, qlen, &p1, name, 1, 0)) - return 0; - goto cname_loop1; } - else + + cpp = newc; + if (attl < cttl) + cttl = attl; + + if (!extract_name(header, qlen, &p1, name, 1, 0)) + return 0; + goto cname_loop1; + } + else if (!(flags & F_NXDOMAIN)) + { + found = 1; + + /* copy address into aligned storage */ + if (!CHECK_LEN(header, p1, qlen, addrlen)) + return 0; /* bad packet */ + memcpy(&addr, p1, addrlen); + + /* check for returned address in private space */ + if (check_rebind) { - found = 1; - - /* copy address into aligned storage */ - if (!CHECK_LEN(header, p1, qlen, addrlen)) - return 0; /* bad packet */ - memcpy(&addr, p1, addrlen); - - /* check for returned address in private space */ - if (check_rebind && - (flags & F_IPV4) && + if ((flags & F_IPV4) && private_net(addr.addr.addr4, !option_bool(OPT_LOCAL_REBIND))) return 1; - newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD); - if (newc && cpp) +#ifdef HAVE_IPV6 + if ((flags & F_IPV6) && + IN6_IS_ADDR_V4MAPPED(&addr.addr.addr6)) { - cpp->addr.cname.cache = newc; - cpp->addr.cname.uid = newc->uid; + struct in_addr v4; + v4.s_addr = ((const uint32_t *) (&addr.addr.addr6))[3]; + if (private_net(v4, !option_bool(OPT_LOCAL_REBIND))) + return 1; + } +#endif + } + +#ifdef HAVE_IPSET + if (ipsets && (flags & (F_IPV4 | F_IPV6))) + { + ipsets_cur = ipsets; + while (*ipsets_cur) + { + log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, name, &addr, *ipsets_cur); + add_to_ipset(*ipsets_cur++, &addr, flags, 0); } - cpp = NULL; } +#endif + + newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD | secflag); + if (newc && cpp) + { + cpp->addr.cname.target.cache = newc; + cpp->addr.cname.uid = newc->uid; + } + cpp = NULL; } - - p1 = endrr; - if (!CHECK_LEN(header, p1, qlen, 0)) - return 0; /* bad packet */ } + + p1 = endrr; + if (!CHECK_LEN(header, p1, qlen, 0)) + return 0; /* bad packet */ } if (!found && !option_bool(OPT_NO_NEG)) @@ -987,16 +1123,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t if (!searched_soa) { searched_soa = 1; - ttl = find_soa(header, qlen, NULL); + ttl = find_soa(header, qlen, NULL, doctored); } /* If there's no SOA to get the TTL from, but there is a CNAME pointing at this, inherit its TTL */ if (ttl || cpp) { - newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags); + newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | secflag); if (newc && cpp) { - cpp->addr.cname.cache = newc; + cpp->addr.cname.target.cache = newc; cpp->addr.cname.uid = newc->uid; } } @@ -1004,10 +1140,14 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t } } - /* Don't put stuff from a truncated packet into the cache, - also 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->hb3 & HB3_TC) && !(header->hb4 & HB4_CD) && !checking_disabled) + /* Don't put stuff from a truncated packet into the cache. + Don't cache replies from non-recursive nameservers, since we may get a + reply containing a CNAME but not its target, even though the target + does exist. */ + if (!(header->hb3 & HB3_TC) && + !(header->hb4 & HB4_CD) && + (header->hb4 & HB4_RA) && + !no_cache_dnssec) cache_end_insert(); return 0; @@ -1015,7 +1155,6 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t /* If the packet holds exactly one query return F_IPV4 or F_IPV6 and leave the name from the query in name */ - unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep) { unsigned char *p = (unsigned char *)(header+1); @@ -1044,8 +1183,6 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, return F_IPV6; if (qtype == T_ANY) return F_IPV4 | F_IPV6; - if (qtype == T_NS || qtype == T_SOA) - return F_QUERY | F_NSRR; } return F_QUERY; @@ -1055,7 +1192,10 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, size_t setup_reply(struct dns_header *header, size_t qlen, struct all_addr *addrp, unsigned int flags, unsigned long ttl) { - unsigned char *p = skip_questions(header, qlen); + unsigned char *p; + + if (!(p = skip_questions(header, qlen))) + return 0; /* clear authoritative and truncated flags, set QR flag */ header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR; @@ -1071,7 +1211,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen, SET_RCODE(header, NOERROR); /* empty domain */ else if (flags == F_NXDOMAIN) SET_RCODE(header, NXDOMAIN); - else if (p && flags == F_IPV4) + else if (flags == F_IPV4) { /* we know the address */ SET_RCODE(header, NOERROR); header->ancount = htons(1); @@ -1079,7 +1219,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen, add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_A, C_IN, "4", addrp); } #ifdef HAVE_IPV6 - else if (p && flags == F_IPV6) + else if (flags == F_IPV6) { SET_RCODE(header, NOERROR); header->ancount = htons(1); @@ -1101,12 +1241,22 @@ int check_for_local_domain(char *name, time_t now) struct txt_record *txt; struct interface_name *intr; struct ptr_record *ptr; - - if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6)) && - (crecp->flags & (F_HOSTS | F_DHCP))) + struct naptr *naptr; + + /* Note: the call to cache_find_by_name is intended to find any record which matches + ie A, AAAA, CNAME, DS. Because RRSIG records are marked by setting both F_DS and F_DNSKEY, + cache_find_by name ordinarily only returns records with an exact match on those bits (ie + for the call below, only DS records). The F_NSIGMATCH bit changes this behaviour */ + + if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_DS | F_NO_RR | F_NSIGMATCH)) && + (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) return 1; - for (mx = daemon->mxnames; mx; mx = mx->next) + for (naptr = daemon->naptr; naptr; naptr = naptr->next) + if (hostname_isequal(name, naptr->name)) + return 1; + + for (mx = daemon->mxnames; mx; mx = mx->next) if (hostname_isequal(name, mx->name)) return 1; @@ -1161,7 +1311,7 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, /* Found a bogus address. Insert that info here, since there no SOA record to get the ttl from in the normal processing */ cache_start_insert(); - cache_insert(name, NULL, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN | F_CONFIG); + cache_insert(name, NULL, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN); cache_end_insert(); return 1; @@ -1175,8 +1325,45 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, return 0; } -static int add_resource_record(struct dns_header *header, char *limit, int *truncp, unsigned int nameoffset, unsigned char **pp, - unsigned long ttl, unsigned int *offset, unsigned short type, unsigned short class, char *format, ...) +int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr) +{ + unsigned char *p; + int i, qtype, qclass, rdlen; + struct bogus_addr *baddrp; + + /* skip over questions */ + if (!(p = skip_questions(header, qlen))) + return 0; /* bad packet */ + + for (i = ntohs(header->ancount); i != 0; i--) + { + if (!(p = skip_name(p, header, qlen, 10))) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + p += 4; /* TTL */ + GETSHORT(rdlen, p); + + if (qclass == C_IN && qtype == T_A) + { + if (!CHECK_LEN(header, p, qlen, INADDRSZ)) + return 0; + + for (baddrp = baddr; baddrp; baddrp = baddrp->next) + if (memcmp(&baddrp->addr, p, INADDRSZ) == 0) + return 1; + } + + if (!ADD_RDLEN(header, p, qlen, rdlen)) + return 0; + } + + return 0; +} + +int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, + unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...) { va_list ap; unsigned char *sav, *p = *pp; @@ -1187,8 +1374,26 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun if (truncp && *truncp) return 0; + + va_start(ap, format); /* make ap point to 1st unamed argument */ + + if (nameoffset > 0) + { + PUTSHORT(nameoffset | 0xc000, p); + } + else + { + char *name = va_arg(ap, char *); + if (name) + p = do_rfc1035_name(p, name); + if (nameoffset < 0) + { + PUTSHORT(-nameoffset | 0xc000, p); + } + else + *p++ = 0; + } - PUTSHORT(nameoffset | 0xc000, p); PUTSHORT(type, p); PUTSHORT(class, p); PUTLONG(ttl, p); /* TTL */ @@ -1196,8 +1401,6 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun sav = p; /* Save pointer to RDLength field */ PUTSHORT(0, p); /* Placeholder RDLength */ - va_start(ap, format); /* make ap point to 1st unamed argument */ - for (; *format; format++) switch (*format) { @@ -1215,6 +1418,11 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun p += INADDRSZ; break; + case 'b': + usval = va_arg(ap, int); + *p++ = usval; + break; + case 's': usval = va_arg(ap, int); PUTSHORT(usval, p); @@ -1236,7 +1444,8 @@ static int add_resource_record(struct dns_header *header, char *limit, int *trun case 't': usval = va_arg(ap, int); sval = va_arg(ap, char *); - memcpy(p, sval, usval); + if (usval != 0) + memcpy(p, sval, usval); p += usval; break; @@ -1286,45 +1495,49 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now) /* return zero if we can't answer from cache, or packet size if we can */ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, - struct in_addr local_addr, struct in_addr local_netmask, time_t now) + struct in_addr local_addr, struct in_addr local_netmask, + time_t now, int *ad_reqd, int *do_bit) { char *name = daemon->namebuff; unsigned char *p, *ansp, *pheader; - int qtype, qclass; + unsigned int qtype, qclass; struct all_addr addr; - unsigned int nameoffset; + int nameoffset; unsigned short flag; int q, ans, anscount = 0, addncount = 0; - int dryrun = 0, sec_reqd = 0; - int is_sign; + int dryrun = 0, sec_reqd = 0, have_pseudoheader = 0; struct crec *crecp; - int nxdomain = 0, auth = 1, trunc = 0; + int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1; struct mx_srv_record *rec; + size_t len; + /* Don't return AD set if checking disabled. */ + if (header->hb4 & HB4_CD) + sec_data = 0; + + /* RFC 6840 5.7 */ + *ad_reqd = header->hb4 & HB4_AD; + *do_bit = 0; + /* If there is an RFC2671 pseudoheader then it will be overwritten by partial replies, so we have to do a dry run to see if we can answer the query. We check to see if the do bit is set, if so we always forward rather than answering from the cache, which doesn't include - security information. */ + security information, unless we're in DNSSEC validation mode. */ - if (find_pseudoheader(header, qlen, NULL, &pheader, &is_sign)) + if (find_pseudoheader(header, qlen, NULL, &pheader, NULL)) { - unsigned short udpsz, ext_rcode, flags; - unsigned char *psave = pheader; + unsigned short flags; + + have_pseudoheader = 1; - GETSHORT(udpsz, pheader); - GETSHORT(ext_rcode, pheader); + pheader += 4; /* udp size, ext_rcode */ GETSHORT(flags, pheader); - sec_reqd = flags & 0x8000; /* do bit */ - - /* If our client is advertising a larger UDP packet size - than we allow, trim it so that we don't get an overlarge - response from upstream */ - - if (!is_sign && (udpsz > daemon->edns_pktsz)) - PUTSHORT(daemon->edns_pktsz, psave); + if ((sec_reqd = flags & 0x8000)) + *do_bit = 1;/* do bit */ + *ad_reqd = 1; dryrun = 1; } @@ -1354,6 +1567,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, GETSHORT(qtype, p); GETSHORT(qclass, p); + /* Don't filter RRSIGS from answers to ANY queries, even if do-bit + not set. */ + if (qtype == T_ANY) + *do_bit = 1; + ans = 0; /* have we answered this question */ if (qtype == T_TXT || qtype == T_ANY) @@ -1366,10 +1584,20 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ans = 1; if (!dryrun) { + unsigned long ttl = daemon->local_ttl; + int ok = 1; log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>"); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, - T_TXT, t->class, "t", t->len, t->txt)) + /* Dynamically generate stat record */ + if (t->stat != 0) + { + ttl = 0; + if (!cache_make_stat(t)) + ok = 0; + } + + if (ok && add_resource_record(header, limit, &trunc, nameoffset, &ansp, + ttl, NULL, + T_TXT, t->class, "t", t->len, t->txt)) anscount++; } @@ -1377,8 +1605,116 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DNSKEY || qtype == T_DS)) + { + int gotone = 0; + struct blockdata *keydata; + + /* Do we have RRSIG? Can't do DS or DNSKEY otherwise. */ + if (sec_reqd) + { + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS))) + if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype) + break; + } + + if (!sec_reqd || crecp) + { + if (qtype == T_DS) + { + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, name, now, F_DS))) + if (crecp->uid == qclass) + { + gotone = 1; + if (!dryrun) + { + if (crecp->flags & F_NEG) + { + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + log_query(F_UPSTREAM, name, NULL, "no DS"); + } + else if ((keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL))) + { + struct all_addr a; + a.addr.keytag = crecp->addr.ds.keytag; + log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DS keytag %u"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_DS, qclass, "sbbt", + crecp->addr.ds.keytag, crecp->addr.ds.algo, + crecp->addr.ds.digest, crecp->addr.ds.keylen, keydata)) + anscount++; + + } + } + } + } + else /* DNSKEY */ + { + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY))) + if (crecp->uid == qclass) + { + gotone = 1; + if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL))) + { + struct all_addr a; + a.addr.keytag = crecp->addr.key.keytag; + log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DNSKEY keytag %u"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_DNSKEY, qclass, "sbbt", + crecp->addr.key.flags, 3, crecp->addr.key.algo, crecp->addr.key.keylen, keydata)) + anscount++; + } + } + } + } + + /* Now do RRSIGs */ + if (gotone) + { + ans = 1; + auth = 0; + if (!dryrun && sec_reqd) + { + crecp = NULL; + while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY | F_DS))) + if (crecp->uid == qclass && crecp->addr.sig.type_covered == qtype && + (keydata = blockdata_retrieve(crecp->addr.sig.keydata, crecp->addr.sig.keylen, NULL))) + { + add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_RRSIG, qclass, "t", crecp->addr.sig.keylen, keydata); + anscount++; + } + } + } + } +#endif + if (qclass == C_IN) { + struct txt_record *t; + + for (t = daemon->rr; t; t = t->next) + if ((t->class == qtype || qtype == T_ANY) && hostname_isequal(name, t->name)) + { + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_RRNAME, name, NULL, "<RR>"); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + t->class, C_IN, "t", t->len, t->txt)) + anscount ++; + } + } + if (qtype == T_PTR || qtype == T_ANY) { /* see if it's w.z.y.z.in-addr.arpa format */ @@ -1393,19 +1729,42 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (is_arpa == F_IPV4) for (intr = daemon->int_names; intr; intr = intr->next) { - if (addr.addr.addr4.s_addr == get_ifaddr(intr->intr).s_addr) + struct addrlist *addrlist; + + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr.addr4.s_addr == addrlist->addr.addr.addr4.s_addr) + break; + + if (addrlist) + break; + else + while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) + intr = intr->next; + } +#ifdef HAVE_IPV6 + else if (is_arpa == F_IPV6) + for (intr = daemon->int_names; intr; intr = intr->next) + { + struct addrlist *addrlist; + + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) + if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr.addr6, &addrlist->addr.addr.addr6)) + break; + + if (addrlist) break; else while (intr->next && strcmp(intr->intr, intr->next->intr) == 0) intr = intr->next; } +#endif if (intr) { ans = 1; if (!dryrun) { - log_query(F_IPV4 | F_REVERSE | F_CONFIG, intr->name, &addr, NULL); + log_query(is_arpa | F_REVERSE | F_CONFIG, intr->name, &addr, NULL); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, NULL, T_PTR, C_IN, "d", intr->name)) @@ -1428,38 +1787,90 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } } else if ((crecp = cache_find_by_addr(NULL, &addr, now, is_arpa))) - do - { - /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ - if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) - continue; - - if (crecp->flags & F_NEG) - { - ans = 1; - auth = 0; - if (crecp->flags & F_NXDOMAIN) - nxdomain = 1; - if (!dryrun) - log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL); - } - else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd) - { - ans = 1; - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; - if (!dryrun) - { - log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr, - record_source(crecp->uid)); - - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), NULL, - T_PTR, C_IN, "d", cache_get_name(crecp))) + { + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd) + { + if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))) + crecp = NULL; +#ifdef HAVE_DNSSEC + else if (crecp->flags & F_DNSSECOK) + { + int gotsig = 0; + struct crec *rr_crec = NULL; + + while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY))) + { + if (rr_crec->addr.sig.type_covered == T_PTR && rr_crec->uid == C_IN) + { + char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL); + gotsig = 1; + + if (!dryrun && + add_resource_record(header, limit, &trunc, nameoffset, &ansp, + rr_crec->ttd - now, &nameoffset, + T_RRSIG, C_IN, "t", crecp->addr.sig.keylen, sigdata)) + anscount++; + } + } + + if (!gotsig) + crecp = NULL; + } +#endif + } + + if (crecp) + { + do + { + /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ + if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) + continue; + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + if (crecp->flags & F_NEG) + { + ans = 1; + auth = 0; + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + if (!dryrun) + log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL); + } + else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd || option_bool(OPT_DNSSEC_VALID)) + { + ans = 1; + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + if (!dryrun) + { + log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr, + record_source(crecp->uid)); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, + T_PTR, C_IN, "d", cache_get_name(crecp))) + anscount++; + } + } + } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))); + } + } + else if (is_rev_synth(is_arpa, &addr, name)) + { + ans = 1; + if (!dryrun) + { + log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, + T_PTR, C_IN, "d", name)) anscount++; - } - } - } while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa))); + } + } else if (is_arpa == F_IPV4 && option_bool(OPT_BOGUSPRIV) && private_net(addr.addr.addr4, 1)) @@ -1476,7 +1887,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0) { unsigned short type = T_A; - + struct interface_name *intr; + if (flag == F_IPV6) #ifdef HAVE_IPV6 type = T_AAAA; @@ -1525,35 +1937,51 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } /* interface name stuff */ - if (qtype == T_A) + intname_restart: + for (intr = daemon->int_names; intr; intr = intr->next) + if (hostname_isequal(name, intr->name)) + break; + + if (intr) { - struct interface_name *intr; + struct addrlist *addrlist; + int gotit = 0; + enumerate_interfaces(0); + for (intr = daemon->int_names; intr; intr = intr->next) if (hostname_isequal(name, intr->name)) - break; - - if (intr) - { - ans = 1; - if (!dryrun) - { - if ((addr.addr.addr4 = get_ifaddr(intr->intr)).s_addr == (in_addr_t) -1) - log_query(F_FORWARD | F_CONFIG | F_IPV4 | F_NEG, name, NULL, NULL); - else + { + for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) +#ifdef HAVE_IPV6 + if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == type) +#endif { - log_query(F_FORWARD | F_CONFIG | F_IPV4, name, &addr, NULL); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - daemon->local_ttl, NULL, type, C_IN, "4", &addr)) - anscount++; +#ifdef HAVE_IPV6 + if (addrlist->flags & ADDRLIST_REVONLY) + continue; +#endif + ans = 1; + if (!dryrun) + { + gotit = 1; + log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, type, C_IN, + type == T_A ? "4" : "6", &addrlist->addr)) + anscount++; + } } - } - continue; - } + } + + if (!dryrun && !gotit) + log_query(F_FORWARD | F_CONFIG | flag | F_NEG, name, NULL, NULL); + + continue; } cname_restart: - if ((crecp = cache_find_by_name(NULL, name, now, flag | F_CNAME))) + if ((crecp = cache_find_by_name(NULL, name, now, flag | F_CNAME | (dryrun ? F_NO_RR : 0)))) { int localise = 0; @@ -1572,66 +2000,153 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); crecp = save; } - - do - { - /* don't answer wildcard queries with data not from /etc/hosts - or DHCP leases */ - if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) - break; - - if (crecp->flags & F_CNAME) + + /* If the client asked for DNSSEC and we can't provide RRSIGs, either + because we've not doing DNSSEC or the cached answer is signed by negative, + don't answer from the cache, forward instead. */ + if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && sec_reqd) + { + if (!option_bool(OPT_DNSSEC_VALID) || ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))) + crecp = NULL; +#ifdef HAVE_DNSSEC + else if (crecp->flags & F_DNSSECOK) { - if (!dryrun) - { - log_query(crecp->flags, name, NULL, record_source(crecp->uid)); - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), &nameoffset, - T_CNAME, C_IN, "d", cache_get_name(crecp->addr.cname.cache))) - anscount++; - } + /* We're returning validated data, need to return the RRSIG too. */ + struct crec *rr_crec = NULL; + int sigtype = type; + /* The signature may have expired even though the data is still in cache, + forward instead of answering from cache if so. */ + int gotsig = 0; - strcpy(name, cache_get_name(crecp->addr.cname.cache)); - goto cname_restart; - } - - if (crecp->flags & F_NEG) - { - ans = 1; - auth = 0; - if (crecp->flags & F_NXDOMAIN) - nxdomain = 1; - if (!dryrun) - log_query(crecp->flags, name, NULL, NULL); - } - else if ((crecp->flags & (F_HOSTS | F_DHCP)) || !sec_reqd) - { - /* If we are returning local answers depending on network, - filter here. */ - if (localise && - (crecp->flags & F_HOSTS) && - !is_same_net(*((struct in_addr *)&crecp->addr), local_addr, local_netmask)) - continue; - - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; + if (crecp->flags & F_CNAME) + sigtype = T_CNAME; - ans = 1; - if (!dryrun) + while ((rr_crec = cache_find_by_name(rr_crec, name, now, F_DS | F_DNSKEY))) { - log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr.addr, - record_source(crecp->uid)); - - if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, - crec_ttl(crecp, now), NULL, type, C_IN, - type == T_A ? "4" : "6", &crecp->addr)) - anscount++; + if (rr_crec->addr.sig.type_covered == sigtype && rr_crec->uid == C_IN) + { + char *sigdata = blockdata_retrieve(rr_crec->addr.sig.keydata, rr_crec->addr.sig.keylen, NULL); + gotsig = 1; + + if (!dryrun && + add_resource_record(header, limit, &trunc, nameoffset, &ansp, + rr_crec->ttd - now, &nameoffset, + T_RRSIG, C_IN, "t", rr_crec->addr.sig.keylen, sigdata)) + anscount++; + } } + + if (!gotsig) + crecp = NULL; } - } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); +#endif + } + + if (crecp) + do + { + /* don't answer wildcard queries with data not from /etc/hosts + or DHCP leases */ + if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) + break; + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + if (crecp->flags & F_CNAME) + { + char *cname_target = cache_get_cname_target(crecp); + + if (!dryrun) + { + log_query(crecp->flags, name, NULL, record_source(crecp->uid)); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_CNAME, C_IN, "d", cname_target)) + anscount++; + } + + strcpy(name, cname_target); + /* check if target interface_name */ + if (crecp->addr.cname.uid == SRC_INTERFACE) + goto intname_restart; + else + goto cname_restart; + } + + if (crecp->flags & F_NEG) + { + /* We don't cache NSEC records, so if a DNSSEC-validated negative answer + is cached and the client wants DNSSEC, forward rather than answering from the cache */ + if (!sec_reqd || !(crecp->flags & F_DNSSECOK)) + { + ans = 1; + auth = 0; + if (crecp->flags & F_NXDOMAIN) + nxdomain = 1; + if (!dryrun) + log_query(crecp->flags, name, NULL, NULL); + } + } + else + { + /* If we are returning local answers depending on network, + filter here. */ + if (localise && + (crecp->flags & F_HOSTS) && + !is_same_net(*((struct in_addr *)&crecp->addr), local_addr, local_netmask)) + continue; + + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + + ans = 1; + if (!dryrun) + { + log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr.addr, + record_source(crecp->uid)); + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), NULL, type, C_IN, + type == T_A ? "4" : "6", &crecp->addr)) + anscount++; + } + } + } while ((crecp = cache_find_by_name(crecp, name, now, flag | F_CNAME))); + } + else if (is_name_synthetic(flag, name, &addr)) + { + ans = 1; + if (!dryrun) + { + log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->local_ttl, NULL, type, C_IN, type == T_A ? "4" : "6", &addr)) + anscount++; + } } } - + + if (qtype == T_CNAME || qtype == T_ANY) + { + if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) && + (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | (dryrun ? F_NO_RR : 0))))) + { + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + ans = 1; + if (!dryrun) + { + log_query(crecp->flags, name, NULL, record_source(crecp->uid)); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + crec_ttl(crecp, now), &nameoffset, + T_CNAME, C_IN, "d", cache_get_cname_target(crecp))) + anscount++; + } + } + } + if (qtype == T_MX || qtype == T_ANY) { int found = 0; @@ -1641,7 +2156,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ans = found = 1; if (!dryrun) { - unsigned int offset; + int offset; log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, &offset, T_MX, C_IN, "sd", rec->weight, rec->target)) @@ -1654,7 +2169,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) && - cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP)) + cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP | F_NO_RR)) { ans = 1; if (!dryrun) @@ -1679,7 +2194,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, found = ans = 1; if (!dryrun) { - unsigned int offset; + int offset; log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>"); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, &offset, T_SRV, C_IN, "sssd", @@ -1798,18 +2313,25 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, /* truncation */ if (trunc) header->hb3 |= HB3_TC; - - if (anscount == 0 && nxdomain) + + if (nxdomain) SET_RCODE(header, NXDOMAIN); else SET_RCODE(header, NOERROR); /* no error */ header->ancount = htons(anscount); header->nscount = htons(0); header->arcount = htons(addncount); - return ansp - (unsigned char *)header; -} - - - + len = ansp - (unsigned char *)header; + + if (have_pseudoheader) + len = add_pseudoheader(header, len, (unsigned char *)limit, 0, NULL, 0, sec_reqd); + + if (*ad_reqd && sec_data) + header->hb4 |= HB4_AD; + else + header->hb4 &= ~HB4_AD; + + return len; +} |