]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-2.11-20130331
authorWietse Venema <wietse@porcupine.org>
Sun, 31 Mar 2013 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Sat, 6 Apr 2013 03:31:58 +0000 (23:31 -0400)
17 files changed:
postfix/.indent.pro
postfix/HISTORY
postfix/src/global/mail_version.h
postfix/src/smtp/Makefile.in
postfix/src/smtp/smtp.h
postfix/src/smtp/smtp_connect.c
postfix/src/smtp/smtp_proto.c
postfix/src/smtp/smtp_reuse.c
postfix/src/smtp/smtp_session.c
postfix/src/smtp/smtp_tls_sess.c [new file with mode: 0644]
postfix/src/tls/tls.h
postfix/src/tls/tls_client.c
postfix/src/tls/tls_fprint.c
postfix/src/tls/tls_misc.c
postfix/src/tls/tls_verify.c
postfix/src/util/iostuff.h
postfix/src/util/poll_fd.c

index 41daed59edf382645e302d6f1a356d6aa2e7b244..8979235c9f5bc8ddbacf87d8a64554b3b7f01a52 100644 (file)
 -TSMTP_SASL_AUTH_CACHE
 -TSMTP_SESSION
 -TSMTP_STATE
+-TSMTP_TLS_SESS
 -TSMTP_TLS_SITE_POLICY
 -TSM_STATE
 -TSOCKADDR_SIZE
index 6ef1a2bdc8376af516e242296a4dd3be2b35cec8..ec8a2b775433bb22c23a3414f776f82206747a47 100644 (file)
@@ -18383,3 +18383,20 @@ Apologies for any names omitted.
 
        Export tls_fprint() and tls_digest_encode() for use in DANE.
        Viktor Dukhovni. Files: src/tls/tls.h, src/tls/tls_fprint.c.
+
+20130331
+
+       Refactoring: TLS verification callback processing in
+       preparation for DANE support. Viktor Dukhovni. Files:
+       src/tls/tls.h, src/tls/tls_client.c, src/tls/tls_misc.c,
+       src/tls/tls_verify.c.
+
+       Refactoring: split off SMTP client per-session TLS policy
+       data and code in preparation for DANE support.  Viktor
+       Dukhovni.  Files: src/smtp/Makefile.in, src/smtp/smtp.h,
+       src/smtp/smtp_connect.c, src/smtp/smtp_proto.c,
+       src/smtp/smtp_reuse.c, src/smtp/smtp_session.c,
+       src/smtp/smtp_tls_sess.c.
+
+       Cleanup: "zero time limit" corner case in read_wait() and
+       write_wait() emulation. Files: util/poll_fd.c, util/iostuff.h.
index 78e976e072b22c2f12f6f19416cdef736a355bc5..85a1359a41b4e5f77ded1b9610babdbffb52556b 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      "20130327"
+#define MAIL_RELEASE_DATE      "20130331"
 #define MAIL_VERSION_NUMBER    "2.11"
 
 #ifdef SNAPSHOT
index ef017fdcce5e1c57df6bcbdb03523b2511e86536..98ba32cbfceb1e8159763a4961e0726b9ea9ebbb 100644 (file)
@@ -1,10 +1,10 @@
 SHELL  = /bin/sh
 SRCS   = smtp.c smtp_connect.c smtp_proto.c smtp_chat.c smtp_session.c \
-       smtp_addr.c smtp_trouble.c smtp_state.c smtp_rcpt.c \
+       smtp_addr.c smtp_trouble.c smtp_state.c smtp_rcpt.c smtp_tls_sess.c \
        smtp_sasl_proto.c smtp_sasl_glue.c smtp_reuse.c smtp_map11.c \
        smtp_sasl_auth_cache.c
 OBJS   = smtp.o smtp_connect.o smtp_proto.o smtp_chat.o smtp_session.o \
-       smtp_addr.o smtp_trouble.o smtp_state.o smtp_rcpt.o \
+       smtp_addr.o smtp_trouble.o smtp_state.o smtp_rcpt.o smtp_tls_sess.o \
        smtp_sasl_proto.o smtp_sasl_glue.o smtp_reuse.o smtp_map11.o \
        smtp_sasl_auth_cache.o
 HDRS   = smtp.h smtp_sasl.h smtp_addr.h smtp_reuse.h smtp_sasl_auth_cache.h
@@ -591,6 +591,39 @@ smtp_state.o: ../../include/vstring.h
 smtp_state.o: smtp.h
 smtp_state.o: smtp_sasl.h
 smtp_state.o: smtp_state.c
+smtp_tls_sess.o: ../../include/argv.h
+smtp_tls_sess.o: ../../include/attr.h
+smtp_tls_sess.o: ../../include/deliver_request.h
+smtp_tls_sess.o: ../../include/dict.h
+smtp_tls_sess.o: ../../include/dsn.h
+smtp_tls_sess.o: ../../include/dsn_buf.h
+smtp_tls_sess.o: ../../include/header_body_checks.h
+smtp_tls_sess.o: ../../include/header_opts.h
+smtp_tls_sess.o: ../../include/htable.h
+smtp_tls_sess.o: ../../include/mail_params.h
+smtp_tls_sess.o: ../../include/maps.h
+smtp_tls_sess.o: ../../include/match_list.h
+smtp_tls_sess.o: ../../include/mime_state.h
+smtp_tls_sess.o: ../../include/msg.h
+smtp_tls_sess.o: ../../include/msg_stats.h
+smtp_tls_sess.o: ../../include/myflock.h
+smtp_tls_sess.o: ../../include/mymalloc.h
+smtp_tls_sess.o: ../../include/name_code.h
+smtp_tls_sess.o: ../../include/name_mask.h
+smtp_tls_sess.o: ../../include/recipient_list.h
+smtp_tls_sess.o: ../../include/resolve_clnt.h
+smtp_tls_sess.o: ../../include/scache.h
+smtp_tls_sess.o: ../../include/string_list.h
+smtp_tls_sess.o: ../../include/stringops.h
+smtp_tls_sess.o: ../../include/sys_defs.h
+smtp_tls_sess.o: ../../include/tls.h
+smtp_tls_sess.o: ../../include/tok822.h
+smtp_tls_sess.o: ../../include/valid_hostname.h
+smtp_tls_sess.o: ../../include/vbuf.h
+smtp_tls_sess.o: ../../include/vstream.h
+smtp_tls_sess.o: ../../include/vstring.h
+smtp_tls_sess.o: smtp.h
+smtp_tls_sess.o: smtp_tls_sess.c
 smtp_trouble.o: ../../include/argv.h
 smtp_trouble.o: ../../include/attr.h
 smtp_trouble.o: ../../include/bounce.h
index cef94edb98693a644087ea5e278da31c2c47735d..03dbf926bf403f34d2c5a8aa4ffcbcb8767197d6 100644 (file)
@@ -200,6 +200,17 @@ extern HBC_CHECKS *smtp_body_checks;       /* limited body checks */
  /*
   * smtp_session.c
   */
