summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/doc-docbook/spec.xfpt47
-rw-r--r--doc/doc-txt/ChangeLog2
-rw-r--r--src/src/dns.c347
-rw-r--r--src/src/lookups/dnsdb.c149
-rw-r--r--test/dnszones-src/db.test.ex3
-rw-r--r--test/scripts/2200-dnsdb/22003
-rw-r--r--test/src/fakens.c143
-rw-r--r--test/stdout/22003
8 files changed, 389 insertions, 308 deletions
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
&<<SECTforexpfai>>& 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