]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.11-20130401-nonprod 20130401-nonprod
authorWietse Venema <wietse@porcupine.org>
Tue, 2 Apr 2013 03:23:11 +0000 (23:23 -0400)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Sat, 6 Apr 2013 04:18:13 +0000 (00:18 -0400)
postfix/HISTORY
postfix/makedefs
postfix/src/global/mail_version.h
postfix/src/smtp/smtp.h
postfix/src/smtp/smtp_connect.c
postfix/src/smtp/smtp_reuse.c
postfix/src/smtp/smtp_session.c
postfix/src/smtp/smtp_tls_sess.c

index ec8a2b775433bb22c23a3414f776f82206747a47..37d4d8a39b5267148d13f726126f220931c72997 100644 (file)
@@ -18398,5 +18398,9 @@ Apologies for any names omitted.
        src/smtp/smtp_reuse.c, src/smtp/smtp_session.c,
        src/smtp/smtp_tls_sess.c.
 
+20130401
+
+       Refactoring: allow smtp_session_alloc() to fail gracefully
+       and report an error.
        Cleanup: "zero time limit" corner case in read_wait() and
        write_wait() emulation. Files: util/poll_fd.c, util/iostuff.h.
index 15edabcd1daea71d798806a75f564e71a531fb91..746e7762ebb41e21d81f9971c3bb0ad5ee965c16 100644 (file)
@@ -623,7 +623,7 @@ CCARGS="$CCARGS -DSNAPSHOT"
 
 # Non-production: needs thorough testing, or major changes are still
 # needed before the code stabilizes.
-#CCARGS="$CCARGS -DNONPROD"
+CCARGS="$CCARGS -DNONPROD"
 
 sed 's/  / /g' <<EOF
 SYSTYPE        = $SYSTYPE
index 85a1359a41b4e5f77ded1b9610babdbffb52556b..eef45f7aecb771d99e63cafb36278556fa54960e 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      "20130331"
+#define MAIL_RELEASE_DATE      "20130401"
 #define MAIL_VERSION_NUMBER    "2.11"
 
 #ifdef SNAPSHOT
index 03dbf926bf403f34d2c5a8aa4ffcbcb8767197d6..7ccd29bfb8f9618044c36204bff141f51cbc43ff 100644 (file)
@@ -149,6 +149,7 @@ typedef struct SMTP_STATE {
 #define SMTP_MISC_FLAG_COMPLETE_SESSION        (1<<8)
 #define SMTP_MISC_FLAG_PREF_IPV6       (1<<9)
 #define SMTP_MISC_FLAG_PREF_IPV4       (1<<10)
+#define SMTP_MISC_FLAG_NO_TLS          (1<<11)
 
 #define SMTP_MISC_FLAG_CONN_CACHE_MASK \
        (SMTP_MISC_FLAG_CONN_LOAD | SMTP_MISC_FLAG_CONN_STORE)
@@ -259,19 +260,21 @@ typedef struct SMTP_SESSION {
     SMTP_STATE *state;                 /* back link */
 } SMTP_SESSION;
 
-extern SMTP_SESSION *smtp_session_alloc(VSTREAM *, const char *, const char *,
-                                      const char *, unsigned, time_t, int);
+extern SMTP_SESSION *smtp_session_alloc(DSN_BUF *, const char *, const char *,
+                                               const char *, unsigned, int);
+extern void smtp_session_new_stream(SMTP_SESSION *, VSTREAM *, time_t, int);
+extern int smtp_sess_tls_check(const char *, const char *, unsigned, int);
 extern void smtp_session_free(SMTP_SESSION *);
 extern int smtp_session_passivate(SMTP_SESSION *, VSTRING *, VSTRING *);
 extern SMTP_SESSION *smtp_session_activate(int, VSTRING *, VSTRING *);
 
 #ifdef USE_TLS
-extern void smtp_tls_list_init(void);
 
  /*
   * smtp_tls_sess.c
   */
-extern SMTP_TLS_SESS *smtp_tls_sess_alloc(const char *, const char *,
+extern void smtp_tls_list_init(void);
+extern SMTP_TLS_SESS *smtp_tls_sess_alloc(DSN_BUF *, const char *, const char *,
                                                  unsigned, int);
 extern SMTP_TLS_SESS *smtp_tls_sess_free(SMTP_TLS_SESS *);
 
index 21b5e42529cfd5121d74fb042230283a88434f43..aba88009ab9fc4149db9e24ec99fca60f998f845 100644 (file)
@@ -293,6 +293,17 @@ static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr * sa,
     int     saved_errno;
     VSTREAM *stream;
     time_t  start_time;
+    SMTP_SESSION *session;
+
+    /*
+     * Session construction is cheap, and can now tempfail when TLSA lookups
+     * don't work at the DANE security level. This also handles table lookup
+     * errors more gracefully. So construct the session, and then connect. If
+     * the connection fails, tear down the session.
+     */
+    if ((session = smtp_session_alloc(why, destination, name, addr,
+                                     port, sess_flags)) == 0)
+       return (0);
 
     start_time = time((time_t *) 0);
     if (var_smtp_conn_tmout > 0) {
@@ -311,6 +322,7 @@ static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr * sa,
        else
            dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr);
        close(sock);
+       smtp_session_free(session);
        return (0);
     }
     stream = vstream_fdopen(sock, O_RDWR);
@@ -326,10 +338,13 @@ static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr * sa,
        vstream_tweak_tcp(stream);
 
     /*
-     * Bundle up what we have into a nice SMTP_SESSION object.
+     * Update the SMTP_SESSION state with this newly-created stream, and make
+     * it subject to the new-stream connection caching policy (as opposed to
+     * the reused-stream caching policy).
      */
-    return (smtp_session_alloc(stream, destination, name, addr,
-                              port, start_time, sess_flags));
+    smtp_session_new_stream(session, stream, start_time, sess_flags);
+
+    return (session);
 }
 
 /* smtp_parse_destination - parse host/port destination */