+#ifdef USE_TLS
+typedef struct SMTP_TLS_SESS {
+    int     level;                     /* TLS enforcement level */
+    char   *protocols;                 /* Acceptable SSL protocols */
+    char   *grade;                     /* Cipher grade: "export", ... */
+    VSTRING *exclusions;               /* Excluded SSL ciphers */
+    ARGV   *matchargv;                 /* Cert match patterns */
+} SMTP_TLS_SESS;
+
+#endif
+
 typedef struct SMTP_SESSION {
     VSTREAM *stream;                   /* network connection */
     char   *dest;                      /* nexthop or fallback */
@@ -239,14 +250,10 @@ typedef struct SMTP_SESSION {
      * TLS related state, don't forget to initialize in session_tls_init()!
      */
 #ifdef USE_TLS
-    TLS_SESS_STATE *tls_context;       /* TLS session state */
+    TLS_SESS_STATE *tls_context;       /* TLS library session state */
     char   *tls_nexthop;               /* Nexthop domain for cert checks */
-    int     tls_level;                 /* TLS enforcement level */
     int     tls_retry_plain;           /* Try plain when TLS handshake fails */
-    char   *tls_protocols;             /* Acceptable SSL protocols */
-    char   *tls_grade;                 /* Cipher grade: "export", ... */
-    VSTRING *tls_exclusions;           /* Excluded SSL ciphers */
-    ARGV   *tls_matchargv;             /* Cert match patterns */
+    SMTP_TLS_SESS *tls;                        /* SMTP session TLS policy */
 #endif
 
     SMTP_STATE *state;                 /* back link */
@@ -261,6 +268,13 @@ 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 *,
+                                                 unsigned, int);
+extern SMTP_TLS_SESS *smtp_tls_sess_free(SMTP_TLS_SESS *);
+
 #endif
 
  /*
index c049742064e134f3294f394f8987d749b3461bd3..21b5e42529cfd5121d74fb042230283a88434f43 100644 (file)
@@ -520,10 +520,10 @@ static void smtp_connect_local(SMTP_STATE *state, const char *path)
        session->state = state;
 #ifdef USE_TLS
        session->tls_nexthop = var_myhostname;  /* for TLS_LEV_SECURE */
-       if (session->tls_level == TLS_LEV_MAY) {
+       if (session->tls->level == TLS_LEV_MAY) {
            msg_warn("%s: opportunistic TLS encryption is not appropriate "
                     "for unix-domain destinations.", myname);
-           session->tls_level = TLS_LEV_NONE;
+           session->tls->level = TLS_LEV_NONE;
        }
 #endif
        /* All delivery errors bounce or defer. */
@@ -902,9 +902,9 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
 #ifdef USE_TLS
                /* Disable TLS when retrying after a handshake failure */
                if (retry_plain) {
-                   if (session->tls_level >= TLS_LEV_ENCRYPT)
+                   if (session->tls->level >= TLS_LEV_ENCRYPT)
                        msg_panic("Plain-text retry wrong for mandatory TLS");
-                   session->tls_level = TLS_LEV_NONE;
+                   session->tls->level = TLS_LEV_NONE;
                    retry_plain = 0;
                }
                session->tls_nexthop = domain;  /* for TLS_LEV_SECURE */
index aa3fe40806353d64fa7c7ee3f9068d8883ccf42d..929c74a670871102221d7a653699329ffd8a9e6e 100644 (file)
@@ -329,7 +329,7 @@ int     smtp_helo(SMTP_STATE *state)
         * now.
         */
 #ifdef USE_TLS
-       if (session->tls_level == TLS_LEV_INVALID)
+       if (session->tls->level == TLS_LEV_INVALID)
            /* Warning is already logged. */
            return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
                                   SMTP_RESP_FAKE(&fake, "4.7.0"),
@@ -638,14 +638,14 @@ int     smtp_helo(SMTP_STATE *state)
         */
        if ((session->features & SMTP_FEATURE_STARTTLS) &&
            var_smtp_tls_note_starttls_offer &&
-           session->tls_level <= TLS_LEV_NONE)
+           session->tls->level <= TLS_LEV_NONE)
            msg_info("Host offered STARTTLS: [%s]", session->host);
 
        /*
         * Decide whether or not to send STARTTLS.
         */
        if ((session->features & SMTP_FEATURE_STARTTLS) != 0
-           && smtp_tls_ctx != 0 && session->tls_level >= TLS_LEV_MAY) {
+           && smtp_tls_ctx != 0 && session->tls->level >= TLS_LEV_MAY) {
 
            /*
             * Prepare for disaster.
@@ -685,7 +685,7 @@ int     smtp_helo(SMTP_STATE *state)
             * although support for it was announced in the EHLO response.
             */
            session->features &= ~SMTP_FEATURE_STARTTLS;
-           if (session->tls_level >= TLS_LEV_ENCRYPT)
+           if (session->tls->level >= TLS_LEV_ENCRYPT)
                return (smtp_site_fail(state, session->host, resp,
                    "TLS is required, but host %s refused to start TLS: %s",
                                       session->namaddr,
@@ -700,7 +700,7 @@ int     smtp_helo(SMTP_STATE *state)
         * block. When TLS is required we must never, ever, end up in
         * plain-text mode.
         */
-       if (session->tls_level >= TLS_LEV_ENCRYPT) {
+       if (session->tls->level >= TLS_LEV_ENCRYPT) {
            if (!(session->features & SMTP_FEATURE_STARTTLS)) {
                return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
                                       SMTP_RESP_FAKE(&fake, "4.7.4"),
@@ -797,17 +797,17 @@ static int smtp_start_tls(SMTP_STATE *state)
                         ctx = smtp_tls_ctx,
                         stream = session->stream,
                         timeout = var_smtp_starttls_tmout,
-                        tls_level = session->tls_level,
+                        tls_level = session->tls->level,
                         nexthop = session->tls_nexthop,
                         host = session->host,
                         namaddr = session->namaddrport,
                         serverid = vstring_str(serverid),
                         helo = session->helo,
-                        protocols = session->tls_protocols,
-                        cipher_grade = session->tls_grade,
+                        protocols = session->tls->protocols,
+                        cipher_grade = session->tls->grade,
                         cipher_exclusions
-                        = vstring_str(session->tls_exclusions),
-                        matchargv = session->tls_matchargv,
+                        = vstring_str(session->tls->exclusions),
+                        matchargv = session->tls->matchargv,
                         mdalg = var_smtp_tls_fpt_dgst);
     vstring_free(serverid);
 
@@ -829,7 +829,7 @@ static int smtp_start_tls(SMTP_STATE *state)
         * plaintext connections, then we don't want delivery to fail with
         * "relay access denied".
         */
-       if (session->tls_level == TLS_LEV_MAY
+       if (session->tls->level == TLS_LEV_MAY
 #ifdef USE_SASL_AUTH
            && !(var_smtp_sasl_enable
                 && *var_smtp_sasl_passwd
@@ -851,12 +851,12 @@ static int smtp_start_tls(SMTP_STATE *state)
      * See src/tls/tls_level.c. Levels above encrypt require matching. Levels >=
      * verify require CA trust.
      */
-    if (session->tls_level >= TLS_LEV_VERIFY)
+    if (session->tls->level >= TLS_LEV_VERIFY)
        if (!TLS_CERT_IS_TRUSTED(session->tls_context))
            return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
                                   SMTP_RESP_FAKE(&fake, "4.7.5"),
                                   "Server certificate not trusted"));
-    if (session->tls_level >= TLS_LEV_DANE)
+    if (session->tls->level >= TLS_LEV_DANE)
        if (!TLS_CERT_IS_MATCHED(session->tls_context))
            return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
                                   SMTP_RESP_FAKE(&fake, "4.7.5"),
index 9737025df91f89cdecce70cce24414b5d0cbb8dd..108b51ddc80f36f30027b34b517c277ae4e10349 100644 (file)
@@ -198,7 +198,7 @@ static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd,
      * lookups, because we can compute the TLS policy much earlier.
      */
 #ifdef USE_TLS
-    if (session->tls_level >= TLS_LEV_ENCRYPT) {
+    if (session->tls->level >= TLS_LEV_ENCRYPT) {
        if (msg_verbose)
            msg_info("%s: skipping plain-text cached session to %s",
                     myname, label);
index 5b72402f5678207c71ee4c33fe9ae50e0d7d6fa9..8390cccac7a9a607a43cf7aa603f8794b613edfb 100644 (file)
 #include "smtp.h"
 #include "smtp_sasl.h"
 
-#ifdef USE_TLS
-
-static MAPS *tls_policy;               /* lookup table(s) */
-static MAPS *tls_per_site;             /* lookup table(s) */
-
-/* smtp_tls_list_init - initialize per-site policy lists */
-
-void    smtp_tls_list_init(void)
-{
-    if (*var_smtp_tls_policy) {
-       tls_policy = maps_create(SMTP_X(TLS_POLICY), var_smtp_tls_policy,
-                                DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
-       if (*var_smtp_tls_per_site)
-           msg_warn("%s ignored when %s is not empty.",
-                    SMTP_X(TLS_PER_SITE), SMTP_X(TLS_POLICY));
-       return;
-    }
-    if (*var_smtp_tls_per_site) {
-       tls_per_site = maps_create(SMTP_X(TLS_PER_SITE), var_smtp_tls_per_site,
-                                  DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
-    }
-}
-
-/* policy_name - printable tls policy level */
-
-static const char *policy_name(int tls_level)
-{
-    const char *name = str_tls_level(tls_level);
-
-    if (name == 0)
-       name = "unknown";
-    return name;
-}
-
-/* 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)
-{
-    const char *lookup;
-
-    /*
-     * Look up a non-default policy. In case of multiple lookup results, the
-     * precedence order is a permutation of the TLS enforcement level order:
-     * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more
-     * specific policy including NONE, otherwise we choose the stronger
-     * enforcement level.
-     */
-    if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) {
-       if (!strcasecmp(lookup, "NONE")) {
-           /* NONE overrides MAY or NOTFOUND. */
-           if (*site_level <= TLS_LEV_MAY)
-               *site_level = TLS_LEV_NONE;
-       } else if (!strcasecmp(lookup, "MAY")) {
-           /* MAY overrides NOTFOUND but not NONE. */
-           if (*site_level < TLS_LEV_NONE)
-               *site_level = TLS_LEV_MAY;
-       } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) {
-           if (*site_level < TLS_LEV_ENCRYPT)
-               *site_level = TLS_LEV_ENCRYPT;
-       } else if (!strcasecmp(lookup, "MUST")) {
-           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);
-       }
-    } else if (tls_per_site->error) {
-       msg_fatal("%s lookup error for %s", tls_per_site->title, site_name);
-    }
-}
-
-/* tls_policy_lookup_one - look up destination TLS policy */
-
-static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level,
-                                        const char *site_name,
-                                        const char *site_class)
-{
-    const char *lookup;
-    char   *policy;
-    char   *saved_policy;
-    char   *tok;
-    const char *err;
-    char   *name;
-    char   *val;
-    static VSTRING *cbuf;
-
-#undef FREE_RETURN
-#define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0)
-
-    if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
-       if (tls_policy->error) {
-           msg_fatal("%s: %s lookup error for %s",
-                     session->state->request->queue_id,
-                     tls_policy->title, site_name);
-           /* XXX session->stream has no longjmp context yet. */
-       }
-       return (0);
-    }
-    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);
-       *site_level = TLS_LEV_INVALID;
-       FREE_RETURN(1);                         /* No further lookups */
-    }
-    *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 */
-    }
-
-    /*
-     * Warn about ignored attributes when TLS is disabled.
-     */
-    if (*site_level < TLS_LEV_MAY) {
-       while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0)
-           msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
-                    WHERE, tok);
-       FREE_RETURN(1);
-    }
-
-    /*
-     * Errors in attributes may have security consequences, don't ignore
-     * errors that can degrade security.
-     */
-    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;
-       }
-       /* Only one instance per policy. */
-       if (!strcasecmp(name, "ciphers")) {
-           if (*val == 0) {
-               msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
-               *site_level = TLS_LEV_INVALID;
-               break;
-           }
-           if (session->tls_grade) {
-               msg_warn("%s: attribute \"%s\" is specified multiple times",
-                        WHERE, name);
-               *site_level = TLS_LEV_INVALID;
-               break;
-           }
-           session->tls_grade = mystrdup(val);
-           continue;
-       }
-       /* Only one instance per policy. */
-       if (!strcasecmp(name, "protocols")) {
-           if (session->tls_protocols) {
-               msg_warn("%s: attribute \"%s\" is specified multiple times",
-                        WHERE, name);
-               *site_level = TLS_LEV_INVALID;
-               break;
-           }
-           session->tls_protocols = mystrdup(val);
-           continue;
-       }
-       /* Multiple instance(s) per policy. */
-       if (!strcasecmp(name, "match")) {
-           char   *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":";
-
-           if (*site_level <= TLS_LEV_ENCRYPT) {
-               msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"",
-                        WHERE, name, policy_name(*site_level));
-               *site_level = TLS_LEV_INVALID;
-               break;
-           }
-           if (*val == 0) {
-               msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
-               *site_level = TLS_LEV_INVALID;
-               break;
-           }
-           if (session->tls_matchargv == 0)
-               session->tls_matchargv = argv_split(val, delim);
-           else
-               argv_split_append(session->tls_matchargv, val, delim);
-           continue;
-       }
-       /* Only one instance per policy. */
-       if (!strcasecmp(name, "exclude")) {
-           if (session->tls_exclusions) {
-               msg_warn("%s: attribute \"%s\" is specified multiple times",
-                        WHERE, name);
-               *site_level = TLS_LEV_INVALID;
-               break;
-           }
-           session->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;
-       }
-    }
-    FREE_RETURN(1);
-}
-
-/* tls_policy_lookup - look up destination TLS policy */
-
-static void tls_policy_lookup(SMTP_SESSION *session, int *site_level,
-                                     const char *site_name,
-                                     const char *site_class)
-{
-
-    /*
-     * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These
-     * are never the domain part of localpart@domain, rather they are
-     * explicit nexthops from transport:nexthop, and match only the
-     * corresponding policy. Parent domain matching (below) applies only to
-     * sub-domains of the recipient domain.
-     */
-    if (!valid_hostname(site_name, DONT_GRIPE)) {
-       tls_policy_lookup_one(session, site_level, site_name, site_class);
-       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(session, site_level, site_name, site_class))
-           return;
-       /* Re-try with parent domain */
-       if ((site_name = strchr(site_name + 1, '.')) == 0)
-           return;
-    }
-}
-
-/* set_cipher_grade - Set cipher grade and exclusions */
-
-static void set_cipher_grade(SMTP_SESSION *session)
-{
-    const char *mand_exclude = "";
-    const char *also_exclude = "";
-
-    /*
-     * Use main.cf cipher level if no per-destination value specified. With
-     * mandatory encryption at least encrypt, and with mandatory verification
-     * at least authenticate!
-     */
-    switch (session->tls_level) {
-    case TLS_LEV_INVALID:
-    case TLS_LEV_NONE:
-       return;
-
-    case TLS_LEV_MAY:
-       if (session->tls_grade == 0)
-           session->tls_grade = mystrdup(var_smtp_tls_ciph);
-       break;
-
-    case TLS_LEV_ENCRYPT:
-       if (session->tls_grade == 0)
-           session->tls_grade = mystrdup(var_smtp_tls_mand_ciph);
-       mand_exclude = var_smtp_tls_mand_excl;
-       also_exclude = "eNULL";
-       break;
-
-    case TLS_LEV_FPRINT:
-    case TLS_LEV_VERIFY:
-    case TLS_LEV_SECURE:
-       if (session->tls_grade == 0)
-           session->tls_grade = mystrdup(var_smtp_tls_mand_ciph);
-       mand_exclude = var_smtp_tls_mand_excl;
-       also_exclude = "aNULL";
-       break;
-    }
-
-#define ADD_EXCLUDE(vstr, str) \
-    do { \
-       if (*(str)) \
-           vstring_sprintf_append((vstr), "%s%s", \
-                                  VSTRING_LEN(vstr) ? " " : "", (str)); \
-    } while (0)
-
-    /*
-     * The "exclude" policy table attribute overrides main.cf exclusion
-     * lists.
-     */
-    if (session->tls_exclusions == 0) {
-       session->tls_exclusions = vstring_alloc(10);
-       ADD_EXCLUDE(session->tls_exclusions, var_smtp_tls_excl_ciph);
-       ADD_EXCLUDE(session->tls_exclusions, mand_exclude);
-    }
-    ADD_EXCLUDE(session->tls_exclusions, also_exclude);
-}
-
-/* session_tls_init - session TLS parameters */
-
-static void session_tls_init(SMTP_SESSION *session, const char *dest,
-                                    const char *host, int flags)
-{
-    const char *myname = "session_tls_init";
-    int     global_level;
-    int     site_level;
-
-    /*
-     * Initialize all TLS related session properties.
-     */
-    session->tls_context = 0;
-    session->tls_nexthop = 0;
-    session->tls_level = TLS_LEV_NONE;
-    session->tls_retry_plain = 0;
-    session->tls_protocols = 0;
-    session->tls_grade = 0;
-    session->tls_exclusions = 0;
-    session->tls_matchargv = 0;
-
-    /*
-     * 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
-     * per-site policy.
-     */
-    if (*var_smtp_tls_level) {
-       /* Require that var_smtp_tls_level is sanitized upon startup. */
-       global_level = tls_level_lookup(var_smtp_tls_level);
-       if (global_level == TLS_LEV_INVALID)
-           msg_panic("%s: invalid TLS security level: \"%s\"",
-                     myname, var_smtp_tls_level);
-    } else if (var_smtp_enforce_tls) {
-       global_level = var_smtp_tls_enforce_peername ?
-           TLS_LEV_VERIFY : TLS_LEV_ENCRYPT;
-    } else {
-       global_level = var_smtp_use_tls ?
-           TLS_LEV_MAY : TLS_LEV_NONE;
-    }
-    if (msg_verbose)
-       msg_info("%s TLS level: %s", "global", policy_name(global_level));
-
-    /*
-     * Compute the per-site TLS enforcement level. For compatibility with the
-     * original TLS patch, this algorithm is gives equal precedence to host
-     * and next-hop policies.
-     */
-    site_level = TLS_LEV_NOTFOUND;
-
-    if (tls_policy) {
-       tls_policy_lookup(session, &site_level, dest, "next-hop destination");
-    } 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));
-
-       /*
-        * Override a wild-card per-site policy with a more specific global
-        * policy.
-        * 
-        * With the original TLS patch, 1) a per-site ENCRYPT could not override
-        * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy
-        * produced inconsistent results: it changed a global VERIFY into
-        * NONE, while producing MAY with all weaker global policy settings.
-        * 
-        * With the current implementation, a combined per-site (NONE+MAY)
-        * consistently overrides global policy with NONE, and global policy
-        * can override only a per-site MAY wildcard. That is, specific
-        * policies consistently override wildcard policies, and
-        * (non-wildcard) per-site policies consistently override global
-        * policies.
-        */
-       if (site_level == TLS_LEV_MAY && global_level > TLS_LEV_MAY)
-           site_level = global_level;
-    }
-    if (site_level == TLS_LEV_NOTFOUND)
-       session->tls_level = global_level;
-    else
-       session->tls_level = site_level;
-
-    /*
-     * Use main.cf protocols setting if not set in per-destination table.
-     */
-    if (session->tls_level > TLS_LEV_NONE && session->tls_protocols == 0)
-       session->tls_protocols =
-           mystrdup((session->tls_level == TLS_LEV_MAY) ?
-                    var_smtp_tls_proto : var_smtp_tls_mand_proto);
-
-    /*
-     * Compute cipher grade (if set in per-destination table, else
-     * set_cipher() uses main.cf settings) and security level dependent
-     * cipher exclusion list.
-     */
-    set_cipher_grade(session);
-
-    /*
-     * Use main.cf cert_match setting if not set in per-destination table.
-     */
-    if (session->tls_matchargv == 0) {
-       switch (session->tls_level) {
-       case TLS_LEV_INVALID:
-       case TLS_LEV_NONE:
-       case TLS_LEV_MAY:
-       case TLS_LEV_ENCRYPT:
-           break;
-       case TLS_LEV_FPRINT:
-           session->tls_matchargv =
-               argv_split(var_smtp_tls_fpt_cmatch, "\t\n\r, |");
-           break;
-       case TLS_LEV_VERIFY:
-           session->tls_matchargv =
-               argv_split(var_smtp_tls_vfy_cmatch, "\t\n\r, :");
-           break;
-       case TLS_LEV_SECURE:
-           session->tls_matchargv =
-               argv_split(var_smtp_tls_sec_cmatch, "\t\n\r, :");
-           break;
-       default:
-           msg_panic("unexpected TLS security level: %d",
-                     session->tls_level);
-       }
-    }
-    if (msg_verbose && (tls_policy || tls_per_site))
-       msg_info("%s TLS level: %s", "effective",
-                policy_name(session->tls_level));
-}
-
-#endif
-
 /* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */
 
 SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, const char *dest,
