From: Jeremy Harris Date: Sat, 9 May 2015 18:21:15 +0000 (+0100) Subject: Support SOA lookup in dnsdb lookups. Bug 286 X-Git-Tag: exim-4_86_RC1~43 X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/d2a2c69b7b97d080d63dfb434584d98eb3228332 Support SOA lookup in dnsdb lookups. Bug 286 --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 535e81ee6..80d8aef81 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -6878,16 +6878,9 @@ is used on its own as the result. If the lookup does not succeed, the &`fail`& keyword causes a &'forced expansion failure'& &-- see section &<>& for an explanation of what this means. -The supported DNS record types are A, CNAME, MX, NS, PTR, SPF, SRV, TLSA and TXT, -and, when Exim is compiled with IPv6 support, AAAA. -If no type is given, TXT is assumed. When the type is PTR, -the data can be an IP address, written as normal; inversion and the addition of -&%in-addr.arpa%& or &%ip6.arpa%& happens automatically. For example: -.code -${lookup dnsdb{ptr=192.168.4.5}{$value}fail} -.endd -If the data for a PTR record is not a syntactically valid IP address, it is not -altered and nothing is added. +The supported DNS record types are A, CNAME, MX, NS, PTR, SOA, SPF, SRV, TLSA +and TXT, and, when Exim is compiled with IPv6 support, AAAA. +If no type is given, TXT is assumed. For any record type, if multiple records are found, the data is returned as a concatenation, with newline as the default separator. The order, of course, @@ -6899,21 +6892,33 @@ ${lookup dnsdb{>: a=host1.example}} .endd It is permitted to specify a space as the separator character. Further white space is ignored. +For lookup types that return multiple fields per record, +an alternate field separator can be specified using a comma after the main +separator character, followed immediately by the field separator. + +.cindex "PTR record" "in &(dnsdb)& lookup" +When the type is PTR, +the data can be an IP address, written as normal; inversion and the addition of +&%in-addr.arpa%& or &%ip6.arpa%& happens automatically. For example: +.code +${lookup dnsdb{ptr=192.168.4.5}{$value}fail} +.endd +If the data for a PTR record is not a syntactically valid IP address, it is not +altered and nothing is added. .cindex "MX record" "in &(dnsdb)& lookup" .cindex "SRV record" "in &(dnsdb)& lookup" For an MX lookup, both the preference value and the host name are returned for each record, separated by a space. For an SRV lookup, the priority, weight, port, and host name are returned for each record, separated by spaces. -An alternate field separator can be specified using a comma after the main -separator character, followed immediately by the field separator. +The field separator can be modified as above. .cindex "TXT record" "in &(dnsdb)& lookup" .cindex "SPF record" "in &(dnsdb)& lookup" For TXT records with multiple items of data, only the first item is returned, -unless a separator for them is specified using a comma after the separator -character followed immediately by the TXT record item separator. To concatenate -items without a separator, use a semicolon instead. For SPF records the +unless a field separator is specified. +To concatenate items without a separator, use a semicolon instead. +For SPF records the default behaviour is to concatenate multiple items without using a separator. .code ${lookup dnsdb{>\n,: txt=a.b.example}} @@ -6923,6 +6928,15 @@ ${lookup dnsdb{spf=example.org}} It is permitted to specify a space as the separator character. Further white space is ignored. +.cindex "SOA record" "in &(dnsdb)& lookup" +For an SOA lookup, while no result is obtained the lookup is redone with +successively more leading components dropped from the given domain. +Only the primary-nameserver field is returned unless a field separator is +specified. +.code +${lookup dnsdb{>:,; soa=a.b.example.com}} +.endd + .section "Dnsdb lookup modifiers" "SECTdnsdb_mod" .cindex "dnsdb modifiers" .cindex "modifiers" "dnsdb" @@ -9722,8 +9736,9 @@ the regular expression from string expansion. .vitem &*${sort{*&<&'string'&>&*}{*&<&'comparator'&>&*}{*&<&'extractor'&>&*}}*& -.cindex sorting a list +.cindex sorting "a list" .cindex list sorting +.cindex expansion "list sorting" After expansion, <&'string'&> is interpreted as a list, colon-separated by default, but the separator can be changed in the usual way. The <&'comparator'&> argument is interpreted as the operator diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 89e0ecf50..4e6b9783b 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -94,6 +94,8 @@ HS/02 Bug 1575: exigrep falls back to autodetection of compressed JH/26 Bug 1539: Add timout/retry options on dnsdb lookups. +JH/27 Bug 286: Support SOA lookup in dnsdb lookups. + Exim version 4.85 ----------------- diff --git a/src/src/dns.c b/src/src/dns.c index a9970d9fe..ef734d21a 100644 --- a/src/src/dns.c +++ b/src/src/dns.c @@ -881,173 +881,179 @@ int dns_special_lookup(dns_answer *dnsa, const uschar *name, int type, const uschar **fully_qualified_name) { -if (type >= 0) return dns_lookup(dnsa, name, type, fully_qualified_name); - -/* The "mx hosts only" type doesn't require any special action here */ - -if (type == T_MXH) return dns_lookup(dnsa, name, T_MX, fully_qualified_name); - -/* Find nameservers for the domain or the nearest enclosing zone, excluding the -root servers. */ - -if (type == T_ZNS) - { - const uschar *d = name; - while (d != 0) - { - int rc = dns_lookup(dnsa, d, T_NS, fully_qualified_name); - if (rc != DNS_NOMATCH && rc != DNS_NODATA) return rc; - while (*d != 0 && *d != '.') d++; - if (*d++ == 0) break; - } - return DNS_NOMATCH; - } - -/* Try to look up the Client SMTP Authorization SRV record for the name. If -there isn't one, search from the top downwards for a CSA record in a parent -domain, which might be making assertions about subdomains. If we find a record -we set fully_qualified_name to whichever lookup succeeded, so that the caller -can tell whether to look at the explicit authorization field or the subdomain -assertion field. */ - -if (type == T_CSA) +switch (type) { - uschar *srvname, *namesuff, *tld, *p; - int priority, weight, port; - int limit, rc, i; - BOOL ipv6; - dns_record *rr; - dns_scan dnss; - - DEBUG(D_dns) debug_printf("CSA lookup of %s\n", name); - - srvname = string_sprintf("_client._smtp.%s", name); - rc = dns_lookup(dnsa, srvname, T_SRV, NULL); - if (rc == DNS_SUCCEED || rc == DNS_AGAIN) - { - if (rc == DNS_SUCCEED) *fully_qualified_name = string_copy(name); - return rc; - } - - /* Search for CSA subdomain assertion SRV records from the top downwards, - starting with the 2nd level domain. This order maximizes cache-friendliness. - We skip the top level domains to avoid loading their nameservers and because - we know they'll never have CSA SRV records. */ - - namesuff = Ustrrchr(name, '.'); - if (namesuff == NULL) return DNS_NOMATCH; - tld = namesuff + 1; - ipv6 = FALSE; - limit = dns_csa_search_limit; - - /* Use more appropriate search parameters if we are in the reverse DNS. */ - - if (strcmpic(namesuff, US".arpa") == 0) - { - if (namesuff - 8 > name && strcmpic(namesuff - 8, US".in-addr.arpa") == 0) - { - namesuff -= 8; - tld = namesuff + 1; - limit = 3; - } - else if (namesuff - 4 > name && strcmpic(namesuff - 4, US".ip6.arpa") == 0) - { - namesuff -= 4; - tld = namesuff + 1; - ipv6 = TRUE; - limit = 3; - } - } - - DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld); - - /* Do not perform the search if the top level or 2nd level domains do not - exist. This is quite common, and when it occurs all the search queries would - go to the root or TLD name servers, which is not friendly. So we check the - AUTHORITY section; if it contains the root's SOA record or the TLD's SOA then - the TLD or the 2LD (respectively) doesn't exist and we can skip the search. - If the TLD and the 2LD exist but the explicit CSA record lookup failed, then - the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */ - - if (rc == DNS_NOMATCH) - { - /* This is really gross. The successful return value from res_search() is - the packet length, which is stored in dnsa->answerlen. If we get a - negative DNS reply then res_search() returns -1, which causes the bounds - checks for name decompression to fail when it is treated as a packet - length, which in turn causes the authority search to fail. The correct - packet length has been lost inside libresolv, so we have to guess a - replacement value. (The only way to fix this properly would be to - re-implement res_search() and res_query() so that they don't muddle their - success and packet length return values.) For added safety we only reset - the packet length if the packet header looks plausible. */ - - HEADER *h = (HEADER *)dnsa->answer; - if (h->qr == 1 && h->opcode == QUERY && h->tc == 0 - && (h->rcode == NOERROR || h->rcode == NXDOMAIN) - && ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0 - && ntohs(h->nscount) >= 1) - dnsa->answerlen = MAXPACKET; - - for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); - rr != NULL; - rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) - if (rr->type != T_SOA) continue; - else if (strcmpic(rr->name, US"") == 0 || - strcmpic(rr->name, tld) == 0) return DNS_NOMATCH; - else break; - } - - for (i = 0; i < limit; i++) - { - if (ipv6) - { - /* Scan through the IPv6 reverse DNS in chunks of 16 bits worth of IP - address, i.e. 4 hex chars and 4 dots, i.e. 8 chars. */ - namesuff -= 8; - if (namesuff <= name) return DNS_NOMATCH; - } - else - /* Find the start of the preceding domain name label. */ - do - if (--namesuff <= name) return DNS_NOMATCH; - while (*namesuff != '.'); - - DEBUG(D_dns) debug_printf("CSA parent search at %s\n", namesuff + 1); - - srvname = string_sprintf("_client._smtp.%s", namesuff + 1); - rc = dns_lookup(dnsa, srvname, T_SRV, NULL); - if (rc == DNS_AGAIN) return rc; - if (rc != DNS_SUCCEED) continue; - - /* Check that the SRV record we have found is worth returning. We don't - just return the first one we find, because some lower level SRV record - might make stricter assertions than its parent domain. */ + /* The "mx hosts only" type doesn't require any special action here */ + case T_MXH: + return dns_lookup(dnsa, name, T_MX, fully_qualified_name); + + /* Find nameservers for the domain or the nearest enclosing zone, excluding + the root servers. */ + case T_ZNS: + type = T_NS; + /* FALLTHROUGH */ + case T_SOA: + { + const uschar *d = name; + while (d != 0) + { + int rc = dns_lookup(dnsa, d, type, fully_qualified_name); + if (rc != DNS_NOMATCH && rc != DNS_NODATA) return rc; + while (*d != 0 && *d != '.') d++; + if (*d++ == 0) break; + } + return DNS_NOMATCH; + } + + /* Try to look up the Client SMTP Authorization SRV record for the name. If + there isn't one, search from the top downwards for a CSA record in a parent + domain, which might be making assertions about subdomains. If we find a record + we set fully_qualified_name to whichever lookup succeeded, so that the caller + can tell whether to look at the explicit authorization field or the subdomain + assertion field. */ + case T_CSA: + { + uschar *srvname, *namesuff, *tld, *p; + int priority, weight, port; + int limit, rc, i; + BOOL ipv6; + dns_record *rr; + dns_scan dnss; + + DEBUG(D_dns) debug_printf("CSA lookup of %s\n", name); + + srvname = string_sprintf("_client._smtp.%s", name); + rc = dns_lookup(dnsa, srvname, T_SRV, NULL); + if (rc == DNS_SUCCEED || rc == DNS_AGAIN) + { + if (rc == DNS_SUCCEED) *fully_qualified_name = string_copy(name); + return rc; + } + + /* Search for CSA subdomain assertion SRV records from the top downwards, + starting with the 2nd level domain. This order maximizes cache-friendliness. + We skip the top level domains to avoid loading their nameservers and because + we know they'll never have CSA SRV records. */ + + namesuff = Ustrrchr(name, '.'); + if (namesuff == NULL) return DNS_NOMATCH; + tld = namesuff + 1; + ipv6 = FALSE; + limit = dns_csa_search_limit; + + /* Use more appropriate search parameters if we are in the reverse DNS. */ + + if (strcmpic(namesuff, US".arpa") == 0) + { + if (namesuff - 8 > name && strcmpic(namesuff - 8, US".in-addr.arpa") == 0) + { + namesuff -= 8; + tld = namesuff + 1; + limit = 3; + } + else if (namesuff - 4 > name && strcmpic(namesuff - 4, US".ip6.arpa") == 0) + { + namesuff -= 4; + tld = namesuff + 1; + ipv6 = TRUE; + limit = 3; + } + } + + DEBUG(D_dns) debug_printf("CSA TLD %s\n", tld); + + /* Do not perform the search if the top level or 2nd level domains do not + exist. This is quite common, and when it occurs all the search queries would + go to the root or TLD name servers, which is not friendly. So we check the + AUTHORITY section; if it contains the root's SOA record or the TLD's SOA then + the TLD or the 2LD (respectively) doesn't exist and we can skip the search. + If the TLD and the 2LD exist but the explicit CSA record lookup failed, then + the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */ + + if (rc == DNS_NOMATCH) + { + /* This is really gross. The successful return value from res_search() is + the packet length, which is stored in dnsa->answerlen. If we get a + negative DNS reply then res_search() returns -1, which causes the bounds + checks for name decompression to fail when it is treated as a packet + length, which in turn causes the authority search to fail. The correct + packet length has been lost inside libresolv, so we have to guess a + replacement value. (The only way to fix this properly would be to + re-implement res_search() and res_query() so that they don't muddle their + success and packet length return values.) For added safety we only reset + the packet length if the packet header looks plausible. */ + + HEADER *h = (HEADER *)dnsa->answer; + if (h->qr == 1 && h->opcode == QUERY && h->tc == 0 + && (h->rcode == NOERROR || h->rcode == NXDOMAIN) + && ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0 + && ntohs(h->nscount) >= 1) + dnsa->answerlen = MAXPACKET; + + for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY); + rr != NULL; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) + if (rr->type != T_SOA) continue; + else if (strcmpic(rr->name, US"") == 0 || + strcmpic(rr->name, tld) == 0) return DNS_NOMATCH; + else break; + } + + for (i = 0; i < limit; i++) + { + if (ipv6) + { + /* Scan through the IPv6 reverse DNS in chunks of 16 bits worth of IP + address, i.e. 4 hex chars and 4 dots, i.e. 8 chars. */ + namesuff -= 8; + if (namesuff <= name) return DNS_NOMATCH; + } + else + /* Find the start of the preceding domain name label. */ + do + if (--namesuff <= name) return DNS_NOMATCH; + while (*namesuff != '.'); + + DEBUG(D_dns) debug_printf("CSA parent search at %s\n", namesuff + 1); + + srvname = string_sprintf("_client._smtp.%s", namesuff + 1); + rc = dns_lookup(dnsa, srvname, T_SRV, NULL); + if (rc == DNS_AGAIN) return rc; + if (rc != DNS_SUCCEED) continue; + + /* Check that the SRV record we have found is worth returning. We don't + just return the first one we find, because some lower level SRV record + might make stricter assertions than its parent domain. */ + + for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + rr != NULL; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) + { + if (rr->type != T_SRV) continue; + + /* Extract the numerical SRV fields (p is incremented) */ + p = rr->data; + GETSHORT(priority, p); + GETSHORT(weight, p); weight = weight; /* compiler quietening */ + GETSHORT(port, p); + + /* Check the CSA version number */ + if (priority != 1) continue; + + /* If it's making an interesting assertion, return this response. */ + if (port & 1) + { + *fully_qualified_name = namesuff + 1; + return DNS_SUCCEED; + } + } + } + return DNS_NOMATCH; + } - for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); - rr != NULL; - rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) - { - if (rr->type != T_SRV) continue; - - /* Extract the numerical SRV fields (p is incremented) */ - p = rr->data; - GETSHORT(priority, p); - GETSHORT(weight, p); weight = weight; /* compiler quietening */ - GETSHORT(port, p); - - /* Check the CSA version number */ - if (priority != 1) continue; - - /* If it's making an interesting assertion, return this response. */ - if (port & 1) - { - *fully_qualified_name = namesuff + 1; - return DNS_SUCCEED; - } - } - } - return DNS_NOMATCH; + default: + if (type >= 0) + return dns_lookup(dnsa, name, type, fully_qualified_name); } /* Control should never reach here */ @@ -1064,16 +1070,13 @@ return DNS_FAIL; *************************************************/ /* The record type is either T_A for an IPv4 address or T_AAAA (or T_A6 when -supported) for an IPv6 address. In the A6 case, there may be several addresses, -generated by following chains. A recursive function does all the hard work. A6 -records now look like passing into history, so the code is only included when -explicitly asked for. +supported) for an IPv6 address. Argument: dnsa the DNS answer block rr the RR -Returns: pointer a chain of dns_address items +Returns: pointer to a chain of dns_address items */ dns_address * @@ -1085,7 +1088,7 @@ dnsa = dnsa; /* Stop picky compilers warning */ if (rr->type == T_A) { - uschar *p = (uschar *)(rr->data); + uschar *p = US rr->data; yield = store_get(sizeof(dns_address) + 20); (void)sprintf(CS yield->address, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); yield->next = NULL; @@ -1096,7 +1099,7 @@ if (rr->type == T_A) else { yield = store_get(sizeof(dns_address) + 50); - inet_ntop(AF_INET6, (uschar *)(rr->data), CS yield->address, 50); + inet_ntop(AF_INET6, US rr->data, CS yield->address, 50); yield->next = NULL; } #endif /* HAVE_IPV6 */ diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c index a0a457ab2..d06455e61 100644 --- a/src/src/lookups/dnsdb.c +++ b/src/src/lookups/dnsdb.c @@ -41,6 +41,7 @@ static const char *type_names[] = { "mxh", "ns", "ptr", + "soa", "spf", "srv", "tlsa", @@ -60,6 +61,7 @@ static int type_values[] = { T_MXH, /* Private type for "MX hostnames" */ T_NS, T_PTR, + T_SOA, T_SPF, T_SRV, T_TLSA, @@ -386,10 +388,6 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) { if (rr->type != searchtype) continue; - /* There may be several addresses from an A6 record. Put the configured - separator between them, just as for between several records. However, A6 - support is not normally configured these days. */ - if (type == T_A || type == T_AAAA || type == T_ADDRESSES) { dns_address *da; @@ -436,7 +434,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) uint16_t i, payload_length; uschar s[MAX_TLSA_EXPANDED_SIZE]; uschar * sp = s; - uschar *p = (uschar *)(rr->data); + uschar * p = US rr->data; usage = *p++; selector = *p++; @@ -449,72 +447,75 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) for (i=0; i < payload_length && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4); i++) - { sp += sprintf(CS sp, "%02x", (unsigned char)p[i]); - } + yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); } - else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SRV */ + else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */ { int priority, weight, port; uschar s[264]; - uschar *p = (uschar *)(rr->data); - - if (type == T_MXH) - { - /* mxh ignores the priority number and includes only the hostnames */ - GETSHORT(priority, p); - } - else if (type == T_MX) - { - GETSHORT(priority, p); - sprintf(CS s, "%d%c", priority, *outsep2); - yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); - } - else if (type == T_SRV) - { - GETSHORT(priority, p); - GETSHORT(weight, p); - GETSHORT(port, p); - sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2, - weight, *outsep2, port, *outsep2); - yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); - } - else if (type == T_CSA) - { - /* See acl_verify_csa() for more comments about CSA. */ - - GETSHORT(priority, p); - GETSHORT(weight, p); - GETSHORT(port, p); - - if (priority != 1) continue; /* CSA version must be 1 */ - - /* If the CSA record we found is not the one we asked for, analyse - the subdomain assertions in the port field, else analyse the direct - authorization status in the weight field. */ - - if (Ustrcmp(found, domain) != 0) - { - if (port & 1) *s = 'X'; /* explicit authorization required */ - else *s = '?'; /* no subdomain assertions here */ - } - else - { - if (weight < 2) *s = 'N'; /* not authorized */ - else if (weight == 2) *s = 'Y'; /* authorized */ - else if (weight == 3) *s = '?'; /* unauthorizable */ - else continue; /* invalid */ - } - - s[1] = ' '; - yield = string_cat(yield, &size, &ptr, s, 2); - } + uschar * p = US rr->data; + + switch (type) + { + case T_MXH: + /* mxh ignores the priority number and includes only the hostnames */ + GETSHORT(priority, p); + break; + + case T_MX: + GETSHORT(priority, p); + sprintf(CS s, "%d%c", priority, *outsep2); + yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); + break; + + case T_SRV: + GETSHORT(priority, p); + GETSHORT(weight, p); + GETSHORT(port, p); + sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2, + weight, *outsep2, port, *outsep2); + yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); + break; + + case T_CSA: + /* See acl_verify_csa() for more comments about CSA. */ + GETSHORT(priority, p); + GETSHORT(weight, p); + GETSHORT(port, p); + + if (priority != 1) continue; /* CSA version must be 1 */ + + /* If the CSA record we found is not the one we asked for, analyse + the subdomain assertions in the port field, else analyse the direct + authorization status in the weight field. */ + + if (Ustrcmp(found, domain) != 0) + { + if (port & 1) *s = 'X'; /* explicit authorization required */ + else *s = '?'; /* no subdomain assertions here */ + } + else + { + if (weight < 2) *s = 'N'; /* not authorized */ + else if (weight == 2) *s = 'Y'; /* authorized */ + else if (weight == 3) *s = '?'; /* unauthorizable */ + else continue; /* invalid */ + } + + s[1] = ' '; + yield = string_cat(yield, &size, &ptr, s, 2); + break; + + default: + break; + } /* GETSHORT() has advanced the pointer to the target domain. */ rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p, - (DN_EXPAND_ARG4_TYPE)(s), sizeof(s)); + (DN_EXPAND_ARG4_TYPE)s, sizeof(s)); /* If an overlong response was received, the data will have been truncated and dn_expand may fail. */ @@ -526,6 +527,32 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) break; } else yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); + + if (type == T_SOA && outsep2 != NULL) + { + unsigned long serial, refresh, retry, expire, minimum; + + p += rc; + yield = string_cat(yield, &size, &ptr, outsep2, 1); + + rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p, + (DN_EXPAND_ARG4_TYPE)s, sizeof(s)); + if (rc < 0) + { + log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s " + "domain=%s", dns_text_type(type), domain); + break; + } + else yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); + + p += rc; + GETLONG(serial, p); GETLONG(refresh, p); + GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); + sprintf(CS s, "%c%d%c%d%c%d%c%d%c%d", + *outsep2, serial, *outsep2, refresh, + *outsep2, retry, *outsep2, expire, *outsep2, minimum); + yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); + } } } /* Loop for list of returned records */ diff --git a/test/dnszones-src/db.test.ex b/test/dnszones-src/db.test.ex index 1339981c8..c9b004c76 100644 --- a/test/dnszones-src/db.test.ex +++ b/test/dnszones-src/db.test.ex @@ -1,5 +1,5 @@ ; This is a testing zone file for use when testing DNS handling in Exim. This -; is a fake zone of no real use - hence no SOA record. The zone name is +; is a fake zone of no real use. The zone name is ; test.ex. This file is passed through the substitution mechanism before being ; used by the fakens auxiliary program. This inserts the actual IP addresses ; of the local host into the zone. @@ -17,6 +17,7 @@ ; host ever uses them. test.ex. NS exim.test.ex. +test.ex. SOA exim.test.ex. hostmaster.exim.test.ex 1430683638 1200 120 604800 3600 test.ex. TXT "A TXT record for test.ex." s/lash TXT "A TXT record for s/lash.test.ex." diff --git a/test/scripts/2200-dnsdb/2200 b/test/scripts/2200-dnsdb/2200 index 837c85a1c..40837dbd0 100644 --- a/test/scripts/2200-dnsdb/2200 +++ b/test/scripts/2200-dnsdb/2200 @@ -21,6 +21,9 @@ srv=_smtp._tcp.nosmtp.test.ex ${lookup dnsdb{srv=_smtp._tcp.nosmtp.test.ex}{$ csa=csa1.test.ex ${lookup dnsdb{csa=csa1.test.ex}} csa=csa2.test.ex ${lookup dnsdb{csa=csa2.test.ex}} +soa=test.ex ${lookup dnsdb{soa=test.ex}{$value}{fail}} +soa=a.test.ex ${lookup dnsdb{>:, soa=test.ex}{$value}{fail}} + # DNS lookups with multiple items ten-1:ten2 ${lookup dnsdb{a=ten-1.test.ex:ten-2.test.ex}} diff --git a/test/src/fakens.c b/test/src/fakens.c index 31b6471fa..fc7848e77 100644 --- a/test/src/fakens.c +++ b/test/src/fakens.c @@ -130,7 +130,7 @@ static tlist type_list[] = { { US"A", ns_t_a }, { US"NS", ns_t_ns }, { US"CNAME", ns_t_cname }, -/* { US"SOA", ns_t_soa }, Not currently in use */ + { US"SOA", ns_t_soa }, { US"PTR", ns_t_ptr }, { US"MX", ns_t_mx }, { US"TXT", ns_t_txt }, @@ -227,6 +227,22 @@ while (isspace(*p)) p++; return pk; } +uschar * +longfield(uschar ** pp, uschar * pk) +{ +unsigned long value = 0; +uschar * p = *pp; + +while (isdigit(*p)) value = value*10 + *p++ - '0'; +while (isspace(*p)) p++; +*pp = p; +*pk++ = (value >> 24) & 255; +*pk++ = (value >> 16) & 255; +*pk++ = (value >> 8) & 255; +*pk++ = value & 255; +return pk; +} + /*************************************************/ @@ -316,7 +332,7 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) uschar *rdlptr; uschar *p, *ep, *pp; BOOL found_cname = FALSE; - int i, plen, value; + int i, value; int tvalue = typeptr->value; int qtlen = qtypelen; BOOL rr_sec = FALSE; @@ -436,60 +452,72 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) switch (tvalue) { - case ns_t_soa: /* Not currently used */ - break; + case ns_t_soa: + p = strtok(p, " "); + ep = p + strlen(p); + if (ep[-1] != '.') sprintf(CS ep, "%s.", zone); + pk = packname(p, pk); /* primary ns */ + p = strtok(NULL, " "); + pk = packname(p , pk); /* responsible mailbox */ + *(p += strlen(p)) = ' '; + while (isspace(*p)) p++; + pk = longfield(&p, pk); /* serial */ + pk = longfield(&p, pk); /* refresh */ + pk = longfield(&p, pk); /* retry */ + pk = longfield(&p, pk); /* expire */ + pk = longfield(&p, pk); /* minimum */ + break; case ns_t_a: - for (i = 0; i < 4; i++) - { - value = 0; - while (isdigit(*p)) value = value*10 + *p++ - '0'; - *pk++ = value; - p++; - } - break; + for (i = 0; i < 4; i++) + { + value = 0; + while (isdigit(*p)) value = value*10 + *p++ - '0'; + *pk++ = value; + p++; + } + break; /* The only occurrence of a double colon is for ::1 */ case ns_t_aaaa: - if (Ustrcmp(p, "::1") == 0) - { - memset(pk, 0, 15); - pk += 15; - *pk++ = 1; - } - else for (i = 0; i < 8; i++) - { - value = 0; - while (isxdigit(*p)) - { - value = value * 16 + toupper(*p) - (isdigit(*p)? '0' : '7'); - p++; - } - *pk++ = (value >> 8) & 255; - *pk++ = value & 255; - p++; - } - break; + if (Ustrcmp(p, "::1") == 0) + { + memset(pk, 0, 15); + pk += 15; + *pk++ = 1; + } + else for (i = 0; i < 8; i++) + { + value = 0; + while (isxdigit(*p)) + { + value = value * 16 + toupper(*p) - (isdigit(*p)? '0' : '7'); + p++; + } + *pk++ = (value >> 8) & 255; + *pk++ = value & 255; + p++; + } + break; case ns_t_mx: - pk = shortfield(&p, pk); - if (ep[-1] != '.') sprintf(CS ep, "%s.", zone); - pk = packname(p, pk); - plen = Ustrlen(p); - break; + pk = shortfield(&p, pk); + if (ep[-1] != '.') sprintf(CS ep, "%s.", zone); + pk = packname(p, pk); + break; case ns_t_txt: - pp = pk++; - if (*p == '"') p++; /* Should always be the case */ - while (*p != 0 && *p != '"') *pk++ = *p++; - *pp = pk - pp - 1; - break; + pp = pk++; + if (*p == '"') p++; /* Should always be the case */ + while (*p != 0 && *p != '"') *pk++ = *p++; + *pp = pk - pp - 1; + break; case ns_t_tlsa: - pk = bytefield(&p, pk); /* usage */ - pk = bytefield(&p, pk); /* selector */ - pk = bytefield(&p, pk); /* match type */ - while (isxdigit(*p)) + pk = bytefield(&p, pk); /* usage */ + pk = bytefield(&p, pk); /* selector */ + pk = bytefield(&p, pk); /* match type */ + while (isxdigit(*p)) { value = toupper(*p) - (isdigit(*p) ? '0' : '7') << 4; if (isxdigit(*++p)) @@ -500,27 +528,26 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) *pk++ = value & 255; } - break; + break; case ns_t_srv: - for (i = 0; i < 3; i++) - { - value = 0; - while (isdigit(*p)) value = value*10 + *p++ - '0'; - while (isspace(*p)) p++; - *pk++ = (value >> 8) & 255; - *pk++ = value & 255; - } + for (i = 0; i < 3; i++) + { + value = 0; + while (isdigit(*p)) value = value*10 + *p++ - '0'; + while (isspace(*p)) p++; + *pk++ = (value >> 8) & 255; + *pk++ = value & 255; + } /* Fall through */ case ns_t_cname: case ns_t_ns: case ns_t_ptr: - if (ep[-1] != '.') sprintf(CS ep, "%s.", zone); - pk = packname(p, pk); - plen = Ustrlen(p); - break; + if (ep[-1] != '.') sprintf(CS ep, "%s.", zone); + pk = packname(p, pk); + break; } /* Fill in the length, and we are done with this RR */ diff --git a/test/stdout/2200 b/test/stdout/2200 index 3d7d0db88..b77594803 100644 --- a/test/stdout/2200 +++ b/test/stdout/2200 @@ -19,6 +19,9 @@ > csa=csa1.test.ex Y csa1.test.ex > csa=csa2.test.ex N csa2.test.ex > +> soa=test.ex exim.test.ex +> soa=a.test.ex exim.test.ex hostmaster.exim.test.ex 1430683638 1200 120 604800 3600 +> > # DNS lookups with multiple items > > ten-1:ten2 V4NET.0.0.1