index 108b51ddc80f36f30027b34b517c277ae4e10349..fad09e1da2f1d23721abb5caee8d826d1dc7c47f 100644 (file)
@@ -274,6 +274,7 @@ SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *state, const char *addr,
      * credentials or the wrong TLS policy.
      */
     if ((var_smtp_tls_per_site && *var_smtp_tls_per_site)
+       || (var_smtp_tls_policy && *var_smtp_tls_policy)
        || (var_smtp_sasl_passwd && *var_smtp_sasl_passwd))
        return (0);
 
index 8390cccac7a9a607a43cf7aa603f8794b613edfb..79c89b8f00817defbf24ed695fc0ffe996933be5 100644 (file)
@@ -6,16 +6,27 @@
 /* SYNOPSIS
 /*     #include "smtp.h"
 /*
-/*     SMTP_SESSION *smtp_session_alloc(stream, dest, host, addr,
-/*                                     port, start, flags)
-/*     VSTREAM *stream;
+/*     SMTP_SESSION *smtp_session_alloc(why, dest, host, addr,
+/*                                     port, flags)
+/*     DSN_BUF *why;
 /*     char    *dest;
 /*     char    *host;
 /*     char    *addr;
 /*     unsigned port;
+/*     int     flags;
+/*
+/*     void    smtp_session_new_stream(session, stream, start, flags)
+/*     SMTP_SESSION *session;
+/*     VSTREAM *stream;
 /*     time_t  start;
 /*     int     flags;
 /*
+/*     int     smtp_sess_tls_check(dest, host, port, valid)
+/*     char    *dest;
+/*     char    *host;
+/*     unsigned port;
+/*     int     valid;
+/*
 /*     void    smtp_session_free(session)
 /*     SMTP_SESSION *session;
 /*
 /*     VSTRING *endp_prop;
 /* DESCRIPTION
 /*     smtp_session_alloc() allocates memory for an SMTP_SESSION structure
-/*     and initializes it with the given stream and destination, host name
-/*     and address information.  The host name and address strings are
-/*     copied. The port is in network byte order.
-/*     When TLS is enabled, smtp_session_alloc() looks up the
-/*     per-site TLS policies for TLS enforcement and certificate
-/*     verification.  The resulting policy is stored into the
-/*     SMTP_SESSION object.
+/*     and initializes it with the given destination, host name and address
+/*     information.  The host name and address strings are copied. The port
+/*     is in network byte order.  When TLS is enabled, smtp_session_alloc()
+/*     looks up the per-site TLS policies for TLS enforcement and certificate
+/*     verification.  The resulting policy is stored into the SMTP_SESSION
+/*     object.  Table and DNS lookups can fail during TLS policy creation,
+/*     when this happens, "why" is updated with the error reason and a null
+/*     session pointer is returned.
+/*
+/*     smtp_session_new_stream() updates an SMTP_SESSION structure
+/*     with a newly-created stream that was created at the specified
+/*     start time, and makes it subject to the specified connection
+/*     caching policy.
+/*
+/*     smtp_sess_tls_check() returns true if TLS use is mandatory, invalid
+/*     or indeterminate. The return value is false only if TLS is optional.
+/*     This is not yet used anywhere, it can be used to safely enable TLS
+/*     policy with smtp_reuse_addr().
 /*
 /*     smtp_session_free() destroys an SMTP_SESSION structure and its
 /*     members, making memory available for reuse. It will handle the
@@ -61,6 +83,8 @@
 /*     The address of the host that we are connected to.
 /* .IP port
 /*     The remote port, network byte order.
+/* .IP valid
+/*     The DNSSEC validation status of the host name.
 /* .IP start
 /*     The time when this connection was opened.
 /* .IP flags
@@ -72,6 +96,8 @@
 /*     Enable re-use of cached SMTP or LMTP connections.
 /* .IP SMTP_MISC_FLAG_CONN_STORE
 /*     Enable saving of cached SMTP or LMTP connections.
+/* .IP SMTP_MISC_FLAG_NO_TLS
+/*     Used only internally in smtp_session.c
 /* .RE
 /*     SMTP_MISC_FLAG_CONN_MASK corresponds with both _LOAD and _STORE.
 /* .IP dest_prop
 #include <vstream.h>
 #include <stringops.h>
 #include <valid_hostname.h>
-#include <name_code.h>
 
 /* Global library. */
 
 
 /* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */
 
-SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, const char *dest,
+SMTP_SESSION *smtp_session_alloc(DSN_BUF *why, const char *dest,
                                         const char *host, const char *addr,
-                                        unsigned port, time_t start,
-                                        int flags)
+                                        unsigned port, int flags)
 {
     SMTP_SESSION *session;
+    int     valid = (flags & SMTP_MISC_FLAG_TLSA_HOST);
 
     session = (SMTP_SESSION *) mymalloc(sizeof(*session));
-    session->stream = stream;
+    session->stream = 0;
     session->dest = mystrdup(dest);
     session->host = mystrdup(host);
     session->addr = mystrdup(addr);
@@ -168,29 +193,71 @@ SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, const char *dest,
 
     session->send_proto_helo = 0;
 
-    if (flags & SMTP_MISC_FLAG_CONN_STORE)
-       CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time);
-    else
-       DONT_CACHE_THIS_SESSION;
-    session->reuse_count = 0;
     USE_NEWBORN_SESSION;                       /* He's not dead Jim! */
 
 #ifdef USE_SASL_AUTH
     smtp_sasl_connect(session);
 #endif
 
+    /*
+     * XXX Not documented: calling smtp_session_alloc() with a null pointer
+     * DSN_BUF argument.
+     * 
+     * XXX Not documented: calling smtp_tls_sess_alloc() with a null pointer
+     * host argument.
+     */
 #ifdef USE_TLS
     session->tls_context = 0;
     session->tls_retry_plain = 0;
     session->tls_nexthop = 0;
