]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.7-20090805
authorWietse Venema <wietse@porcupine.org>
Wed, 5 Aug 2009 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <viktor@dukhovni.org>
Tue, 5 Feb 2013 06:35:38 +0000 (06:35 +0000)
postfix/HISTORY
postfix/RELEASE_NOTES
postfix/src/global/mail_version.h
postfix/src/smtpd/smtpd_check.c

index 1fc1ec5f622b2168a1e62486853719a75ae9802a..7996d16b0eecc5e7370966dbfaeb78b617101939 100644 (file)
@@ -15331,13 +15331,27 @@ Apologies for any names omitted.
        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.
+
+       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.
index 31e75432360ee32acd3bf520262e0ad51e798b1f..5fabd25743513204e27f92f158facd6f0cf21f91 100644 (file)
@@ -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
-==============================================
-
-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.
+Incompatibility with snapshot 20090805
+======================================
 
-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 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.
+
+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
 ======================================
index d93cc436143ed7133a502b6ac5291149f46fe965..ce299ad39a2055b4e3853fd740e71645f65873d8 100644 (file)
@@ -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
index 74d86ec4782cd335aa1855a17521a6b447270134..33bc092370c8b9caeaa67b07cf09b3664957ae9a 100644 (file)
@@ -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)