-/* $Cambridge: exim/src/src/dns.c,v 1.3 2004/11/24 15:43:36 ph10 Exp $ */
+/* $Cambridge: exim/src/src/dns.c,v 1.8 2005/06/10 13:38:06 tom Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for interfacing with the DNS. */
*************************************************/
/* Call this with reset == RESET_ANSWERS to scan the answer block, reset ==
-RESET_ADDITIONAL to scan the additional records, and reset == RESET_NEXT to
-get the next record. The result is in static storage which must be copied if
-it is to be preserved.
+RESET_AUTHORITY to scan the authority records, reset == RESET_ADDITIONAL to
+scan the additional records, and reset == RESET_NEXT to get the next record.
+The result is in static storage which must be copied if it is to be preserved.
Arguments:
dnsa pointer to dns answer block
dnss->rrcount = ntohs(h->ancount);
- /* Skip over answers and NS records if wanting to look at the additional
+ /* Skip over answers if we want to look at the authority section. Also skip
+ the NS records (i.e. authority section) if wanting to look at the additional
records. */
- if (reset == RESET_ADDITIONAL)
+ if (reset == RESET_ADDITIONAL) dnss->rrcount += ntohs(h->nscount);
+
+ if (reset == RESET_AUTHORITY || reset == RESET_ADDITIONAL)
{
- dnss->rrcount += ntohs(h->nscount);
while (dnss->rrcount-- > 0)
{
namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
GETSHORT(dnss->srr.size, dnss->aptr); /* size of data portion */
dnss->aptr += dnss->srr.size; /* skip over it */
}
- dnss->rrcount = ntohs(h->arcount);
+ dnss->rrcount = (reset == RESET_AUTHORITY)
+ ? ntohs(h->nscount) : ntohs(h->arcount);
}
}
-
/* The variable dnss->aptr is now pointing at the next RR, and dnss->rrcount
contains the number of RR records left. */
case T_PTR: return US"PTR";
case T_SRV: return US"SRV";
case T_NS: return US"NS";
- case T_CNAME: return US"CNAME";
+ case T_CNAME: return US"CNAME";
default: return US"?";
}
}
* Do a DNS lookup and handle virtual types *
************************************************/
-/* This function handles some invented "lookup types" that synthesize feature
-not available in the basic types. The special types all have negative values.
+/* This function handles some invented "lookup types" that synthesize feature
+not available in the basic types. The special types all have negative values.
Positive type values are passed straight on to dns_lookup().
Arguments:
*/
int
-dns_special_lookup(dns_answer *dnsa, uschar *name, int type,
+dns_special_lookup(dns_answer *dnsa, uschar *name, int type,
uschar **fully_qualified_name)
{
if (type >= 0) return dns_lookup(dnsa, name, type, fully_qualified_name);
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
+/* Find nameservers for the domain or the nearest enclosing zone, excluding the
root servers. */
if (type == T_ZNS)
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;
+ 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)
+ {
+ 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 = name;
+ return rc;
}
- return DNS_NOMATCH;
- }
+
+ /* 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);
+ 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;
+ }
/* Control should never reach here */