@@ -609,13 +179,12 @@ SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, const char *dest,
     smtp_sasl_connect(session);
 #endif
 
-    /*
-     * Need to pass the session as a parameter when the new-style per-nexthop
-     * policies can specify not only security level thresholds, but also how
-     * security levels are defined.
-     */
 #ifdef USE_TLS
-    session_tls_init(session, dest, host, flags);
+    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);
 #endif
     session->state = 0;
     debug_peer_check(host, addr);
@@ -633,14 +202,8 @@ void    smtp_session_free(SMTP_SESSION *session)
            tls_client_stop(smtp_tls_ctx, session->stream,
                          var_smtp_starttls_tmout, 0, session->tls_context);
     }
-    if (session->tls_protocols)
-       myfree(session->tls_protocols);
-    if (session->tls_grade)
-       myfree(session->tls_grade);
-    if (session->tls_exclusions)
-       vstring_free(session->tls_exclusions);
-    if (session->tls_matchargv)
-       argv_free(session->tls_matchargv);
+    if (session->tls)
+       (void) smtp_tls_sess_free(session->tls);
 #endif
     if (session->stream)
        vstream_fclose(session->stream);
diff --git a/postfix/src/smtp/smtp_tls_sess.c b/postfix/src/smtp/smtp_tls_sess.c
new file mode 100644 (file)
index 0000000..f127a80
--- /dev/null
@@ -0,0 +1,537 @@
+/*++
+/* NAME
+/*     smtp_tls_sess 3
+/* SUMMARY
+/*     SMTP_TLS_SESS structure management
+/* SYNOPSIS
+/*     #include "smtp.h"
+/*
+/*     void    smtp_tls_list_init()
+/*
+/*     SMTP_TLS_SESS *smtp_tls_sess_alloc(dest, host, port, valid)
+/*     char    *dest;
+/*     char    *host;
+/*     unsigned port;
+/*     int     valid;
+/*
+/*     SMTP_TLS_SESS *smtp_tls_sess_free(tls)
+/*     SMTP_TLS_SESS *tls;
+/* DESCRIPTION
+/*     smtp_tls_list_init() initializes lookup tables used by the TLS
+/*     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.
+/*
+/*     smtp_tls_sess_free() destroys an SMTP_TLS_SESS structure and its
+/*     members.  A null pointer is returned for convenience.
+/*
+/*     Arguments:
+/* .IP dest
+/*     The unmodified next-hop or fall-back destination including
+/*     the optional [] and including the optional port or service.
+/* .IP host
+/*     The name of the host that we are connected to.  If the name was
+/*     obtained via an MX lookup and/or CNAME expansion (any indirection),
+/*     all those lookups must also have been validated.  If the hostname
+/*     is validated, it must be the final name after all CNAME expansion.
+/*     Otherwise, it is generally the original name or that returned by
+/*     the MX lookup (see smtp_cname_overrides_servername).
+/* .IP port
+/*     The remote port, network byte order.
+/* .IP valid
+/*     The DNSSEC validation status of the host name.
+/* LICENSE
+/* .ad
+/* .fi
+/*     The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/*     Wietse Venema
+/*     IBM T.J. Watson Research
+/*     P.O. Box 704
+/*     Yorktown Heights, NY 10598, USA
+/*
+/*     Viktor Dukhovni
+/*
+/*     TLS support originally by:
+/*     Lutz Jaenicke
+/*     BTU Cottbus
+/*     Allgemeine Elektrotechnik
+/*     Universitaetsplatz 3-4
+/*     D-03044 Cottbus, Germany
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef USE_TLS
+
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <maps.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+
+static MAPS *tls_policy;               /* lookup table(s) */
+static MAPS *tls_per_site;             /* lookup table(s) */
+
+/* smtp_tls_list_init - initialize per-site policy lists */
+
+void    smtp_tls_list_init(void)
+{
+    if (*var_smtp_tls_policy) {
+       tls_policy = maps_create(SMTP_X(TLS_POLICY), var_smtp_tls_policy,
+                                DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+       if (*var_smtp_tls_per_site)
+           msg_warn("%s ignored when %s is not empty.",
+                    SMTP_X(TLS_PER_SITE), SMTP_X(TLS_POLICY));
+       return;
+    }
+    if (*var_smtp_tls_per_site) {
+       tls_per_site = maps_create(SMTP_X(TLS_PER_SITE), var_smtp_tls_per_site,
+                                  DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
+    }
+}
+
+/* policy_name - printable tls policy level */
+
+static const char *policy_name(int tls_level)
+{
+    const char *name = str_tls_level(tls_level);
+
+    if (name == 0)
+       name = "unknown";
+    return name;
+}
+
+/* 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)
+{
+    const char *lookup;
+
+    /*
+     * Look up a non-default policy. In case of multiple lookup results, the
+     * precedence order is a permutation of the TLS enforcement level order:
+     * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more
+     * specific policy including NONE, otherwise we choose the stronger
+     * enforcement level.
+     */
+    if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) {
+       if (!strcasecmp(lookup, "NONE")) {
+           /* NONE overrides MAY or NOTFOUND. */
+           if (*site_level <= TLS_LEV_MAY)
+               *site_level = TLS_LEV_NONE;
+       } else if (!strcasecmp(lookup, "MAY")) {
+           /* MAY overrides NOTFOUND but not NONE. */
+           if (*site_level < TLS_LEV_NONE)
+               *site_level = TLS_LEV_MAY;
+       } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) {
+           if (*site_level < TLS_LEV_ENCRYPT)
+               *site_level = TLS_LEV_ENCRYPT;
+       } else if (!strcasecmp(lookup, "MUST")) {
+           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);
+       }
+    } else if (tls_per_site->error) {
+       msg_fatal("%s lookup error for %s", tls_per_site->title, site_name);
+    }
+}
+
+/* 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)
+{
+    const char *lookup;
+    char   *policy;
+    char   *saved_policy;
+    char   *tok;
+    const char *err;
+    char   *name;
+    char   *val;
+    static VSTRING *cbuf;
+
+#undef FREE_RETURN
+#define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0)
+
+    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. */
+       }
+       return (0);
+    }
+    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);
+       *site_level = TLS_LEV_INVALID;
+       FREE_RETURN(1);                         /* No further lookups */
+    }
+    *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 */
+    }
+
+    /*
+     * Warn about ignored attributes when TLS is disabled.
+     */
+    if (*site_level < TLS_LEV_MAY) {
+       while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0)
+           msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
+                    WHERE, tok);
+       FREE_RETURN(1);
+    }
+
+    /*
+     * Errors in attributes may have security consequences, don't ignore
+     * errors that can degrade security.
+     */
+    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;
+       }
+       /* Only one instance per policy. */
+       if (!strcasecmp(name, "ciphers")) {
+           if (*val == 0) {
+               msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+               *site_level = TLS_LEV_INVALID;
+               break;
+           }
+           if (tls->grade) {
+               msg_warn("%s: attribute \"%s\" is specified multiple times",
+                        WHERE, name);
+               *site_level = TLS_LEV_INVALID;
+               break;
+           }
+           tls->grade = mystrdup(val);
+           continue;
+       }
+       /* Only one instance per policy. */
+       if (!strcasecmp(name, "protocols")) {
+           if (tls->protocols) {
+               msg_warn("%s: attribute \"%s\" is specified multiple times",
+                        WHERE, name);
+               *site_level = TLS_LEV_INVALID;
+               break;
+           }
+           tls->protocols = mystrdup(val);
+           continue;
+       }
+       /* Multiple instances per policy. */
+       if (!strcasecmp(name, "match")) {
+           char   *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":";
+
+           if (*site_level <= TLS_LEV_ENCRYPT) {
+               msg_warn("%s: attribute \"%s\" invalid at security level "
+                        "\"%s\"", WHERE, name, policy_name(*site_level));
+               *site_level = TLS_LEV_INVALID;
+               break;
+           }
+           if (*val == 0) {
+               msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+               *site_level = TLS_LEV_INVALID;
+               break;
+           }
+           if (tls->matchargv == 0)
+               tls->matchargv = argv_split(val, delim);
+           else
+               argv_split_append(tls->matchargv, val, delim);
+           continue;
+       }
+       /* Only one instance per policy. */
+       if (!strcasecmp(name, "exclude")) {
+           if (tls->exclusions) {
+               msg_warn("%s: attribute \"%s\" is specified multiple times",
+                        WHERE, name);
+               *site_level = TLS_LEV_INVALID;
+               break;
+           }
+           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;
+       }
+    }
+    FREE_RETURN(1);
+}
+
+/* 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)
+{
+
+    /*
+     * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These
+     * are never the domain part of localpart@domain, rather they are
+     * explicit nexthops from transport:nexthop, and match only the
+     * corresponding policy. Parent domain matching (below) applies only to
+     * sub-domains of the recipient domain.
+     */
+    if (!valid_hostname(site_name, DONT_GRIPE)) {
+       tls_policy_lookup_one(tls, site_level, site_name, site_class);
+       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;
+    }
+}
+
+/* set_cipher_grade - Set cipher grade and exclusions */
+
+static void set_cipher_grade(SMTP_TLS_SESS *tls)
+{
+    const char *mand_exclude = "";
+    const char *also_exclude = "";
+
+    /*
+     * Use main.cf cipher level if no per-destination value specified. With
+     * mandatory encryption at least encrypt, and with mandatory verification
+     * at least authenticate!
+     */
+    switch (tls->level) {
+    case TLS_LEV_INVALID:
+    case TLS_LEV_NONE:
+       return;
+
+    case TLS_LEV_MAY:
+       if (tls->grade == 0)
+           tls->grade = mystrdup(var_smtp_tls_ciph);
+       break;
+
+    case TLS_LEV_ENCRYPT:
+       if (tls->grade == 0)
+           tls->grade = mystrdup(var_smtp_tls_mand_ciph);
+       mand_exclude = var_smtp_tls_mand_excl;
+       also_exclude = "eNULL";
+       break;
+
+    case TLS_LEV_FPRINT:
+    case TLS_LEV_VERIFY:
+    case TLS_LEV_SECURE:
+       if (tls->grade == 0)
+           tls->grade = mystrdup(var_smtp_tls_mand_ciph);
+       mand_exclude = var_smtp_tls_mand_excl;
+       also_exclude = "aNULL";
+       break;
+    }
+
+#define ADD_EXCLUDE(vstr, str) \
+    do { \
+       if (*(str)) \
+           vstring_sprintf_append((vstr), "%s%s", \
+                                  VSTRING_LEN(vstr) ? " " : "", (str)); \
+    } while (0)
+
+    /*
+     * The "exclude" policy table attribute overrides main.cf exclusion
+     * lists.
+     */
+    if (tls->exclusions == 0) {
+       tls->exclusions = vstring_alloc(10);
+       ADD_EXCLUDE(tls->exclusions, var_smtp_tls_excl_ciph);
+       ADD_EXCLUDE(tls->exclusions, mand_exclude);
+    }
+    ADD_EXCLUDE(tls->exclusions, also_exclude);
+}
+
+/* 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)
+{
+    const char *myname = "session_tls_init";
+    int     global_level;
+    int     site_level;
+    SMTP_TLS_SESS *tls = (SMTP_TLS_SESS *) mymalloc(sizeof(*tls));
+
+    tls->level = TLS_LEV_NONE;
+    tls->protocols = 0;
+    tls->grade = 0;
+    tls->exclusions = 0;
+    tls->matchargv = 0;
+
+    /*
+     * 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
+     * per-site policy.
+     */
+    if (*var_smtp_tls_level) {
+       /* Require that var_smtp_tls_level is sanitized upon startup. */
+       global_level = tls_level_lookup(var_smtp_tls_level);
+       if (global_level == TLS_LEV_INVALID)
+           msg_panic("%s: invalid TLS security level: \"%s\"",
+                     myname, var_smtp_tls_level);
+    } else if (var_smtp_enforce_tls) {
+       global_level = var_smtp_tls_enforce_peername ?
+           TLS_LEV_VERIFY : TLS_LEV_ENCRYPT;
+    } else {
+       global_level = var_smtp_use_tls ?
+           TLS_LEV_MAY : TLS_LEV_NONE;
+    }
+    if (msg_verbose)
+       msg_info("%s TLS level: %s", "global", policy_name(global_level));
+
+    /*
+     * Compute the per-site TLS enforcement level. For compatibility with the
+     * original TLS patch, this algorithm is gives equal precedence to host
+     * and next-hop policies.
+     */
+    site_level = TLS_LEV_NOTFOUND;
+
+    if (tls_policy) {
+       tls_policy_lookup(tls, &site_level, dest, "next-hop destination");
+    } 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));
+
+       /*
+        * Override a wild-card per-site policy with a more specific global
+        * policy.
+        * 
+        * With the original TLS patch, 1) a per-site ENCRYPT could not override
+        * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy
+        * produced inconsistent results: it changed a global VERIFY into
+        * NONE, while producing MAY with all weaker global policy settings.
+        * 
+        * With the current implementation, a combined per-site (NONE+MAY)
+        * consistently overrides global policy with NONE, and global policy
+        * can override only a per-site MAY wildcard. That is, specific
+        * policies consistently override wildcard policies, and
+        * (non-wildcard) per-site policies consistently override global
+        * policies.
+        */
+       if (site_level == TLS_LEV_MAY && global_level > TLS_LEV_MAY)
+           site_level = global_level;
+    }
+    if (site_level == TLS_LEV_NOTFOUND)
+       tls->level = global_level;
+    else
+       tls->level = site_level;
+
+    /*
+     * Use main.cf protocols setting if not set in per-destination table.
+     */
+    if (tls->level > TLS_LEV_NONE && tls->protocols == 0)
+       tls->protocols =
+           mystrdup((tls->level == TLS_LEV_MAY) ?
+                    var_smtp_tls_proto : var_smtp_tls_mand_proto);
+
+    /*
+     * Compute cipher grade (if set in per-destination table, else
+     * set_cipher() uses main.cf settings) and security level dependent
+     * cipher exclusion list.
+     */
+    set_cipher_grade(tls);
+
+    /*
+     * Use main.cf cert_match setting if not set in per-destination table.
+     */
+    switch (tls->level) {
+    case TLS_LEV_INVALID:
+    case TLS_LEV_NONE:
+    case TLS_LEV_MAY:
+    case TLS_LEV_ENCRYPT:
+    case TLS_LEV_DANE:
+       break;
+    case TLS_LEV_FPRINT:
+       if (tls->matchargv == 0)
+           tls->matchargv =
+               argv_split(var_smtp_tls_fpt_cmatch, "\t\n\r, |");
+       break;
+    case TLS_LEV_VERIFY:
+       if (tls->matchargv == 0)
+           tls->matchargv =
+               argv_split(var_smtp_tls_vfy_cmatch, "\t\n\r, :");
+       break;
+    case TLS_LEV_SECURE:
+       if (tls->matchargv == 0)
+           tls->matchargv =
+               argv_split(var_smtp_tls_sec_cmatch, "\t\n\r, :");
+       break;
+    default:
+       msg_panic("unexpected TLS security level: %d",
+                 tls->level);
+    }
+
+    if (msg_verbose && (tls_policy || tls_per_site))
+       msg_info("%s TLS level: %s", "effective", policy_name(tls->level));
+
+    return (tls);
+}
+
+/* smtp_sess_tls_free - free and return null pointer of same type */
+
+SMTP_TLS_SESS *smtp_tls_sess_free(SMTP_TLS_SESS *tls)
+{
+
+    if (tls->protocols)
+       myfree(tls->protocols);
+    if (tls->grade)
+       myfree(tls->grade);
+    if (tls->exclusions)
+       vstring_free(tls->exclusions);
+    if (tls->matchargv)
+       argv_free(tls->matchargv);
+
+    myfree((char *) tls);
+    return (0);
+}
+
+#endif
index 0c65743dd66eb410d9afb7919116c5827a1a4a19..a89f456ac324c3668e9394fa8cbf745def6c2893 100644 (file)
@@ -101,6 +101,9 @@ typedef struct {
     const char *mdalg;                 /* default message digest algorithm */
     /* Built-in vs external SSL_accept/read/write/shutdown support. */
     VSTREAM *stream;                   /* Blocking-mode SMTP session */
+    int     errordepth;                        /* Chain depth of error cert */
+    int     errorcode;                 /* First error at error depth */
+    X509   *errorcert;                 /* Error certificate closest to leaf */
 } TLS_SESS_STATE;
 
  /*
@@ -400,6 +403,7 @@ extern char *tls_peer_CN(X509 *, const TLS_SESS_STATE *);
 extern char *tls_issuer_CN(X509 *, const TLS_SESS_STATE *);
 extern const char *tls_dns_name(const GENERAL_NAME *, const TLS_SESS_STATE *);
 extern int tls_verify_certificate_callback(int, X509_STORE_CTX *);
+extern void tls_log_verify_error(TLS_SESS_STATE *);
 
  /*
   * tls_fprint.c
index 19582ee6e85bfa7d5c71e919acd58d246054ae8e..2282c448903b63abc75a73380e887fffb82a73d2 100644 (file)
@@ -700,16 +700,19 @@ static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
        TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext);
 
     /*
-     * Give them a clue. Problems with trust chain verification were logged
-     * when the session was first negotiated, before the session was stored
+     * Give them a clue. Problems with trust chain verification are logged
+     * when the session is first negotiated, before the session is stored
      * into the cache. We don't want mystery failures, so log the fact the
      * real problem is to be found in the past.
      */