-    session->tls =
-       smtp_tls_sess_alloc(dest, host, port, flags & SMTP_MISC_FLAG_TLSA_HOST);
+#define NO_DSN_BUF     (DSN_BUF *) 0
+#define NO_DEST                (char *) 0
+#define NO_HOST                (char *) 0
+#define NO_PORT                0
+#define NO_FLAGS       0
+    if (flags & SMTP_MISC_FLAG_NO_TLS)
+       session->tls = smtp_tls_sess_alloc(NO_DSN_BUF, NO_DEST, NO_HOST,
+                                          NO_PORT, NO_FLAGS);
+    else
+       session->tls = smtp_tls_sess_alloc(why, dest, host, port, valid);
+    if (!session->tls) {
+       smtp_session_free(session);
+       return (0);
+    }
 #endif
     session->state = 0;
     debug_peer_check(host, addr);
     return (session);
 }
 
+/* smtp_session_new_stream - finalize session with newly-created connection */
+
+void    smtp_session_new_stream(SMTP_SESSION *session, VSTREAM *stream,
+                                       time_t start, int flags)
+{
+    const char *myname = "smtp_session_new_stream";
+
+    /*
+     * Sanity check.
+     */
+    if (session->stream != 0)
+       msg_panic("%s: session exists", myname);
+
+    session->stream = stream;
+
+    /*
+     * Make the session subject to the new-stream connection caching policy,
+     * as opposed to the reused-stream connection caching policy at the
+     * bottom of this module. Both policies are enforced in this file, not
+     * one policy here and the other at some random place in smtp_connect.c.
+     */
+    if (flags & SMTP_MISC_FLAG_CONN_STORE)
+       CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time);
+    else
+       DONT_CACHE_THIS_SESSION;
+    session->reuse_count = 0;
+}
+
 /* smtp_session_free - destroy SMTP_SESSION structure and contents */
 
 void    smtp_session_free(SMTP_SESSION *session)
@@ -232,6 +299,32 @@ void    smtp_session_free(SMTP_SESSION *session)
     myfree((char *) session);
 }
 
+/* smtp_sess_tls_check - does session require tls */
+
+int     smtp_sess_tls_check(const char *dest, const char *host, unsigned port,
+                                   int valid)
+{
+#ifdef USE_TLS
+    static DSN_BUF *why;
+    SMTP_TLS_SESS *tls;
+
+    if (!why)
+       why = dsb_create();
+
+    tls = smtp_tls_sess_alloc(why, dest, host, ntohs(port), valid);
+    dsb_reset(why);
+
+    if (tls && tls->level >= TLS_LEV_NONE && tls->level <= TLS_LEV_MAY)
+       return (0);
+    if (tls)
+       smtp_tls_sess_free(tls);
+    return (1);
+#else
+            return (0);
+#endif
+}
+
+
 /* smtp_session_passivate - passivate an SMTP_SESSION object */
 
 int     smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop,
@@ -372,11 +465,14 @@ SMTP_SESSION *smtp_session_activate(int fd, VSTRING *dest_prop,
 
     /*
      * Allright, bundle up what we have sofar.
+     * 
+     * Caller is responsible for not reusing plain-text connections when TLS is
+     * required.  We disable TLS policy lookups, and therefore
+     * smtp_session_alloc() will never fail?!?
      */
-#define NO_FLAGS       0
-
-    session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), dest, host,
-                                addr, port, (time_t) 0, NO_FLAGS);
+    session = smtp_session_alloc(NO_DSN_BUF, dest, host, addr, port,
+                                SMTP_MISC_FLAG_NO_TLS);
+    session->stream = vstream_fdopen(fd, O_RDWR);
     session->features = (features | SMTP_FEATURE_FROM_CACHE);
     CACHE_THIS_SESSION_UNTIL(expire_time);
     session->reuse_count = ++reuse_count;
