int dkim_exim_query_dns_txt(char *name, char *answer) { dns_answer dnsa; dns_scan dnss; dns_record *rr; if (dns_lookup(&dnsa, (uschar *)name, T_TXT, NULL) != DNS_SUCCEED) return PDKIM_FAIL; /* Search for TXT record */ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); rr != NULL; rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == T_TXT) break; /* Copy record content to the answer buffer */ if (rr != NULL) { int rr_offset = 0; int answer_offset = 0; while (rr_offset < rr->size) { uschar len = (rr->data)[rr_offset++]; snprintf(answer+(answer_offset), PDKIM_DNS_TXT_MAX_RECLEN-(answer_offset), "%.*s", (int)len, (char *)((rr->data)+rr_offset)); rr_offset+=len; answer_offset+=len; if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN) { return PDKIM_FAIL; } } } else return PDKIM_FAIL; return PDKIM_OK; }
static const uschar * dns_extract_auth_name(const dns_answer * dnsa) /* FIXME: const dns_answer */ { dns_scan dnss; dns_record * rr; HEADER * h = (HEADER *) dnsa->answer; if (!h->nscount || !h->aa) return NULL; for (rr = dns_next_rr((dns_answer*) dnsa, &dnss, RESET_AUTHORITY); rr; rr = dns_next_rr((dns_answer*) dnsa, &dnss, RESET_NEXT)) if (rr->type == (h->ancount ? T_NS : T_SOA)) return rr->name; return NULL; }
static int dkim_exim_query_dns_txt(char *name, char *answer) { dns_answer dnsa; dns_scan dnss; dns_record *rr; lookup_dnssec_authenticated = NULL; if (dns_lookup(&dnsa, US name, T_TXT, NULL) != DNS_SUCCEED) return PDKIM_FAIL; /* Search for TXT record */ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); rr; rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == T_TXT) { int rr_offset = 0; int answer_offset = 0; /* Copy record content to the answer buffer */ while (rr_offset < rr->size) { uschar len = rr->data[rr_offset++]; snprintf(answer + answer_offset, PDKIM_DNS_TXT_MAX_RECLEN - answer_offset, "%.*s", (int)len, (char *) (rr->data + rr_offset)); rr_offset += len; answer_offset += len; if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN) return PDKIM_FAIL; } return PDKIM_OK; } return PDKIM_FAIL; }
int dns_special_lookup(dns_answer *dnsa, const uschar *name, int type, const uschar **fully_qualified_name) { switch (type) { /* 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; 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; 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 */ return DNS_FAIL; }
int dns_lookup(dns_answer *dnsa, const uschar *name, int type, const uschar **fully_qualified_name) { int i; const uschar *orig_name = name; BOOL secure_so_far = TRUE; /* Loop to follow CNAME chains so far, but no further... */ for (i = 0; i < 10; i++) { uschar data[256]; dns_record *rr, cname_rr, type_rr; dns_scan dnss; int datalen, rc; /* DNS lookup failures get passed straight back. */ if ((rc = dns_basic_lookup(dnsa, name, type)) != DNS_SUCCEED) return rc; /* We should have either records of the required type, or a CNAME record, or both. We need to know whether both exist for getting the fully qualified name, but avoid scanning more than necessary. Note that we must copy the contents of any rr blocks returned by dns_next_rr() as they use the same area in the dnsa block. */ cname_rr.data = type_rr.data = NULL; for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) { if (rr->type == type) { if (type_rr.data == NULL) type_rr = *rr; if (cname_rr.data != NULL) break; } else if (rr->type == T_CNAME) cname_rr = *rr; } /* For the first time round this loop, if a CNAME was found, take the fully qualified name from it; otherwise from the first data record, if present. */ if (i == 0 && fully_qualified_name != NULL) { uschar * rr_name = cname_rr.data ? cname_rr.name : type_rr.data ? type_rr.name : NULL; if ( rr_name && Ustrcmp(rr_name, *fully_qualified_name) != 0 && rr_name[0] != '*' #ifdef EXPERIMENTAL_INTERNATIONAL && ( !string_is_utf8(*fully_qualified_name) || Ustrcmp(rr_name, string_domain_utf8_to_alabel(*fully_qualified_name, NULL)) != 0 ) #endif ) *fully_qualified_name = string_copy_dnsdomain(rr_name); } /* If any data records of the correct type were found, we are done. */ if (type_rr.data != NULL) { if (!secure_so_far) /* mark insecure if any element of CNAME chain was */ dns_set_insecure(dnsa); return DNS_SUCCEED; } /* If there are no data records, we need to re-scan the DNS using the domain given in the CNAME record, which should exist (otherwise we should have had a failure from dns_lookup). However code against the possibility of its not existing. */ if (cname_rr.data == NULL) return DNS_FAIL; datalen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, sizeof(data)); if (datalen < 0) return DNS_FAIL; name = data; if (!dns_is_secure(dnsa)) secure_so_far = FALSE; DEBUG(D_dns) debug_printf("CNAME found: change to %s\n", name); } /* Loop back to do another lookup */ /*Control reaches here after 10 times round the CNAME loop. Something isn't right... */ log_write(0, LOG_MAIN, "CNAME loop for %s encountered", orig_name); return DNS_FAIL; }
static int dnsdb_find(void *handle, uschar *filename, const uschar *keystring, int length, uschar **result, uschar **errmsg, uint *do_cache) { int rc; int size = 256; int ptr = 0; int sep = 0; int defer_mode = PASS; int dnssec_mode = OK; int save_retrans = dns_retrans; int save_retry = dns_retry; int type; int failrc = FAIL; const uschar *outsep = CUS"\n"; const uschar *outsep2 = NULL; uschar *equals, *domain, *found; /* Because we're the working in the search pool, we try to reclaim as much store as possible later, so we preallocate the result here */ uschar *yield = store_get(size); dns_record *rr; dns_answer dnsa; dns_scan dnss; handle = handle; /* Keep picky compilers happy */ filename = filename; length = length; do_cache = do_cache; /* If the string starts with '>' we change the output separator. If it's followed by ';' or ',' we set the TXT output separator. */ while (isspace(*keystring)) keystring++; if (*keystring == '>') { outsep = keystring + 1; keystring += 2; if (*keystring == ',') { outsep2 = keystring + 1; keystring += 2; } else if (*keystring == ';') { outsep2 = US""; keystring++; } while (isspace(*keystring)) keystring++; } /* Check for a modifier keyword. */ for (;;) { if (strncmpic(keystring, US"defer_", 6) == 0) { keystring += 6; if (strncmpic(keystring, US"strict", 6) == 0) { defer_mode = DEFER; keystring += 6; } else if (strncmpic(keystring, US"lax", 3) == 0) { defer_mode = PASS; keystring += 3; } else if (strncmpic(keystring, US"never", 5) == 0) { defer_mode = OK; keystring += 5; } else { *errmsg = US"unsupported dnsdb defer behaviour"; return DEFER; } } else if (strncmpic(keystring, US"dnssec_", 7) == 0) { keystring += 7; if (strncmpic(keystring, US"strict", 6) == 0) { dnssec_mode = DEFER; keystring += 6; } else if (strncmpic(keystring, US"lax", 3) == 0) { dnssec_mode = PASS; keystring += 3; } else if (strncmpic(keystring, US"never", 5) == 0) { dnssec_mode = OK; keystring += 5; } else { *errmsg = US"unsupported dnsdb dnssec behaviour"; return DEFER; } } else if (strncmpic(keystring, US"retrans_", 8) == 0) { int timeout_sec; if ((timeout_sec = readconf_readtime(keystring += 8, ',', FALSE)) <= 0) { *errmsg = US"unsupported dnsdb timeout value"; return DEFER; } dns_retrans = timeout_sec; while (*keystring != ',') keystring++; } else if (strncmpic(keystring, US"retry_", 6) == 0) { int retries; if ((retries = (int)strtol(CCS keystring + 6, CSS &keystring, 0)) < 0) { *errmsg = US"unsupported dnsdb retry count"; return DEFER; } dns_retry = retries; } else break; while (isspace(*keystring)) keystring++; if (*keystring++ != ',') { *errmsg = US"dnsdb modifier syntax error"; return DEFER; } while (isspace(*keystring)) keystring++; } /* Figure out the "type" value if it is not T_TXT. If the keystring contains an = this must be preceded by a valid type name. */ type = T_TXT; if ((equals = Ustrchr(keystring, '=')) != NULL) { int i, len; uschar *tend = equals; while (tend > keystring && isspace(tend[-1])) tend--; len = tend - keystring; for (i = 0; i < nelem(type_names); i++) if (len == Ustrlen(type_names[i]) && strncmpic(keystring, US type_names[i], len) == 0) { type = type_values[i]; break; } if (i >= nelem(type_names)) { *errmsg = US"unsupported DNS record type"; return DEFER; } keystring = equals + 1; while (isspace(*keystring)) keystring++; } /* Initialize the resolver in case this is the first time it has been used. */ dns_init(FALSE, FALSE, dnssec_mode != OK); /* The remainder of the string must be a list of domains. As long as the lookup for at least one of them succeeds, we return success. Failure means that none of them were found. The original implementation did not support a list of domains. Adding the list feature is compatible, except in one case: when PTR records are being looked up for a single IPv6 address. Fortunately, we can hack in a compatibility feature here: If the type is PTR and no list separator is specified, and the entire remaining string is valid as an IP address, set an impossible separator so that it is treated as one item. */ if (type == T_PTR && keystring[0] != '<' && string_is_ip_address(keystring, NULL) != 0) sep = -1; /* SPF strings should be concatenated without a separator, thus make it the default if not defined (see RFC 4408 section 3.1.3). Multiple SPF records are forbidden (section 3.1.2) but are currently not handled specially, thus they are concatenated with \n by default. MX priority and value are space-separated by default. SRV and TLSA record parts are space-separated by default. */ if (!outsep2) switch(type) { case T_SPF: outsep2 = US""; break; case T_SRV: case T_MX: case T_TLSA: outsep2 = US" "; break; } /* Now scan the list and do a lookup for each item */ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0))) { uschar rbuffer[256]; 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; } do { DEBUG(D_lookup) debug_printf("dnsdb key: %s\n", domain); /* Do the lookup and sort out the result. There are four special types that are handled specially: T_CSA, T_ZNS, T_ADDRESSES and T_MXH. The first two are handled in a special lookup function so that the facility could be used from other parts of the Exim code. T_ADDRESSES is handled by looping over the types of A lookup. T_MXH affects only what happens later on in this function, but for tidiness it is handled by the "special". If the lookup fails, continue with the next domain. In the case of DEFER, adjust the final "nothing found" result, but carry on to the next domain. */ found = domain; #if HAVE_IPV6 if (type == T_ADDRESSES) /* NB cannot happen unless HAVE_IPV6 */ { if (searchtype == T_ADDRESSES) searchtype = T_AAAA; else if (searchtype == T_AAAA) searchtype = T_A; rc = dns_special_lookup(&dnsa, domain, searchtype, CUSS &found); } else #endif rc = dns_special_lookup(&dnsa, domain, type, CUSS &found); lookup_dnssec_authenticated = dnssec_mode==OK ? NULL : dns_is_secure(&dnsa) ? US"yes" : US"no"; if (rc == DNS_NOMATCH || rc == DNS_NODATA) continue; if ( rc != DNS_SUCCEED || (dnssec_mode == DEFER && !dns_is_secure(&dnsa)) ) { if (defer_mode == DEFER) { dns_retrans = save_retrans; dns_retry = save_retry; dns_init(FALSE, FALSE, FALSE); /* clr dnssec bit */ return DEFER; /* always defer */ } if (defer_mode == PASS) failrc = DEFER; /* defer only if all do */ continue; /* treat defer as fail */ } /* Search the returned records */ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); rr != NULL; rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) { if (rr->type != searchtype) continue; if (*do_cache > rr->ttl) *do_cache = rr->ttl; if (type == T_A || type == T_AAAA || type == T_ADDRESSES) { dns_address *da; for (da = dns_address_from_rr(&dnsa, rr); da; da = da->next) { if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1); yield = string_cat(yield, &size, &ptr, da->address); } continue; } /* Other kinds of record just have one piece of data each, but there may be several of them, of course. */ if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1); if (type == T_TXT || type == T_SPF) { if (outsep2 == NULL) { /* output only the first item of data */ yield = string_catn(yield, &size, &ptr, (uschar *)(rr->data+1), (rr->data)[0]); } else { /* output all items */ int data_offset = 0; while (data_offset < rr->size) { uschar chunk_len = (rr->data)[data_offset++]; if (outsep2[0] != '\0' && data_offset != 1) yield = string_catn(yield, &size, &ptr, outsep2, 1); yield = string_catn(yield, &size, &ptr, US ((rr->data)+data_offset), chunk_len); data_offset += chunk_len; } } } else if (type == T_TLSA) { uint8_t usage, selector, matching_type; uint16_t i, payload_length; uschar s[MAX_TLSA_EXPANDED_SIZE]; uschar * sp = s; uschar * p = US rr->data; usage = *p++; selector = *p++; matching_type = *p++; /* What's left after removing the first 3 bytes above */ payload_length = rr->size - 3; sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2, selector, *outsep2, matching_type, *outsep2); /* Now append the cert/identifier, one hex char at a time */ 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); } 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 = 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); 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); 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_catn(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)); /* If an overlong response was received, the data will have been truncated and dn_expand may fail. */ if (rc < 0) { log_write(0, LOG_MAIN, "host name alias list truncated: type=%s " "domain=%s", dns_text_type(type), domain); break; } else yield = string_cat(yield, &size, &ptr, s); if (type == T_SOA && outsep2 != NULL) { unsigned long serial, refresh, retry, expire, minimum; p += rc; yield = string_catn(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); p += rc; GETLONG(serial, p); GETLONG(refresh, p); GETLONG(retry, p); GETLONG(expire, p); GETLONG(minimum, p); sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu", *outsep2, serial, *outsep2, refresh, *outsep2, retry, *outsep2, expire, *outsep2, minimum); yield = string_cat(yield, &size, &ptr, s); } } } /* Loop for list of returned records */ /* Loop for set of A-lookup types */ } while (type == T_ADDRESSES && searchtype != T_A); } /* Loop for list of domains */ /* Reclaim unused memory */ store_reset(yield + ptr + 1); /* If ptr == 0 we have not found anything. Otherwise, insert the terminating zero and return the result. */ dns_retrans = save_retrans; dns_retry = save_retry; dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */ if (ptr == 0) return failrc; yield[ptr] = 0; *result = yield; return OK; }