-    if (TLScontext->session_reused
-       && !TLS_CERT_IS_TRUSTED(TLScontext)
-       && (TLScontext->log_mask & TLS_LOG_UNTRUSTED))
-       msg_info("%s: re-using session with untrusted certificate, "
-                "look for details earlier in the log", props->namaddr);
+    if (!TLS_CERT_IS_TRUSTED(TLScontext)
+       && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+       if (TLScontext->session_reused == 0)
+           tls_log_verify_error(TLScontext);
+       else
+           msg_info("%s: re-using session with untrusted certificate, "
+                    "look for details earlier in the log", props->namaddr);
+    }
 }
 
 /* verify_extract_print - extract and verify peer fingerprint */
@@ -731,7 +734,8 @@ static void verify_extract_print(TLS_SESS_STATE *TLScontext, X509 *peercert,
        for (cpp = props->matchargv->argv; *cpp; ++cpp) {
            if (strcasecmp(TLScontext->peer_fingerprint, *cpp) == 0
                || strcasecmp(TLScontext->peer_pkey_fprint, *cpp) == 0) {
-               TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
+               TLScontext->peer_status |=
+                   TLS_CERT_FLAG_TRUSTED | TLS_CERT_FLAG_MATCHED;
                break;
            }
        }
index 88f7c03fad901c8d69b7d99b0bd5780af3cd151b..182b71b6e313349120197e8cc674688fb3855b48 100644 (file)
@@ -215,9 +215,7 @@ char   *tls_fprint(const char *buf, int len, const char *mdalg)
     const EVP_MD *md;
     unsigned char md_buf[EVP_MAX_MD_SIZE];
     unsigned int md_len;
-    int     i;
     int     ok = 1;
-    char   *result = 0;
 
     /* Previously available in "init" routine. */
     if ((md = EVP_get_digestbyname(mdalg)) == 0)
index c09e7f1899ade7acfda11b0fbd8304b91b3f3145..8143da29607bafe474861414b3d778214aee0b62 100644 (file)
@@ -747,6 +747,9 @@ TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr)
     TLScontext->log_mask = log_mask;
     TLScontext->namaddr = lowercase(mystrdup(namaddr));
     TLScontext->mdalg = 0;                     /* Alias for props->mdalg */