index f127a803a4fa7a7685dee32a5e019e3c7d44e7ce..c715ce9986ba1cccb4952058f97da9aa588a2fd7 100644 (file)
@@ -8,7 +8,8 @@
 /*
 /*     void    smtp_tls_list_init()
 /*
-/*     SMTP_TLS_SESS *smtp_tls_sess_alloc(dest, host, port, valid)
+/*     SMTP_TLS_SESS *smtp_tls_sess_alloc(why, dest, host, port, valid)
+/*     DSN_BUF *why;
 /*     char    *dest;
 /*     char    *host;
 /*     unsigned port;
 /*     policy engine.
 /*
 /*     smtp_tls_sess_alloc() allocates memory for an SMTP_TLS_SESS structure
-/*     and initializes it based on the given information.  NOTE: the
-/*     port is in network byte order.
+/*     and initializes it based on the given information.  Any required
+/*     table and DNS lookups can fail.  When this happens, "why" is updated
+/*     with the error reason and a null pointer is returned.  NOTE: the
+/*     port is in network byte order.  If "dest" is null, no policy checks are
+/*     made, rather a trivial policy with tls disabled is returned.
 /*
 /*     smtp_tls_sess_free() destroys an SMTP_TLS_SESS structure and its
 /*     members.  A null pointer is returned for convenience.
@@ -126,8 +130,9 @@ static const char *policy_name(int tls_level)
 
 /* tls_site_lookup - look up per-site TLS security level */
 
-static void tls_site_lookup(int *site_level, const char *site_name,
-                                   const char *site_class)
+static void tls_site_lookup(SMTP_TLS_SESS *tls, int *site_level,
+                             const char *site_name, const char *site_class,
+                                   DSN_BUF *why)
 {
     const char *lookup;
 
@@ -154,19 +159,27 @@ static void tls_site_lookup(int *site_level, const char *site_name,
            if (*site_level < TLS_LEV_VERIFY)
                *site_level = TLS_LEV_VERIFY;
        } else {
-           msg_warn("Table %s: ignoring unknown TLS policy '%s' for %s %s",
-                    var_smtp_tls_per_site, lookup, site_class, site_name);
+           msg_warn("%s: unknown TLS policy '%s' for %s %s",
+                    tls_per_site->title, lookup, site_class, site_name);
+           dsb_simple(why, "4.7.5", "client TLS configuration problem");
+           *site_level = TLS_LEV_INVALID;
+           return;
        }
     } else if (tls_per_site->error) {
-       msg_fatal("%s lookup error for %s", tls_per_site->title, site_name);
+       msg_warn("%s: %s \"%s\": per-site table lookup error",
+                tls_per_site->title, site_class, site_name);
+       dsb_simple(why, "4.3.0", "Temporary lookup error");
+       *site_level = TLS_LEV_INVALID;
+       return;
     }
+    return;
 }
 
 /* tls_policy_lookup_one - look up destination TLS policy */
 
-static int tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
-                                        const char *site_name,
-                                        const char *site_class)
+static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
+                                         const char *site_name,
+                                      const char *site_class, DSN_BUF *why)
 {
     const char *lookup;
     char   *policy;
@@ -178,35 +191,37 @@ static int tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
     static VSTRING *cbuf;
 
 #undef FREE_RETURN
-#define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0)
+#define FREE_RETURN do { myfree(saved_policy); return; } while (0)
+
+#define WHERE \
+    vstring_str(vstring_sprintf(cbuf, "%s, %s \"%s\"", \
+               tls_policy->title, site_class, site_name))
+
+    if (cbuf == 0)
+       cbuf = vstring_alloc(10);
 
     if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
        if (tls_policy->error) {
-           msg_fatal("%s lookup error for %s",
-                     tls_policy->title, site_name);
-           /* XXX session->stream has no longjmp context yet. */
+           msg_warn("%s: policy table lookup error", WHERE);
+           dsb_simple(why, "4.3.0", "Temporary lookup error");
+           *site_level = TLS_LEV_INVALID;
        }
-       return (0);
+       return;
     }
