feature can be used meaningfully at any protocol stage.
File: proto/postconf.proto.
-20090803
-
- 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.
+20090805
+
+ Bugfix: don't panic when an unexpected smtpd access map is
+ specified. File: smtpd/smtpd_check.c.
+
+20090807
+
+ 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 inconsistency would allow spammers to avoid the Postfix
+ check_{client,helo,sender,etc}_ns_access restrictions,
+ because those restrictions have effect only for names that
+ are known in the DNS.
+
+ To address this specific inconsistency, the Postfix
+ check_{client,etc}_ns_access feature now requires that a
+ known-in-DNS domain name (or parent thereof) resolves to
+ at least one name server IP address.
+
+ For consistency, check_{client,etc}_mx_access now requires
+ that a known-in-DNS domain name resolves to at least one
+ mail server IP address.
+
+ The IP addresses thus obtained may or may not be "correct".
+ There is little to stop an uncooperative DNS server from
+ lying, especially when the owner of the domain has no
+ intention to receive email. File: smtpd/smtpd_check.c.
Incompatibility with Postfix 2.6.4
==================================
-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.
-
-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.
+With some domain names, NS record lookups always fail while other
+lookups always succeed (and may even 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 these apply only to domains that are known in the DNS.
+
+To address this specific problem, check_{client,etc}_ns_access now
+requires that a known-in-DNS domain name (or parent thereof) resolves
+to at least one name server IP address.
+
+For consistency, check_{client,etc}_mx_access now requires that a
+known-in-DNS domain name resolves to at least one mail server IP
+address.
+
+Keep in mind that these measures provide no hard assurances. There
+is little to stop an uncooperative DNS server from lying, especially
+when the owner of the domain has no intention to receive email.
Major changes - multi-instance support
--------------------------------------
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,
*/
#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)
#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)
struct addrinfo *res;
int status;
INET_PROTO_INFO *proto_info;
+ const char *saved_domain;
+ int non_err, soft_err;
+ int known_name_in_dns;
+ int ping_status;
/*
* Sanity check.
*
* If the domain name exists but no NS record exists, look up parent domain
* NS records.
+ *
+ * After the initial lookup fails, do one final DNS sanity check. Reject
+ * mail when the name exists, but MX lookup produces no valid response or
+ * NS lookup fails for any reason. Beware, this sanity check provides no
+ * hard assurance. An uncooperative DNS server may lie about everything,
+ * including non-existence.
*/
+#define SOME_DNS_RR_EXISTS(stat, herr) \
+ ((stat) == DNS_OK || (stat) == DNS_INVAL || (herr) == NO_DATA)
+
+ saved_domain = domain;
dns_status = dns_lookup(domain, type, 0, &server_list,
(VSTRING *) 0, (VSTRING *) 0);
- if (dns_status == DNS_NOTFOUND && h_errno == NO_DATA) {
+ known_name_in_dns = SOME_DNS_RR_EXISTS(dns_status, h_errno);
+ if (dns_status == DNS_NOTFOUND /* Not: 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) {
+ } else if (type == T_NS && h_errno == NO_DATA) {
while ((domain = strchr(domain, '.')) != 0 && domain[1]) {
domain += 1;
dns_status = dns_lookup(domain, type, 0, &server_list,
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"));
+ if (known_name_in_dns == 0) {
+ /* With hostile DNS, an address query is more likely to work. */
+ ping_status = dns_lookup_l(saved_domain, 0, (DNS_RR **) 0,
+ (VSTRING *) 0, (VSTRING *) 0,
+ DNS_REQ_FLAG_STOP_OK,
+ RR_ADDR_TYPES, 0);
+ known_name_in_dns = SOME_DNS_RR_EXISTS(ping_status, h_errno);
+ }
+ if (known_name_in_dns)
+ return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ dns_status == DNS_RETRY ?
+ var_map_defer_code : var_map_reject_code,
+ smtpd_dsn_fix("4.1.8", reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ "Domain not found"));
+ return (SMTPD_CHECK_DUNNO);
}
/*
* Check the hostnames first, then the addresses.
*/
proto_info = inet_proto_info();
+ non_err = soft_err = 0;
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)) {
+ non_err = 1;
+ 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)
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);
+ if (aierr == EAI_AGAIN || aierr == EAI_SYSTEM)
+ soft_err = 1;
+ continue;
}
+ non_err = 1;
/* Now we must also free the addrinfo result. */
if (msg_verbose)
msg_info("%s: %s host address check: %s",
}
freeaddrinfo(res0); /* 200412 */
}
- CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO);
+ status = non_err ? SMTPD_CHECK_DUNNO :
+ smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ soft_err ? var_map_defer_code :
+ var_map_reject_code,
+ smtpd_dsn_fix("4.1.8", reply_class),
+ "<%s>: %s rejected: %s",
+ reply_name, reply_class,
+ "Domain not found");
+ CHECK_SERVER_RETURN(status);
}
/* check_ccert_access - access for TLS clients by certificate fingerprint */