+    TLScontext->errordepth = -1;
+    TLScontext->errorcode = X509_V_OK;
+    TLScontext->errorcert = 0;
 
     return (TLScontext);
 }
@@ -777,6 +780,8 @@ void    tls_free_context(TLS_SESS_STATE *TLScontext)
        myfree(TLScontext->peer_fingerprint);
     if (TLScontext->peer_pkey_fprint)
        myfree(TLScontext->peer_pkey_fprint);
+    if (TLScontext->errorcert)
+       X509_free(TLScontext->errorcert);
 
     myfree((char *) TLScontext);
 }
index 50dddc8cff5e67de749177391ce9709baead698e..f3daf2a5c31a93da53465bdefe3e87e7dd6bcefa 100644 (file)
@@ -7,6 +7,13 @@
 /*     #define TLS_INTERNAL
 /*     #include <tls.h>
 /*
+/*     int     tls_verify_certificate_callback(ok, ctx)
+/*     int     ok;
+/*     X509_STORE_CTX *ctx;
+/*
+/*     int     tls_log_verify_error(TLScontext)
+/*     TLS_SESS_STATE *TLScontext;
+/*
 /*     char *tls_peer_CN(peercert, TLScontext)
 /*     X509   *peercert;
 /*     TLS_SESS_STATE *TLScontext;
 /*     const char *tls_dns_name(gn, TLScontext)
 /*     const GENERAL_NAME *gn;
 /*     TLS_SESS_STATE *TLScontext;
+/* DESCRIPTION
+/*     tls_verify_certificate_callback() is called several times (directly
+/*     or indirectly) from crypto/x509/x509_vfy.c. It collects errors
+/*     and trust information at each element of the trust chain.
+/*     The last call at depth 0 sets the verification status based
+/*     on the cumulative winner (lowest depth) of errors vs. trust.
+/*     We always return 1 (continue the handshake) and handle trust
+/*     and peer-name verification problems at the application level.
 /*
+/*     tls_log_verify_error() (called only when we care about the
+/*     peer certificate, that is not when opportunistic) logs the
+/*     reason why the certificate failed to be verified.
 /*
-/*     int     tls_verify_certificate_callback(ok, ctx)
-/*     int     ok;
-/*     X509_STORE_CTX *ctx;
-/* DESCRIPTION
 /*     tls_peer_CN() returns the text CommonName for the peer
 /*     certificate subject, or an empty string if no CommonName was
 /*     found. The result is allocated with mymalloc() and must be
 /*     are found, a null string is returned instead. Further sanity
 /*     checks may be added if the need arises.
 /*
-/*     tls_verify_callback() is called several times (directly or
-/*     indirectly) from crypto/x509/x509_vfy.c. It is called as
-/*     a final check, and if it returns "0", the handshake is
-/*     immediately shut down and the connection fails.
-/*
-/*     Postfix/TLS has two modes, the "opportunistic" mode and
-/*     the "enforce" mode:
-/*
-/*     In the "opportunistic" mode we never want the connection
-/*     to fail just because there is something wrong with the
-/*     peer's certificate. After all, we would have sent or received
-/*     the mail even if TLS weren't available.  Therefore the
-/*     return value is always "1".
-/*
-/*     The SMTP client or server may require TLS (e.g. to protect
-/*     passwords), while peer certificates are optional.  In this
-/*     case we must return "1" even when we are unhappy with the
-/*     peer certificate.  Only when peer certificates are required,
-/*      certificate verification failure will result in immediate
-/*     termination (return 0).
-/*
-/*     The only error condition not handled inside the OpenSSL
-/*     library is the case of a too-long certificate chain. We
-/*     test for this condition only if "ok = 1", that is, if
-/*     verification didn't fail because of some earlier problem.
-/*
 /*     Arguments:
 /* .IP ok
 /*     Result of prior verification: non-zero means success.  In
 #define TLS_INTERNAL
 #include <tls.h>
 
+/* update_error_state - safely stash away error state */
+
+static void update_error_state(TLS_SESS_STATE *TLScontext, int depth,
+                                      X509 *errorcert, int errorcode)
+{
+    /* No news is good news */
+    if (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth)
+       return;
+
+    /*
+     * The certificate pointer is stable during the verification callback,
+     * but may be freed after the callback returns.  Since we delay error
+     * reporting till later, we bump the refcount so we can rely on it still
+     * being there until later.
+     */
+    if (TLScontext->errorcert != 0)
+       X509_free(TLScontext->errorcert);
+    if (errorcert != 0)
+       CRYPTO_add(&errorcert->references, 1, CRYPTO_LOCK_X509);
+    TLScontext->errorcert = errorcert;
+    TLScontext->errorcode = errorcode;
+}
+
 /* tls_verify_certificate_callback - verify peer certificate info */
 
 int     tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
