From: Philip Hazel Date: Tue, 10 May 2005 10:19:11 +0000 (+0000) Subject: 1. Update version. X-Git-Tag: exim-4_52~103 X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=commitdiff_plain;h=e5a9dba621b4301bfbe2bc05576ddc5ec752b1b5 1. Update version. 2. Apply Tony's CSA patch (with some fixes) and add simple test. --- diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index dbd12e564..18113c2f1 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -1,9 +1,15 @@ -$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.133 2005/05/04 10:17:28 ph10 Exp $ +$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.134 2005/05/10 10:19:11 ph10 Exp $ Change log file for Exim from version 4.21 ------------------------------------------- +Exim version 4.52 +----------------- + +TF/01 Added support for Client SMTP Authorization. See NewStuff for details. + + Exim version 4.51 ----------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index f0abee45a..98c6fe258 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -1,4 +1,4 @@ -$Cambridge: exim/doc/doc-txt/NewStuff,v 1.39 2005/05/03 14:20:00 ph10 Exp $ +$Cambridge: exim/doc/doc-txt/NewStuff,v 1.40 2005/05/10 10:19:11 ph10 Exp $ New Features in Exim -------------------- @@ -9,6 +9,66 @@ updated when there is a relatively large batch of changes). The doc/ChangeLog file contains a listing of all changes, including bug fixes. +Exim version 4.52 +----------------- + +TF/01 Support for checking Client SMTP Authorization has been added. CSA is a + system which allows a site to advertise which machines are and are not + permitted to send email. This is done by placing special SRV records in + the DNS, which are looked up using the client's HELO domain. At this + time CSA is still an Internet-Draft. + + Client SMTP Authorization checks are performed by the ACL condition + verify=csa. This will fail if the client is not authorized. If there is + a DNS problem, or if no valid CSA SRV record is found, or if the client + is authorized, the condition succeeds. These three cases can be + distinguished using the expansion variable $csa_status, which can take + one of the values "fail", "defer", "unknown", or "ok". The condition + does not itself defer because that would be likely to cause problems + for legitimate email. + + The error messages produced by the CSA code include slightly more + detail. If $csa_status is "defer" this may be because of problems + looking up the CSA SRV record, or problems looking up the CSA target + address record. There are four reasons for $csa_status being "fail": + the client's host name is explicitly not authorized; the client's IP + address does not match any of the CSA target IP addresses; the client's + host name is authorized but it has no valid target IP addresses (e.g. + the target's addresses are IPv6 and the client is using IPv4); or the + client's host name has no CSA SRV record but a parent domain has + asserted that all subdomains must be explicitly authorized. + + The verify=csa condition can take an argument which is the domain to + use for the DNS query. The default is verify=csa/$sender_helo_name. + + This implementation includes an extension to CSA. If the query domain + is an address literal such as [192.0.2.95], or if it is a bare IP + address, Exim will search for CSA SRV records in the reverse DNS as if + the HELO domain was e.g. 95.2.0.192.in-addr.arpa. Therefore it is + meaningful to say, for example, verify=csa/$sender_host_address - in + fact, this is the check that Exim performs if the client does not say + HELO. This extension can be turned off by setting the main + configuration option dns_csa_use_reverse = false. + + If a CSA SRV record is not found for the domain itself, then a search + is performed through its parent domains for a record which might be + making assertions about subdomains. The maximum depth of this search is + limited using the main configuration option dns_csa_search_limit, which + takes the value 5 by default. Exim does not look for CSA SRV records in + a top level domain, so the default settings handle HELO domains as long + as seven (hostname.five.four.three.two.one.com) which encompasses the + vast majority of legitimate HELO domains. + + The dnsdb lookup also has support for CSA. Although dnsdb already + supports SRV lookups, this is not sufficient because of the extra + parent domain search behaviour of CSA, and (as with PTR lookups) + dnsdb also turns IP addresses into lookups in the reverse DNS space. + The result of ${lookup dnsdb {csa=$sender_helo_name} } has two + space-separated fields: an authorization code and a target host name. + The authorization code can be "Y" for yes, "N" for no, "X" for explicit + authorization required but absent, or "?" for unknown. + + Version 4.51 ------------ diff --git a/src/src/acl.c b/src/src/acl.c index c0a5e944f..b91d9d041 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/acl.c,v 1.28 2005/04/06 14:03:53 ph10 Exp $ */ +/* $Cambridge: exim/src/src/acl.c,v 1.29 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -505,6 +505,45 @@ static control_def controls_list[] = { { US"submission", CONTROL_SUBMISSION, TRUE} }; +/* Support data structures for Client SMTP Authorization. acl_verify_csa() +caches its result in a tree to avoid repeated DNS queries. The result is an +integer code which is used as an index into the following tables of +explanatory strings and verification return codes. */ + +static tree_node *csa_cache = NULL; + +enum { CSA_UNKNOWN, CSA_OK, CSA_DEFER_SRV, CSA_DEFER_ADDR, + CSA_FAIL_EXPLICIT, CSA_FAIL_DOMAIN, CSA_FAIL_NOADDR, CSA_FAIL_MISMATCH }; + +/* The acl_verify_csa() return code is translated into an acl_verify() return +code using the following table. It is OK unless the client is definitely not +authorized. This is because CSA is supposed to be optional for sending sites, +so recipients should not be too strict about checking it - especially because +DNS problems are quite likely to occur. It's possible to use $csa_status in +further ACL conditions to distinguish ok, unknown, and defer if required, but +the aim is to make the usual configuration simple. */ + +static int csa_return_code[] = { + OK, OK, OK, OK, + FAIL, FAIL, FAIL, FAIL +}; + +static uschar *csa_status_string[] = { + US"unknown", US"ok", US"defer", US"defer", + US"fail", US"fail", US"fail", US"fail" +}; + +static uschar *csa_reason_string[] = { + US"unknown", + US"ok", + US"deferred (SRV lookup failed)", + US"deferred (target address lookup failed)", + US"failed (explicit authorization required)", + US"failed (host name not authorized)", + US"failed (no authorized addresses)", + US"failed (client address mismatch)" +}; + /* Enable recursion between acl_check_internal() and acl_check_condition() */ static int acl_check_internal(int, address_item *, uschar *, int, uschar **, @@ -937,6 +976,303 @@ return OK; +/************************************************* +* Check client IP address matches CSA target * +*************************************************/ + +/* Called from acl_verify_csa() below. This routine scans a section of a DNS +response for address records belonging to the CSA target hostname. The section +is specified by the reset argument, either RESET_ADDITIONAL or RESET_ANSWERS. +If one of the addresses matches the client's IP address, then the client is +authorized by CSA. If there are target IP addresses but none of them match +then the client is using an unauthorized IP address. If there are no target IP +addresses then the client cannot be using an authorized IP address. (This is +an odd configuration - why didn't the SRV record have a weight of 1 instead?) + +Arguments: + dnsa the DNS answer block + dnss a DNS scan block for us to use + reset option specifing what portion to scan, as described above + target the target hostname to use for matching RR names + +Returns: CSA_OK successfully authorized + CSA_FAIL_MISMATCH addresses found but none matched + CSA_FAIL_NOADDR no target addresses found +*/ + +static int +acl_verify_csa_address(dns_answer *dnsa, dns_scan *dnss, int reset, + uschar *target) +{ +dns_record *rr; +dns_address *da; + +BOOL target_found = FALSE; + +for (rr = dns_next_rr(dnsa, dnss, reset); + rr != NULL; + rr = dns_next_rr(dnsa, dnss, RESET_NEXT)) + { + /* Check this is an address RR for the target hostname. */ + + if (rr->type != T_A + #if HAVE_IPV6 + && rr->type != T_AAAA + #ifdef SUPPORT_A6 + && rr->type != T_A6 + #endif + #endif + ) continue; + + if (strcmpic(target, rr->name) != 0) continue; + + target_found = TRUE; + + /* Turn the target address RR into a list of textual IP addresses and scan + the list. There may be more than one if it is an A6 RR. */ + + for (da = dns_address_from_rr(dnsa, rr); da != NULL; da = da->next) + { + /* If the client IP address matches the target IP address, it's good! */ + + DEBUG(D_acl) debug_printf("CSA target address is %s\n", da->address); + + if (strcmpic(sender_host_address, da->address) == 0) return CSA_OK; + } + } + +/* If we found some target addresses but none of them matched, the client is +using an unauthorized IP address, otherwise the target has no authorized IP +addresses. */ + +if (target_found) return CSA_FAIL_MISMATCH; +else return CSA_FAIL_NOADDR; +} + + + +/************************************************* +* Verify Client SMTP Authorization * +*************************************************/ + +/* Called from acl_verify() below. This routine calls dns_lookup_special() +to find the CSA SRV record corresponding to the domain argument, or +$sender_helo_name if no argument is provided. It then checks that the +client is authorized, and that its IP address corresponds to the SRV +target's address by calling acl_verify_csa_address() above. The address +should have been returned in the DNS response's ADDITIONAL section, but if +not we perform another DNS lookup to get it. + +Arguments: + domain pointer to optional parameter following verify = csa + +Returns: CSA_UNKNOWN no valid CSA record found + CSA_OK successfully authorized + CSA_FAIL_* client is definitely not authorized + CSA_DEFER_* there was a DNS problem +*/ + +static int +acl_verify_csa(uschar *domain) +{ +tree_node *t; +uschar *found, *p; +int priority, weight, port; +dns_answer dnsa; +dns_scan dnss; +dns_record *rr; +int rc, type; +uschar target[256]; + +/* Work out the domain we are using for the CSA lookup. The default is the +client's HELO domain. If the client has not said HELO, use its IP address +instead. If it's a local client (exim -bs), CSA isn't applicable. */ + +while (isspace(*domain) && *domain != '\0') ++domain; +if (*domain == '\0') domain = sender_helo_name; +if (domain == NULL) domain = sender_host_address; +if (sender_host_address == NULL) return CSA_UNKNOWN; + +/* If we have an address literal, strip off the framing ready for turning it +into a domain. The framing consists of matched square brackets possibly +containing a keyword and a colon before the actual IP address. */ + +if (domain[0] == '[') + { + uschar *start = Ustrchr(domain, ':'); + if (start == NULL) start = domain; + domain = string_copyn(start + 1, Ustrlen(start) - 2); + } + +/* Turn domains that look like bare IP addresses into domains in the reverse +DNS. This code also deals with address literals and $sender_host_address. It's +not quite kosher to treat bare domains such as EHLO 192.0.2.57 the same as +address literals, but it's probably the most friendly thing to do. This is an +extension to CSA, so we allow it to be turned off for proper conformance. */ + +if (string_is_ip_address(domain, NULL)) + { + if (!dns_csa_use_reverse) return CSA_UNKNOWN; + dns_build_reverse(domain, target); + domain = target; + } + +/* Find out if we've already done the CSA check for this domain. If we have, +return the same result again. Otherwise build a new cached result structure +for this domain. The name is filled in now, and the value is filled in when +we return from this function. */ + +t = tree_search(csa_cache, domain); +if (t != NULL) return t->data.val; + +t = store_get_perm(sizeof(tree_node) + Ustrlen(domain)); +Ustrcpy(t->name, domain); +(void)tree_insertnode(&csa_cache, t); + +/* Now we are ready to do the actual DNS lookup(s). */ + +switch (dns_special_lookup(&dnsa, domain, T_CSA, &found)) + { + /* If something bad happened (most commonly DNS_AGAIN), defer. */ + + default: + return t->data.val = CSA_DEFER_SRV; + + /* If we found nothing, the client's authorization is unknown. */ + + case DNS_NOMATCH: + case DNS_NODATA: + return t->data.val = CSA_UNKNOWN; + + /* We got something! Go on to look at the reply in more detail. */ + + case DNS_SUCCEED: + break; + } + +/* Scan the reply for well-formed CSA SRV records. */ + +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); + + DEBUG(D_acl) + debug_printf("CSA priority=%d weight=%d port=%d\n", priority, weight, port); + + /* Check the CSA version number */ + + if (priority != 1) continue; + + /* If the domain does not have a CSA SRV record of its own (i.e. the domain + found by dns_special_lookup() is a parent of the one we asked for), we check + the subdomain assertions in the port field. At the moment there's only one + assertion: legitimate SMTP clients are all explicitly authorized with CSA + SRV records of their own. */ + + if (found != domain) + { + if (port & 1) + return t->data.val = CSA_FAIL_EXPLICIT; + else + return t->data.val = CSA_UNKNOWN; + } + + /* This CSA SRV record refers directly to our domain, so we check the value + in the weight field to work out the domain's authorization. 0 and 1 are + unauthorized; 3 means the client is authorized but we can't check the IP + address in order to authenticate it, so we treat it as unknown; values + greater than 3 are undefined. */ + + if (weight < 2) return t->data.val = CSA_FAIL_DOMAIN; + + if (weight > 2) continue; + + /* Weight == 2, which means the domain is authorized. We must check that the + client's IP address is listed as one of the SRV target addresses. Save the + target hostname then break to scan the additional data for its addresses. */ + + (void)dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p, + (DN_EXPAND_ARG4_TYPE)target, sizeof(target)); + + DEBUG(D_acl) debug_printf("CSA target is %s\n", target); + + break; + } + +/* If we didn't break the loop then no appropriate records were found. */ + +if (rr == NULL) return t->data.val = CSA_UNKNOWN; + +/* Do not check addresses if the target is ".", in accordance with RFC 2782. +A target of "." indicates there are no valid addresses, so the client cannot +be authorized. (This is an odd configuration because weight=2 target=. is +equivalent to weight=1, but we check for it in order to keep load off the +root name servers.) Note that dn_expand() turns "." into "". */ + +if (Ustrcmp(target, "") == 0) return t->data.val = CSA_FAIL_NOADDR; + +/* Scan the additional section of the CSA SRV reply for addresses belonging +to the target. If the name server didn't return any additional data (e.g. +because it does not fully support SRV records), we need to do another lookup +to obtain the target addresses; otherwise we have a definitive result. */ + +rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ADDITIONAL, target); +if (rc != CSA_FAIL_NOADDR) return t->data.val = rc; + +/* The DNS lookup type corresponds to the IP version used by the client. */ + +#if HAVE_IPV6 +if (Ustrchr(sender_host_address, ':') != NULL) + type = T_AAAA; +else +#endif /* HAVE_IPV6 */ + type = T_A; + + +#if HAVE_IPV6 && defined(SUPPORT_A6) +DNS_LOOKUP_AGAIN: +#endif + +switch (dns_lookup(&dnsa, target, type, NULL)) + { + /* If something bad happened (most commonly DNS_AGAIN), defer. */ + + default: + return t->data.val = CSA_DEFER_ADDR; + + /* If the query succeeded, scan the addresses and return the result. */ + + case DNS_SUCCEED: + rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ANSWERS, target); + if (rc != CSA_FAIL_NOADDR) return t->data.val = rc; + /* else fall through */ + + /* If the target has no IP addresses, the client cannot have an authorized + IP address. However, if the target site uses A6 records (not AAAA records) + we have to do yet another lookup in order to check them. */ + + case DNS_NOMATCH: + case DNS_NODATA: + + #if HAVE_IPV6 && defined(SUPPORT_A6) + if (type == T_AAAA) { type = T_A6; goto DNS_LOOKUP_AGAIN; } + #endif + + return t->data.val = CSA_FAIL_NOADDR; + } +} + + + /************************************************* * Handle verification (address & other) * *************************************************/ @@ -1019,6 +1355,19 @@ if (strcmpic(ss, US"helo") == 0) return helo_verified? OK : FAIL; } +/* Do Client SMTP Authorization checks in a separate function, and turn the +result code into user-friendly strings. */ + +if (strcmpic(ss, US"csa") == 0) + { + rc = acl_verify_csa(list); + *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s", + csa_reason_string[rc]); + csa_status = csa_status_string[rc]; + DEBUG(D_acl) debug_printf("CSA result %s\n", csa_status); + return csa_return_code[rc]; + } + /* Check that all relevant header lines have the correct syntax. If there is a syntax error, we return details of the error to the sender if configured to send out full details. (But a "message" setting on the ACL can override, as diff --git a/src/src/dns.c b/src/src/dns.c index ae5515c22..c6cd7060f 100644 --- a/src/src/dns.c +++ b/src/src/dns.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/dns.c,v 1.5 2005/02/17 11:58:26 ph10 Exp $ */ +/* $Cambridge: exim/src/src/dns.c,v 1.6 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -153,9 +153,9 @@ else *************************************************/ /* 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 @@ -192,12 +192,14 @@ if (reset != RESET_NEXT) 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, @@ -207,11 +209,11 @@ if (reset != RESET_NEXT) 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. */ @@ -670,6 +672,153 @@ if (type == T_ZNS) 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; + } + + /* 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 */ return DNS_FAIL; diff --git a/src/src/exim.h b/src/src/exim.h index e44cad788..9bc15cecb 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/exim.h,v 1.11 2005/04/27 10:00:18 ph10 Exp $ */ +/* $Cambridge: exim/src/src/exim.h,v 1.12 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -280,12 +280,19 @@ header files. I don't suppose they have T_SRV either. */ #define T_SRV 33 #endif -/* We use the private type T_ZNS for retrieving the nameservers for the -enclosing zone of a domain, and the private type T_MXH for retrieving -the MX hostnames only (without their priorities). */ +/* We define a few private types for special DNS lookups: + + . T_ZNS gets the nameservers of the enclosing zone of a domain + + . T_MXH gets the MX hostnames only (without their priorities) + + . T_CSA gets the domain's Client SMTP Authorization SRV record + +*/ #define T_ZNS (-1) #define T_MXH (-2) +#define T_CSA (-3) /* The resolv.h header defines __P(x) on some Solaris 2.5.1 systems (without checking that it is already defined, in fact). This conflicts with other diff --git a/src/src/expand.c b/src/src/expand.c index 49686555e..8677ccb5b 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/expand.c,v 1.20 2005/04/28 13:29:27 ph10 Exp $ */ +/* $Cambridge: exim/src/src/expand.c,v 1.21 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -333,6 +333,7 @@ static var_entry var_table[] = { { "caller_uid", vtype_uid, &real_uid }, { "compile_date", vtype_stringptr, &version_date }, { "compile_number", vtype_stringptr, &version_cnumber }, + { "csa_status", vtype_stringptr, &csa_status }, #ifdef WITH_OLD_DEMIME { "demime_errorlevel", vtype_int, &demime_errorlevel }, { "demime_reason", vtype_stringptr, &demime_reason }, diff --git a/src/src/globals.c b/src/src/globals.c index 5fed1a144..b5b889acc 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/globals.c,v 1.23 2005/05/03 14:20:01 ph10 Exp $ */ +/* $Cambridge: exim/src/src/globals.c,v 1.24 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -385,6 +385,8 @@ BOOL continue_more = FALSE; int continue_sequence = 1; uschar *continue_transport = NULL; +uschar *csa_status = NULL; + BOOL daemon_listen = FALSE; uschar *daemon_smtp_port = US"smtp"; BOOL debug_daemon = FALSE; @@ -473,6 +475,8 @@ int dk_do_verify = 0; #endif uschar *dns_again_means_nonexist = NULL; +int dns_csa_search_limit = 5; +BOOL dns_csa_use_reverse = TRUE; uschar *dns_ipv4_lookup = NULL; int dns_retrans = 0; int dns_retry = 0; diff --git a/src/src/globals.h b/src/src/globals.h index 9ba972f1f..d052fbe75 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/globals.h,v 1.15 2005/05/03 14:20:01 ph10 Exp $ */ +/* $Cambridge: exim/src/src/globals.h,v 1.16 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -214,6 +214,8 @@ extern BOOL continue_more; /* Flag more addresses waiting */ extern int continue_sequence; /* Sequence num for continued delivery */ extern uschar *continue_transport; /* Transport for continued delivery */ +extern uschar *csa_status; /* Client SMTP Authorization result */ + extern BOOL daemon_listen; /* True if listening required */ extern uschar *daemon_smtp_port; /* Can be a list of ports */ extern BOOL debug_daemon; /* Debug the daemon process only */ @@ -271,6 +273,8 @@ extern int dk_do_verify; /* DK verification switch. Set with ACL c #endif extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */ +extern int dns_csa_search_limit; /* How deep to search for CSA SRV records */ +extern BOOL dns_csa_use_reverse; /* Check CSA in reverse DNS? (non-standard) */ extern uschar *dns_ipv4_lookup; /* For these domains, don't look for AAAA (or A6) */ extern int dns_retrans; /* Retransmission time setting */ extern int dns_retry; /* Number of retries */ diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c index cdac6a47c..8133506b2 100644 --- a/src/src/lookups/dnsdb.c +++ b/src/src/lookups/dnsdb.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/lookups/dnsdb.c,v 1.10 2005/02/17 11:58:27 ph10 Exp $ */ +/* $Cambridge: exim/src/src/lookups/dnsdb.c,v 1.11 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -31,6 +31,7 @@ static char *type_names[] = { #endif #endif "cname", + "csa", "mx", "mxh", "ns", @@ -49,6 +50,7 @@ static int type_values[] = { #endif #endif T_CNAME, + T_CSA, /* Private type for "Client SMTP Authorization". */ T_MX, T_MXH, /* Private type for "MX hostnames" */ T_NS, @@ -112,7 +114,7 @@ int defer_mode = PASS; int type = T_TXT; int failrc = FAIL; uschar *outsep = US"\n"; -uschar *equals, *domain; +uschar *equals, *domain, *found; uschar buffer[256]; /* Because we're the working in the search pool, we try to reclaim as much @@ -228,15 +230,18 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer))) != NULL) { uschar rbuffer[256]; - int searchtype = (type == T_ZNS)? T_NS : /* record type we want */ - (type == T_MXH)? T_MX : type; - - /* If the type is PTR, we have to construct the relevant magic lookup key if - the original is an IP address (some experimental protocols are using PTR - records for different purposes where the key string is a host name). This - code for doing the reversal is now in a separate function. */ - - if (type == T_PTR && string_is_ip_address(domain, NULL) > 0) + int searchtype = (type == T_CSA)? T_SRV : /* record type we want */ + (type == T_MXH)? T_MX : + (type == T_ZNS)? T_NS : type; + + /* If the type is PTR or CSA, we have to construct the relevant magic lookup + key if the original is an IP address (some experimental protocols are using + PTR records for different purposes where the key string is a host name, and + Exim's extended CSA can be keyed by domains or IP addresses). This code for + doing the reversal is now in a separate function. */ + + if ((type == T_PTR || type == T_CSA) && + string_is_ip_address(domain, NULL) > 0) { dns_build_reverse(domain, rbuffer); domain = rbuffer; @@ -252,7 +257,8 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer))) continue with the next domain. In the case of DEFER, adjust the final "nothing found" result, but carry on to the next domain. */ - rc = dns_special_lookup(&dnsa, domain, type, NULL); + found = domain; + rc = dns_special_lookup(&dnsa, domain, type, &found); if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue; if (rc != DNS_SUCCEED) @@ -300,32 +306,63 @@ while ((domain = string_nextinlist(&keystring, &sep, buffer, sizeof(buffer))) yield = string_cat(yield, &size, &ptr, (uschar *)(rr->data+1), (rr->data)[0]); } - else /* T_CNAME, T_MX, T_MXH, T_NS, T_SRV, T_PTR */ + else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SRV */ { - int num; + 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(num, p); /* pointer is advanced */ + GETSHORT(priority, p); } else if (type == T_MX) { - GETSHORT(num, p); /* pointer is advanced */ - sprintf(CS s, "%d ", num); + GETSHORT(priority, p); + sprintf(CS s, "%d ", priority); yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); } else if (type == T_SRV) { - int weight, port; - GETSHORT(num, p); /* pointer is advanced */ + GETSHORT(priority, p); GETSHORT(weight, p); GETSHORT(port, p); - sprintf(CS s, "%d %d %d ", num, weight, port); + sprintf(CS s, "%d %d %d ", priority, weight, port); 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 (found != domain) + { + 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); + } + + /* 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)); diff --git a/src/src/macros.h b/src/src/macros.h index 2eef204a3..9ab6f0295 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/macros.h,v 1.12 2005/04/07 10:54:54 ph10 Exp $ */ +/* $Cambridge: exim/src/src/macros.h,v 1.13 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -178,7 +178,7 @@ record. */ /* Options for dns_next_rr */ -enum { RESET_NEXT, RESET_ANSWERS, RESET_ADDITIONAL }; +enum { RESET_NEXT, RESET_ANSWERS, RESET_AUTHORITY, RESET_ADDITIONAL }; /* Argument values for the time-of-day function */ diff --git a/src/src/readconf.c b/src/src/readconf.c index cf4048aae..be935bd11 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/readconf.c,v 1.7 2005/04/05 13:58:35 ph10 Exp $ */ +/* $Cambridge: exim/src/src/readconf.c,v 1.8 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -193,6 +193,8 @@ static optionlist optionlist_config[] = { { "delivery_date_remove", opt_bool, &delivery_date_remove }, { "dns_again_means_nonexist", opt_stringptr, &dns_again_means_nonexist }, { "dns_check_names_pattern", opt_stringptr, &check_dns_names_pattern }, + { "dns_csa_search_limit", opt_int, &dns_csa_search_limit }, + { "dns_csa_use_reverse", opt_bool, &dns_csa_use_reverse }, { "dns_ipv4_lookup", opt_stringptr, &dns_ipv4_lookup }, { "dns_retrans", opt_time, &dns_retrans }, { "dns_retry", opt_int, &dns_retry }, diff --git a/src/src/version.c b/src/src/version.c index cba0e64ec..052d41489 100644 --- a/src/src/version.c +++ b/src/src/version.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/version.c,v 1.7 2005/03/01 10:21:44 ph10 Exp $ */ +/* $Cambridge: exim/src/src/version.c,v 1.8 2005/05/10 10:19:11 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -12,7 +12,7 @@ #include "exim.h" -#define THIS_VERSION "4.51" +#define THIS_VERSION "4.52" /* The header file cnumber.h contains a single line containing the