-    if (cbuf == 0)
-       cbuf = vstring_alloc(10);
-
-#define WHERE \
-    vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \
-               site_class, site_name))
-
     saved_policy = policy = mystrdup(lookup);
 
     if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) {
        msg_warn("%s: invalid empty policy", WHERE);
+       dsb_simple(why, "4.7.5", "client TLS configuration problem");
        *site_level = TLS_LEV_INVALID;
-       FREE_RETURN(1);                         /* No further lookups */
+       FREE_RETURN;
     }
     *site_level = tls_level_lookup(tok);
     if (*site_level == TLS_LEV_INVALID) {
        /* tls_level_lookup() logs no warning. */
        msg_warn("%s: invalid security level \"%s\"", WHERE, tok);
-       FREE_RETURN(1);                         /* No further lookups */
+       dsb_simple(why, "4.7.5", "client TLS configuration problem");
+       FREE_RETURN;
     }
 
     /*
@@ -216,7 +231,7 @@ static int tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
        while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0)
            msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
                     WHERE, tok);
-       FREE_RETURN(1);
+       FREE_RETURN;
     }
 
     /*
@@ -225,23 +240,26 @@ static int tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
      */
     while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) {
        if ((err = split_nameval(tok, &name, &val)) != 0) {
-           *site_level = TLS_LEV_INVALID;
            msg_warn("%s: malformed attribute/value pair \"%s\": %s",
                     WHERE, tok, err);
-           break;
+           dsb_simple(why, "4.7.5", "client TLS configuration problem");
+           *site_level = TLS_LEV_INVALID;
+           FREE_RETURN;
        }
        /* Only one instance per policy. */
        if (!strcasecmp(name, "ciphers")) {
            if (*val == 0) {
                msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+               dsb_simple(why, "4.7.5", "client TLS configuration problem");
                *site_level = TLS_LEV_INVALID;
-               break;
+               FREE_RETURN;
            }
            if (tls->grade) {
                msg_warn("%s: attribute \"%s\" is specified multiple times",
                         WHERE, name);
+               dsb_simple(why, "4.7.5", "client TLS configuration problem");
                *site_level = TLS_LEV_INVALID;
-               break;
+               FREE_RETURN;
            }
            tls->grade = mystrdup(val);
            continue;
@@ -251,8 +269,9 @@ static int tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
            if (tls->protocols) {
                msg_warn("%s: attribute \"%s\" is specified multiple times",
                         WHERE, name);
+               dsb_simple(why, "4.7.5", "client TLS configuration problem");
                *site_level = TLS_LEV_INVALID;
-               break;
+               FREE_RETURN;
            }
            tls->protocols = mystrdup(val);
            continue;
@@ -264,13 +283,15 @@ static int tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
            if (*site_level <= TLS_LEV_ENCRYPT) {
                msg_warn("%s: attribute \"%s\" invalid at security level "
                         "\"%s\"", WHERE, name, policy_name(*site_level));
+               dsb_simple(why, "4.7.5", "client TLS configuration problem");
                *site_level = TLS_LEV_INVALID;
-               break;
+               FREE_RETURN;
            }
            if (*val == 0) {
                msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+               dsb_simple(why, "4.7.5", "client TLS configuration problem");
                *site_level = TLS_LEV_INVALID;
-               break;
+               FREE_RETURN;
            }
            if (tls->matchargv == 0)
                tls->matchargv = argv_split(val, delim);
@@ -283,25 +304,27 @@ static int tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
            if (tls->exclusions) {
                msg_warn("%s: attribute \"%s\" is specified multiple times",
                         WHERE, name);
+               dsb_simple(why, "4.7.5", "client TLS configuration problem");
                *site_level = TLS_LEV_INVALID;
-               break;
+               FREE_RETURN;
            }
            tls->exclusions = vstring_strcpy(vstring_alloc(10), val);
            continue;
-       } else {
-           msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
-           *site_level = TLS_LEV_INVALID;
-           break;
        }
+       msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
+       dsb_simple(why, "4.7.5", "client TLS configuration problem");
+       *site_level = TLS_LEV_INVALID;
+       FREE_RETURN;
     }