@@ -140,31 +151,19 @@ int     tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
     X509   *cert;
     int     err;
     int     depth;
+    int     max_depth;
     SSL    *con;
     TLS_SESS_STATE *TLScontext;
 
-    depth = X509_STORE_CTX_get_error_depth(ctx);
+    /* May be NULL as of OpenSSL 1.0, thanks for the API change! */
     cert = X509_STORE_CTX_get_current_cert(ctx);
+    err = X509_STORE_CTX_get_error(ctx);
     con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
     TLScontext = SSL_get_ex_data(con, TLScontext_index);
 
     /*
-     * The callback function is called repeatedly, first with the root
-     * certificate, and then with each intermediate certificate ending with
-     * the peer certificate.
-     * 
-     * With each call, the validity of the current certificate (usage bits,
-     * attributes, expiration, ... checked by the OpenSSL library) is
-     * available in the "ok" argument. Error details are available via
-     * X509_STORE_CTX API.
-     * 
-     * We never terminate the SSL handshake in the verification callback, rather
-     * we allow the TLS handshake to continue, but mark the session as
-     * unverified. The application is responsible for closing any sessions
-     * with unverified credentials.
-     * 
-     * Certificate chain depth limit violations are mis-reported by the OpenSSL
-     * library, from SSL_CTX_set_verify(3):
+     * Certificate chain depth limit violations are mis-reported by the
+     * OpenSSL library, from SSL_CTX_set_verify(3):
      * 
      * The certificate verification depth set with SSL[_CTX]_verify_depth()
      * stops the verification at a certain depth. The error message produced
@@ -176,33 +175,74 @@ int     tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
      * present at this depth. This disambiguates trust chain truncation from
      * an incomplete trust chain.
      */
