diff --git a/postfix/HISTORY b/postfix/HISTORY index 1fc1ec5f6..7996d16b0 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -15331,13 +15331,27 @@ Apologies for any names omitted. feature can be used meaningfully at any protocol stage. File: proto/postconf.proto. -20090803 +20090805 - Workaround: with some local DNS servers including BIND, it - is possible that A or MX lookups succeed, while NS lookups - for the same domains time out. Spammers use this to avoid - access restrictions. To deal with future variations of - this, check_{client,helo,sender,etc}_{mx,ns,etc}_access no - longer tolerate any lookup failures. Instead, they reply - with $access_map_defer_code or $access_map_reject_code as - appropriate. File: smtpd/smtpd_check.c. + Bugfix: don't panic when an unexpected smtpd access map is + specified. File: smtpd/smtpd_check.c. + + Workaround: NS record lookups for certain domains always + fail, while other queries for those domains always succeed + (and even return replies with NS records as additional + information). + + This specific inconsistency would allow spammers to avoid + the Postfix check_{client,helo,sender,etc}_ns_access + restrictions, because those restrictions have effect only + for NS records that can be looked up in the DNS. + + To address this specific inconsistency, the Postfix + reject_unknown_helo_hostname, reject_unknown_sender_domain + and reject_unknown_recipient_domain restrictions now require + that a domain has NS records that resolve to at least one + IP address, and they now accept only MX records that resolve + to at least one IP address; those addresses may or may not + be "correct". Postfix has no code to determine whether the + SMTP client name has a resolvable NS or MX record. File: + smtpd/smtpd_check.c. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 31e754323..5fabd2574 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -14,21 +14,22 @@ specifies the release date of a stable release or snapshot release. If you upgrade from Postfix 2.5 or earlier, read RELEASE_NOTES-2.6 before proceeding. -Incompatibility with snapshot 20090803-nonprod -============================================== +Incompatibility with snapshot 20090805 +====================================== -The check_{client,helo,sender,etc}_{mx,ns,etc}_access features no -longer tolerate any lookup failures. Instead, they now reply with -$access_map_defer_code or $access_map_reject_code as appropriate. +With some domain names, NS record lookups always fail while other +lookups always succeed (and often return NS records as additional +information). This anomaly could be used by evil elements to skip +Postfix check_{client,helo,sender,recipient}_ns_access checks, +because those apply only domains with an NS record in the DNS. -The reason for this change is that spammers are using tricks where -A or MX lookups succeed while NS lookups for the same domains fail, -depending local DNS infrastructure details. The change deals with -future variants of this anomalous behavior. - -As a side effect, non-existent domain names in HELO commands will -now trigger a REJECT action with check_helo_{mx,ns}_access, where -previously such commands were silently permitted. +To address this specific problem, the reject_unknown_helo_hostname, +reject_unknown_sender_domain and reject_unknown_recipient_domain +features now require that a domain name has NS records that resolve +to at least one IP address, and they now accept only MX records +that resolve to at least one IP address; those addresses may or may +not be "correct". Postfix has no code to determine whether the +SMTP client name has a resolvable NS or MX record. Incompatibility with snapshot 20090606 ====================================== diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index d93cc4361..ce299ad39 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20090803" +#define MAIL_RELEASE_DATE "20090805" #define MAIL_VERSION_NUMBER "2.7" #ifdef SNAPSHOT diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 74d86ec47..33bc09237 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -354,6 +354,19 @@ static int unk_addr_tf_act; static int unv_rcpt_tf_act; static int unv_from_tf_act; + /* + * What address types to query for when we determine domain existence. + * + * XXX Should we accept mail from a domain that has IPv6 addresses only, when + * the local system has IPv4 only? This can happen when the receiving system + * uses an IPv6-enabled relayhost. + */ +#ifdef T_AAAA +#define CHECK_RR_ADDR_TYPES T_A, T_AAAA +#else +#define CHECK_RR_ADDR_TYPES T_A +#endif + /* * YASLM. */ @@ -1135,29 +1148,92 @@ static int reject_non_fqdn_hostname(SMTPD_STATE *state, char *name, return (stat); } -/* reject_unknown_hostname - fail if name has no A, AAAA or MX record */ +/* resolve_server_list - check if at least some name resolves */ + +static int resolve_server_list(const char *name, int type, + const char *reply_name, + const char *reply_class) +{ + const char *myname = "resolve_server_list"; + const char *domain = name; + DNS_RR *server_list; + DNS_RR *server; + int dns_status; + int soft_err = 0; + + /* + * Require that at least one server record exists. + */ + dns_status = dns_lookup(domain, type, 0, &server_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status == DNS_NOTFOUND && h_errno == NO_DATA) { + if (type == T_MX) { + server_list = dns_rr_create(domain, domain, type, C_IN, 0, 0, + domain, strlen(domain) + 1); + dns_status = DNS_OK; + } else if (type == T_NS) { + while ((domain = strchr(domain, '.')) != 0 && domain[1]) { + domain += 1; + dns_status = dns_lookup(domain, type, 0, &server_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status != DNS_NOTFOUND || h_errno != NO_DATA) + break; + } + } + } + if (dns_status != DNS_OK) { + msg_warn("Unable to look up %s host for %s: %s", dns_strtype(type), + domain && domain[1] ? domain : name, dns_strerror(h_errno)); + return (dns_status); + } + + /* + * Require that at least one server record resolves to an IP address. + */ + for (server = server_list; server != 0; server = server->next) { + if (msg_verbose) + msg_info("%s: %s hostname check: %s", + myname, dns_strtype(type), (char *) server->data); + if (valid_hostaddr((char *) server->data, DONT_GRIPE)) { + soft_err = 0; + break; + } + dns_status = dns_lookup_l((char *) server->data, 0, (DNS_RR **) 0, + (VSTRING *) 0, (VSTRING *) 0, + DNS_REQ_FLAG_STOP_OK, + CHECK_RR_ADDR_TYPES, 0); + if (dns_status == DNS_OK) { + soft_err = 0; + break; + } + msg_warn("Unable to look up %s host %s for %s %s: %s", + dns_strtype(type), (char *) server->data, + reply_class, reply_name, dns_strerror(h_errno)); + if (dns_status == DNS_RETRY) + soft_err = 1; + } + dns_rr_free(server_list); + return (soft_err ? DNS_RETRY : dns_status); +} + +/* reject_unknown_hostname - fail if name has no NS, A, AAAA or MX record */ static int reject_unknown_hostname(SMTPD_STATE *state, char *name, char *reply_name, char *reply_class) { const char *myname = "reject_unknown_hostname"; int dns_status; - DNS_RR *dummy; if (msg_verbose) msg_info("%s: %s", myname, name); -#ifdef T_AAAA -#define RR_ADDR_TYPES T_A, T_AAAA -#else -#define RR_ADDR_TYPES T_A -#endif - - dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0, - (VSTRING *) 0, DNS_REQ_FLAG_STOP_OK, - RR_ADDR_TYPES, T_MX, 0); - if (dummy) - dns_rr_free(dummy); + /* + * Require that at least one record exists of each type that + * check_server_access() wants to check. + */ + dns_status = resolve_server_list(name, T_MX, reply_name, reply_class); + if (dns_status == DNS_OK) + dns_status = resolve_server_list(name, T_NS, reply_name, reply_class); if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ if (dns_status != DNS_RETRY) return (smtpd_check_reject(state, MAIL_ERROR_POLICY, @@ -1176,25 +1252,24 @@ static int reject_unknown_hostname(SMTPD_STATE *state, char *name, return (SMTPD_CHECK_DUNNO); } -/* reject_unknown_mailhost - fail if name has no A, AAAA or MX record */ +/* reject_unknown_mailhost - fail if name has no NS, A, AAAA or MX record */ static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name, const char *reply_name, const char *reply_class) { const char *myname = "reject_unknown_mailhost"; int dns_status; - DNS_RR *dummy; if (msg_verbose) msg_info("%s: %s", myname, name); -#define MAILHOST_LOOKUP_FLAGS (DNS_REQ_FLAG_STOP_OK | DNS_REQ_FLAG_STOP_INVAL) - - dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0, - (VSTRING *) 0, MAILHOST_LOOKUP_FLAGS, - T_MX, RR_ADDR_TYPES, 0); - if (dummy) - dns_rr_free(dummy); + /* + * Require that at least one record exists of each type that + * check_server_access() wants to check. + */ + dns_status = resolve_server_list(name, T_MX, reply_name, reply_class); + if (dns_status == DNS_OK) + dns_status = resolve_server_list(name, T_NS, reply_name, reply_class); if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ if (dns_status != DNS_RETRY) return (smtpd_check_reject(state, MAIL_ERROR_POLICY, @@ -2315,8 +2390,13 @@ static int check_access(SMTPD_STATE *state, const char *table, const char *name, if (msg_verbose) msg_info("%s: %s", myname, name); - if ((dict = dict_handle(table)) == 0) - msg_panic("%s: dictionary not found: %s", myname, table); + if ((dict = dict_handle(table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + CHK_ACCESS_RETURN(check_table_result(state, table, value, name, + reply_name, reply_class, + def_acl), FOUND); + } if (flags == 0 || (flags & dict->flags) != 0) { if ((value = dict_get(dict, name)) != 0) CHK_ACCESS_RETURN(check_table_result(state, table, value, name, @@ -2360,8 +2440,13 @@ static int check_domain_access(SMTPD_STATE *state, const char *table, */ #define CHK_DOMAIN_RETURN(x,y) { *found = y; return(x); } - if ((dict = dict_handle(table)) == 0) - msg_panic("%s: dictionary not found: %s", myname, table); + if ((dict = dict_handle(table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + CHK_DOMAIN_RETURN(check_table_result(state, table, value, + domain, reply_name, reply_class, + def_acl), FOUND); + } for (name = domain; *name != 0; name = next) { if (flags == 0 || (flags & dict->flags) != 0) { if ((value = dict_get(dict, name)) != 0) @@ -2419,8 +2504,13 @@ static int check_addr_access(SMTPD_STATE *state, const char *table, #endif delim = '.'; - if ((dict = dict_handle(table)) == 0) - msg_panic("%s: dictionary not found: %s", myname, table); + if ((dict = dict_handle(table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + CHK_ADDR_RETURN(check_table_result(state, table, value, address, + reply_name, reply_class, + def_acl), FOUND); + } do { if (flags == 0 || (flags & dict->flags) != 0) { if ((value = dict_get(dict, addr)) != 0) @@ -2575,14 +2665,7 @@ static int check_server_access(SMTPD_STATE *state, const char *table, if (dns_status != DNS_OK) { msg_warn("Unable to look up %s host for %s: %s", dns_strtype(type), domain && domain[1] ? domain : name, dns_strerror(h_errno)); - /* No mercy for DNS failure. */ - return (smtpd_check_reject(state, MAIL_ERROR_POLICY, - dns_status == DNS_NOTFOUND ? - var_map_reject_code : var_map_defer_code, - smtpd_dsn_fix("4.1.8", reply_class), - "<%s>: %s rejected: %s", - reply_name, reply_class, - "Domain not found")); + return (SMTPD_CHECK_DUNNO); } /* @@ -2598,6 +2681,13 @@ static int check_server_access(SMTPD_STATE *state, const char *table, if (msg_verbose) msg_info("%s: %s hostname check: %s", myname, dns_strtype(type), (char *) server->data); + if (valid_hostaddr((char *) server->data, DONT_GRIPE)) { + if ((status = check_addr_access(state, table, (char *) server->data, + FULL, &found, reply_name, reply_class, + def_acl)) != 0 || found) + CHECK_SERVER_RETURN(status); + continue; + } if ((status = check_domain_access(state, table, (char *) server->data, FULL, &found, reply_name, reply_class, def_acl)) != 0 || found) @@ -2607,16 +2697,7 @@ static int check_server_access(SMTPD_STATE *state, const char *table, msg_warn("Unable to look up %s host %s for %s %s: %s", dns_strtype(type), (char *) server->data, reply_class, reply_name, MAI_STRERROR(aierr)); - /* No mercy for DNS failure. */ - status = smtpd_check_reject(state, - MAIL_ERROR_POLICY, - aierr == EAI_NONAME ? - var_map_reject_code : var_map_defer_code, - smtpd_dsn_fix("4.1.8", reply_class), - "<%s>: %s rejected: %s", - reply_name, reply_class, - "Domain not found"); - CHECK_SERVER_RETURN(status); + continue; } /* Now we must also free the addrinfo result. */ if (msg_verbose)