-    FREE_RETURN(1);
+
+    FREE_RETURN;
 }
 
 /* tls_policy_lookup - look up destination TLS policy */
 
 static void tls_policy_lookup(SMTP_TLS_SESS *tls, int *site_level,
                                      const char *site_name,
-                                     const char *site_class)
+                                     const char *site_class, DSN_BUF *why)
 {
 
     /*
@@ -312,22 +335,13 @@ static void tls_policy_lookup(SMTP_TLS_SESS *tls, int *site_level,
      * sub-domains of the recipient domain.
      */
     if (!valid_hostname(site_name, DONT_GRIPE)) {
-       tls_policy_lookup_one(tls, site_level, site_name, site_class);
+       tls_policy_lookup_one(tls, site_level, site_name, site_class, why);
        return;
     }
-
-    /*
-     * XXX For clarity consider using ``do { .. } while'', instead of using
-     * ``while { .. }'' with loop control at the bottom.
-     */
-    while (1) {
-       /* Try the given domain */
-       if (tls_policy_lookup_one(tls, site_level, site_name, site_class))
-           return;
-       /* Re-try with parent domain */
-       if ((site_name = strchr(site_name + 1, '.')) == 0)
-           return;
-    }
+    do {
+       tls_policy_lookup_one(tls, site_level, site_name, site_class, why);
+    } while (*site_level == TLS_LEV_NOTFOUND
+            && (site_name = strchr(site_name + 1, '.')) != 0);
 }
 
 /* set_cipher_grade - Set cipher grade and exclusions */
@@ -390,10 +404,10 @@ static void set_cipher_grade(SMTP_TLS_SESS *tls)
 
 /* smtp_tls_sess_alloc - session TLS policy parameters */
 
-SMTP_TLS_SESS *smtp_tls_sess_alloc(const char *dest, const char *host,
-                                          unsigned port, int valid)
+SMTP_TLS_SESS *smtp_tls_sess_alloc(DSN_BUF *why, const char *dest,
+                                const char *host, unsigned port, int valid)
 {
-    const char *myname = "session_tls_init";
+    const char *myname = "smtp_tls_sess_alloc";
     int     global_level;
     int     site_level;
     SMTP_TLS_SESS *tls = (SMTP_TLS_SESS *) mymalloc(sizeof(*tls));
@@ -404,6 +418,9 @@ SMTP_TLS_SESS *smtp_tls_sess_alloc(const char *dest, const char *host,
     tls->exclusions = 0;
     tls->matchargv = 0;
 
+    if (!dest)
+       return (tls);
+
     /*
      * Compute the global TLS policy. This is the default policy level when
      * no per-site policy exists. It also is used to override a wild-card
@@ -433,13 +450,12 @@ SMTP_TLS_SESS *smtp_tls_sess_alloc(const char *dest, const char *host,
     site_level = TLS_LEV_NOTFOUND;
 
     if (tls_policy) {
-       tls_policy_lookup(tls, &site_level, dest, "next-hop destination");
+       tls_policy_lookup(tls, &site_level, dest, "next-hop destination", why);
     } else if (tls_per_site) {
-       tls_site_lookup(&site_level, dest, "next-hop destination");
-       if (strcasecmp(dest, host) != 0)
-           tls_site_lookup(&site_level, host, "server hostname");
-       if (msg_verbose)
-           msg_info("%s TLS level: %s", "site", policy_name(site_level));
+       tls_site_lookup(tls, &site_level, dest, "next-hop destination", why);
+       if (site_level != TLS_LEV_INVALID
+           && strcasecmp(dest, host) != 0)
+           tls_site_lookup(tls, &site_level, host, "server hostname", why);
 
        /*
         * Override a wild-card per-site policy with a more specific global
@@ -460,10 +476,16 @@ SMTP_TLS_SESS *smtp_tls_sess_alloc(const char *dest, const char *host,
        if (site_level == TLS_LEV_MAY && global_level > TLS_LEV_MAY)
            site_level = global_level;
     }
-    if (site_level == TLS_LEV_NOTFOUND)
+    switch (site_level) {
+    case TLS_LEV_INVALID:
+       return (smtp_tls_sess_free(tls));
+    case TLS_LEV_NOTFOUND:
        tls->level = global_level;
-    else
+       break;
+    default:
        tls->level = site_level;
+       break;
+    }
 
     /*
      * Use main.cf protocols setting if not set in per-destination table.