-    if (depth >= SSL_get_verify_depth(con)) {
+    depth = X509_STORE_CTX_get_error_depth(ctx);
+    max_depth = SSL_get_verify_depth(con) - 1;
+
+    /*
+     * We never terminate the SSL handshake in the verification callback,
+     * rather we allow the TLS handshake to continue, but mark the session as
+     * unverified. The application is responsible for closing any sessions
+     * with unverified credentials.
+     * 
+     * When we have an explicit list of trusted CA fingerprints, record the
+     * smallest depth at which we find a trusted certificate. If this below
+     * the smallest error depth we win and the chain is trusted. Otherwise,
+     * the chain is untrusted. We make this decision *each* time we are
+     * called with depth == 0 (yes we may be called more than once).
+     */
+    if (max_depth >= 0 && depth > max_depth) {
+       update_error_state(TLScontext, depth, cert,
+                          X509_V_ERR_CERT_CHAIN_TOO_LONG);
        ok = 0;
-       X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG);
+    }
+    if (ok == 0)
+       update_error_state(TLScontext, depth, cert, err);
+
+    /*
+     * The final depth zero call sets the verification status.
+     */
+    if (depth == 0) {
+       ok = TLScontext->errordepth < 0 ? 1 : 0;
+       X509_STORE_CTX_set_error(ctx, ok ? X509_V_OK : TLScontext->errorcode);
     }
     if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
-       X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
-       msg_info("%s: certificate verification depth=%d verify=%d subject=%s",
+       if (cert)
+           X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
+       else
+           strcpy(buf, "<unknown>");
+       msg_info("%s: depth=%d verify=%d subject=%s",
                 TLScontext->namaddr, depth, ok, printable(buf, '?'));
     }
+    return (1);
+}
 
-    /*
-     * If no errors, or we are not logging verification errors, we are done.
-     */
-    if (ok || (TLScontext->log_mask & TLS_LOG_UNTRUSTED) == 0)
-       return (1);
+/* tls_log_verify_error - Report final verification error status */
 
-    /*
-     * One counter-example is enough.
-     */
-    TLScontext->log_mask &= ~TLS_LOG_UNTRUSTED;
+void    tls_log_verify_error(TLS_SESS_STATE *TLScontext)
+{
+    char    buf[CCERT_BUFSIZ];
+    int     err = TLScontext->errorcode;
+    X509   *cert = TLScontext->errorcert;
+    int     depth = TLScontext->errordepth;
 
 #define PURPOSE ((depth>0) ? "CA": TLScontext->am_server ? "client": "server")
 
+    if (err == X509_V_OK)
+       return;
+
     /*
      * Specific causes for verification failure.
      */
-    switch (err = X509_STORE_CTX_get_error(ctx)) {
+    switch (err) {
+    case X509_V_ERR_CERT_UNTRUSTED:
+
+       /*
+        * We expect the error cert to be the leaf, but it is likely
+        * sufficient to omit it from the log, even less user confusion.
+        */
+       msg_info("certificate verification failed for %s: "
+                "not trusted by local or TLSA policy", TLScontext->namaddr);
+       break;
     case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
        msg_info("certificate verification failed for %s: "
                 "self-signed certificate", TLScontext->namaddr);
@@ -215,8 +255,10 @@ int     tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
         * provided, but not found in CAfile/CApath. Either way, we don't
         * trust it.
         */
-       X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert),
-                         buf, sizeof(buf));
+       if (cert)
+           X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf));
+       else
+           strcpy(buf, "<unknown>");
        msg_info("certificate verification failed for %s: untrusted issuer %s",
                 TLScontext->namaddr, printable(buf, '?'));
        break;
@@ -237,7 +279,7 @@ int     tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
     case X509_V_ERR_CERT_CHAIN_TOO_LONG:
        msg_info("certificate verification failed for %s: "
                 "certificate chain longer than limit(%d)",
-                TLScontext->namaddr, SSL_get_verify_depth(con) - 1);
+                TLScontext->namaddr, depth - 1);
        break;
     default:
        msg_info("%s certificate verification failed for %s: num=%d:%s",
@@ -245,8 +287,6 @@ int     tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
                 X509_verify_cert_error_string(err));
        break;
     }
-
-    return (1);
 }
 
 #ifndef DONT_GRIPE
index da3fa3a38777c2436c058e4caf2f55404a02b986..539165d7fbae661b3692ece43d07f3cdfeb9d1e7 100644 (file)
@@ -16,7 +16,7 @@
 extern int non_blocking(int, int);
 extern int close_on_exec(int, int);
 extern int open_limit(int);
-extern int poll_fd(int, int, int, int);
+extern int poll_fd(int, int, int, int, int);
 extern off_t get_file_limit(void);
 extern void set_file_limit(off_t);
 extern ssize_t peekfd(int);
@@ -33,11 +33,11 @@ extern int unix_send_fd(int, int);
 extern ssize_t dummy_read(int, void *, size_t, int, void *);
 extern ssize_t dummy_write(int, void *, size_t, int, void *);
 
-#define readable(fd)           poll_fd((fd), POLL_FD_READ, 0, 1)
-#define writable(fd)           poll_fd((fd), POLL_FD_WRITE, 0, 1)
+#define readable(fd)           poll_fd((fd), POLL_FD_READ, 0, 1, 0)
+#define writable(fd)           poll_fd((fd), POLL_FD_WRITE, 0, 1, 0)
 
-#define read_wait(fd, time_limit) poll_fd((fd), POLL_FD_READ, (time_limit), 0)
-#define write_wait(fd, time_limit) poll_fd((fd), POLL_FD_WRITE, (time_limit), 0)
+#define read_wait(fd, timeout) poll_fd((fd), POLL_FD_READ, (timeout), 0, -1)
+#define write_wait(fd, timeout) poll_fd((fd), POLL_FD_WRITE, (timeout), 0, -1)
 
 extern int inet_windowsize;
 extern void set_inet_windowsize(int, int);
index 5b22b1289131515d241ae2c82be3f5435d800fa4..e8c7ed21f710bcb1a0986957d5cd99ab80b06126 100644 (file)
 /*     int     fd;
 /*     int     time_limit;
 /*
-/*     int     poll_fd(fd, request, time_limit, success_val)
+/*     int     poll_fd(fd, request, time_limit, true_res, false_res)
 /*     int     fd;
 /*     int     request;
 /*     int     time_limit;
-/*     int     success_val;
+/*     int     true_res;
+/*     int     false_res;
 /* DESCRIPTION
 /*     The functions in this module are macros that provide a
 /*     convenient interface to poll_fd().
 /*     value effects a poll (return immediately).  A negative value
 /*     means wait until the requested POLL_FD_READ or POLL_FD_WRITE
 /*     condition becomes true.
-/* .IP success_val
+/* .IP true_res
 /*     Result value when the requested POLL_FD_READ or POLL_FD_WRITE
 /*     condition is true.
+/* .IP false_res
+/*     Result value when the requested POLL_FD_READ or POLL_FD_WRITE
+/*     condition is false.
 /* DIAGNOSTICS
 /*     Panic: interface violation. All system call errors are fatal
 /*     unless specified otherwise.
 /*
 /*     readable() and writable() return 1 when the requested
-/*     condition is true, zero when it is false. They never return
-/*     an error indication.
+/*     POLL_FD_READ or POLL_FD_WRITE condition is true, zero when
+/*     it is false. They never return an error indication.
 /*
-/*     read_wait() and write_wait() return zero when successful,
-/*     -1 with errno set to ETIMEDOUT when the time limit was
-/*     reached.
+/*     read_wait() and write_wait() return zero when the requested
+/*     POLL_FD_READ or POLL_FD_WRITE condition is true, -1 with
+/*     errno set to ETIMEDOUT when it is false.
 /*
-/*     poll_fd() returns -1 with errno set to ETIMEDOUT when the
-/*     time limit was reached, success_val if the requested
-/*     POLL_FD_READ or POLL_FD_WRITE condition is true, and returns
-/*     zero otherwise.
+/*     poll_fd() returns true_res when the requested POLL_FD_READ
+/*     or POLL_FD_WRITE condition is true, false_res when it is
+/*     false.  When poll_fd() returns a false_res value < 0, it
+/*     also sets errno to ETIMEDOUT.
 /* LICENSE
 /* .ad
 /* .fi
 #define poll_fd_sysv   poll_fd
 #define USE_SYSV_POLL
 #define USE_BSD_SELECT
-int     poll_fd_bsd(int, int, int, int);
+int     poll_fd_bsd(int, int, int, int, int);
 
  /*
   * Use select() only.
@@ -141,7 +145,8 @@ int     poll_fd_bsd(int, int, int, int);
 
 /* poll_fd_bsd - block with time_limit until file descriptor is ready */
 
-int     poll_fd_bsd(int fd, int request, int time_limit, int success_val)
+int     poll_fd_bsd(int fd, int request, int time_limit,
+                           int true_res, int false_res)
 {
     fd_set  req_fds;
     fd_set *read_fds;
@@ -196,16 +201,13 @@ int     poll_fd_bsd(int fd, int request, int time_limit, int success_val)
        case 0:
            if (temp_fd != -1)
                (void) close(temp_fd);
-           if (time_limit == 0) {
-               return (0);
-           } else {
+           if (false_res < 0)
                errno = ETIMEDOUT;
-               return (-1);
-           }
+           return (false_res);
        default:
            if (temp_fd != -1)
                (void) close(temp_fd);
-           return (success_val);
+           return (true_res);
        }
     }
 }
@@ -215,16 +217,17 @@ int     poll_fd_bsd(int fd, int request, int time_limit, int success_val)
 #ifdef USE_SYSV_POLL
 
 #ifdef USE_SYSV_POLL_THEN_SELECT
-#define HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, success_val) \
-       return (poll_fd_bsd((fd), (request), (time_limit), (success_val)))
+#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \
+       return (poll_fd_bsd((fd), (req), (time_limit), (true_res), (false_res)))
 #else
-#define HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, success_val) \
+#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \
        msg_fatal("poll: %m")
 #endif
 
 /* poll_fd_sysv - block with time_limit until file descriptor is ready */
 
-int     poll_fd_sysv(int fd, int request, int time_limit, int success_val)
+int     poll_fd_sysv(int fd, int request, int time_limit,
+                            int true_res, int false_res)
 {
     struct pollfd pollfd;
 
@@ -247,19 +250,18 @@ int     poll_fd_sysv(int fd, int request, int time_limit, int success_val)
                     WAIT_FOR_EVENT : time_limit * 1000)) {
        case -1:
            if (errno != EINTR)
-               HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, success_val);
+               HANDLE_SYSV_POLL_ERROR(fd, request, time_limit,
+                                      true_res, false_res);
            continue;
        case 0:
-           if (time_limit == 0) {
-               return (0);
-           } else {
+           if (false_res < 0)
                errno = ETIMEDOUT;
-               return (-1);
-           }
+           return (false_res);
        default:
            if (pollfd.revents & POLLNVAL)
-               HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, success_val);
-           return (success_val);
+               HANDLE_SYSV_POLL_ERROR(fd, request, time_limit,
+                                      true_res, false_res);
+           return (true_res);
        }
     }
 }