]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
Configurable fallback to unauthenticated TLS
authorViktor Dukhovni <postfix-users@dukhovni.org>
Thu, 27 Feb 2014 07:40:28 +0000 (02:40 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Fri, 1 Aug 2014 06:40:47 +0000 (02:40 -0400)
Since the primary purpose of fallback levels is to allow policy
degradation with an audit trail, add configurable tls audit logging,
which allows policy lookup errors to be logged synchronously, and
the fact of the fallback is then recorded in the audit entry.

14 files changed:
postfix/mantools/postlink
postfix/proto/TLS_README.html
postfix/proto/postconf.proto
postfix/src/global/mail_params.h
postfix/src/smtp/Makefile.in
postfix/src/smtp/lmtp_params.c
postfix/src/smtp/smtp.c
postfix/src/smtp/smtp.h
postfix/src/smtp/smtp_connect.c
postfix/src/smtp/smtp_params.c
postfix/src/smtp/smtp_proto.c
postfix/src/smtp/smtp_tls_audit.c [new file with mode: 0644]
postfix/src/smtp/smtp_tls_policy.c
postfix/src/smtp/smtp_trouble.c

index 4d314730cb51eb9e54f9fbba429a1c9b2a02badc..731bec18ddb6da0f1af9cb59d05e3226f4ef4129 100755 (executable)
@@ -631,6 +631,10 @@ while (<>) {
     s;\bsmtp_starttls_timeout\b;<a href="postconf.5.html#smtp_starttls_timeout">$&</a>;g;
     s;\bsmtp_tls_CAfile\b;<a href="postconf.5.html#smtp_tls_CAfile">$&</a>;g;
     s;\bsmtp_tls_CApath\b;<a href="postconf.5.html#smtp_tls_CApath">$&</a>;g;
+    s;\bsmtp_tls_fallback_level\b;<a href="postconf.5.html#smtp_tls_fallback_level">$&</a>;g;
+    s;\blmtp_tls_fallback_level\b;<a href="postconf.5.html#lmtp_tls_fallback_level">$&</a>;g;
+    s;\bsmtp_tls_audit_template\b;<a href="postconf.5.html#smtp_tls_audit_template">$&</a>;g;
+    s;\blmtp_tls_audit_template\b;<a href="postconf.5.html#lmtp_tls_audit_template">$&</a>;g;
     s;\bsmtp_tls_cert_file\b;<a href="postconf.5.html#smtp_tls_cert_file">$&</a>;g;
     s;\bsmtp_tls_fingerprint_digest\b;<a href="postconf.5.html#smtp_tls_fingerprint_digest">$&</a>;g;
     s;\bsmtp_tls_protocols\b;<a href="postconf.5.html#smtp_tls_protocols">$&</a>;g;
index bc492ebd506d2fd8e7dcdbc82aaec3db558af460..af9bd7cf83905a8c7fac7c014b6e10d7ff29a4b1 100644 (file)
@@ -1373,8 +1373,13 @@ for early adopters. <p>
 <li> <p> The "example.com" destination uses DANE, but if TLSA records
 are not present or are unusable, mail is deferred. </p>
 
-<li> <p> The "example.org" destination uses DANE if possible, but if no TLSA
-records are found opportunistic TLS is used. </p>
+<li> <p> The "example.org" destination uses DANE if possible, but
+if no TLSA records are found opportunistic TLS is used.  The
+"fallback" attribute supported with Postfix &ge; 2.12, overrides
+the main.cf smtp_tls_fallback_level parameter to employ unauthenticated
+mandatory encryption if DANE authentication fails, after logging a
+warning.  See smtp_tls_audit_template for additional control over TLS
+security logging.  </p>
 </ul>
 
 <blockquote>
@@ -1394,26 +1399,16 @@ main.cf:
     # default_transport = smtp, but some destinations are special:
     #
     transport_maps = ${indexed}transport
-</pre>
-</blockquote>
 
-<blockquote>
-<pre>
 transport:
     example.com dane
     example.org dane
-</pre>
-</blockquote>
 
-<blockquote>
-<pre>
 tls_policy:
     example.com dane-only
-</pre>
-</blockquote>
+    # Postfix &ge; 2.12, per-destination smtp_tls_fallback_level override
+    example.org dane fallback=encrypt
 
-<blockquote>
-<pre>
 master.cf:
     dane       unix  -       -       n       -       -       smtp
       -o smtp_dns_support_level=dnssec
@@ -2146,7 +2141,9 @@ href="#client_tls_encrypt">encrypt</a>.  When usable TLSA records
 are obtained for the remote SMTP server, SSLv2 is automatically
 disabled (see smtp_tls_mandatory_protocols), and the server certificate
 must match the TLSA records.  RFC 6698 (DANE) TLS authentication
-and DNSSEC support is available with Postfix 2.11 and later.  </dd>
+and DNSSEC support is available with Postfix 2.11 and later.  With Postfix
+&ge; 2.12 the optional "fallback" attribute can be used as a per-site override
+of the main.cf smtp_tls_fallback_level parameter.  </dd>
 
 <dt><b>dane-only</b></dt> <dd><a href="#client_tls_dane">Mandatory DANE TLS</a>.
 The TLS policy for the destination is obtained via TLSA records in
@@ -2155,7 +2152,9 @@ connection is made to the server.  When usable TLSA records are
 obtained for the remote SMTP server, SSLv2 is automatically disabled
 (see smtp_tls_mandatory_protocols), and the server certificate must
 match the TLSA records.  RFC 6698 (DANE) TLS authentication and
-DNSSEC support is available with Postfix 2.11 and later.  </dd>
+DNSSEC support is available with Postfix 2.11 and later.  With Postfix
+&ge; 2.12 the optional "fallback" attribute can be used as a per-site override
+of the main.cf smtp_tls_fallback_level parameter.  </dd>
 
 <dt><b>fingerprint</b></dt> <dd><a href="#client_tls_fprint">Certificate
 fingerprint verification.</a> Available with Postfix 2.5 and
@@ -2164,13 +2163,14 @@ authorities. The certificate trust chain, expiration date, ... are
 not checked. Instead, the optional <b>match</b> attribute, or else
 the main.cf <b>smtp_tls_fingerprint_cert_match</b> parameter, lists
 the server certificate fingerprints or public key fingerprints
-(Postfix 2.9 and later).  The
-digest algorithm used to calculate fingerprints is selected by the
-<b>smtp_tls_fingerprint_digest</b> parameter. Multiple fingerprints can
-be combined with a "|" delimiter in a single match attribute, or multiple
-match attributes can be employed. The ":" character is not used as a
-delimiter as it occurs between each pair of fingerprint (hexadecimal)
-digits. </dd>
+(Postfix 2.9 and later).  The digest algorithm used to calculate
+fingerprints is selected by the <b>smtp_tls_fingerprint_digest</b>
+parameter. Multiple fingerprints can be combined with a "|" delimiter
+in a single match attribute, or multiple match attributes can be
+employed. The ":" character is not used as a delimiter as it occurs
+between each pair of fingerprint (hexadecimal) digits.  With Postfix
+&ge; 2.12 the optional "fallback" attribute can be used as a per-site
+override of the main.cf smtp_tls_fallback_level parameter.  </dd>
 
 <dt><b>verify</b></dt> <dd><a href="#client_tls_verify">Mandatory
 server certificate verification</a>.  Mail is delivered only if the
@@ -2181,9 +2181,11 @@ the optional "match" attribute (or the main.cf smtp_tls_verify_cert_match
 parameter value when no optional "match" attribute is specified).
 With Postfix &ge; 2.11 the "tafile" attribute optionally modifies
 trust chain verification in the same manner as the
-"smtp_tls_trust_anchor_file" parameter.  The "tafile" attribute
-may be specified multiple times to load multiple trust-anchor
-files.  </dd>
+"smtp_tls_trust_anchor_file" parameter.  The "tafile" attribute may
+be specified multiple times to load multiple trust-anchor files.
+With Postfix &ge; 2.12 the optional "fallback" attribute can be
+used as a per-site override of the main.cf smtp_tls_fallback_level
+parameter.  </dd>
 
 <dt><b>secure</b></dt> <dd><a href="#client_tls_secure">Secure certificate
 verification.</a> Mail is delivered only if the TLS handshake succeeds,
@@ -2195,7 +2197,9 @@ main.cf smtp_tls_secure_cert_match parameter value when no optional
 attribute optionally modifies trust chain verification in the same manner
 as the "smtp_tls_trust_anchor_file" parameter.  The "tafile" attribute
 may be specified multiple times to load multiple trust-anchor
-files.  </dd>
+files.  With Postfix &ge; 2.12 the optional "fallback" attribute
+can be used as a per-site override of the main.cf smtp_tls_fallback_level
+parameter.  </dd>
 
 </dl>
 
@@ -2242,6 +2246,7 @@ Example:
     smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
     # Postfix 2.5 and later
     smtp_tls_fingerprint_digest = md5
+
 /etc/postfix/tls_policy:
     example.edu             none
     example.mil             may
@@ -2256,6 +2261,8 @@ Example:
        match=3D:95:34:51:24:66:33:B9:D2:40:99:C0:C1:17:0B:D1
     # Postfix 2.6 and later
     example.info            may protocols=!SSLv2 ciphers=medium exclude=3DES
+    # Postfix 2.12 and later override of smtp_tls_fallback_level
+    fallback.example        secure fallback=encrypt
 </pre>
 </blockquote>
 
index c26f1a5da42a17c133c264d123fe7a1a4992605f..4c285eefab4b8e583953397802de40c994faa92f 100644 (file)
@@ -11019,8 +11019,8 @@ Example:
     [mail.example.org]:587      secure match=nexthop
     # Postfix 2.5 and later
     [thumb.example.org]          fingerprint
-       match=EC:3B:2D:B0:5B:B1:FB:6D:20:A3:9D:72:F6:8D:12:35
-       match=3D:95:34:51:24:66:33:B9:D2:40:99:C0:C1:17:0B:D1
+        match=EC:3B:2D:B0:5B:B1:FB:6D:20:A3:9D:72:F6:8D:12:35
+        match=3D:95:34:51:24:66:33:B9:D2:40:99:C0:C1:17:0B:D1
 </pre>
 
 <p> <b>Note:</b> The <b>hostname</b> strategy if listed in a non-default
@@ -16184,3 +16184,132 @@ mail. </dd>
 </dl>
 
 <p> This feature is available in Postfix 2.12 and later. </p>
+
+%PARAM smtp_tls_fallback_level
+
+<p> Optional fallback levels for authenticated TLS levels.  Specify
+a white-space or comma-separate list of
+<b>policy_level</b>=<b>fallback_level</b> pairs.  The <b>policy_level</b>
+must require authentication (be one of dane, dane-only, fingerprint,
+verify, secure).  The <b>fallback_level</b> must be "encrypt" or
+"may".  When an authenticated connection with a policy level equal
+to one of the specified values cannot be established, delivery will
+proceed at the fallback level if possible.  A warning will be logged
+indicating the fallback reason.  You can use smtp_tls_audit_template
+to record the TLS security status for each delivery. </p>
+
+<p> The TLS <a href="TLS_README.html#client_tls_policy">policy</a> table
+can be used to specify a destination-specific fallback strategy via the
+"fallback" policy attribute.  The value of the "fallback" attribute, if
+specified, must be "may", "encrypt" or "none".  If not "none", this
+specifies the fallback level for the destination in question.  If the
+attribute value is "none", fallback is suppressed for the destination
+even if enabled via a global setting of smtp_tls_fallback_level.  </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+    # When authentication fails, log a warning and deliver anyway
+    # over an unauthenticated TLS connection.
+    #
+    smtp_tls_fallback_level =
+       dane=encrypt,
+       dane-only=encrypt,
+       fingerprint=encrypt,
+       verify=encrypt,
+       secure=encrypt
+    indexed = ${default_database_type}:${config_directory}/
+    smtp_tls_policy_maps = ${indexed}tls-policy
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/tls-policy:
+    # No fallback for example.com
+    example.com secure fallback=none
+    # For example.net tolerate cleartext fallback
+    example.net dane fallback=may
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 2.12 and later. </p>
+
+%PARAM lmtp_tls_fallback_level
+
+<p> The LMTP-specific version of the smtp_tls_fallback_level
+configuration parameter.  See there for details. </p>
+
+<p> This feature is available in Postfix 2.12 and later. </p>
+
+%PARAM smtp_tls_audit_template
+
+<p> Optional template for tls audit logging at the completion of each
+message data transfer.  If empty (the default setting) no TLS audit log
+entries are generated.  </p>
+
+<p> The following $name expansions are done on smtp_tls_audit_template: </p>
+
+<dl>
+
+<dt><b>$relay</b></dt>
+<dd>The remote SMTP server. </dd>
+
+<dt><b>$level</b></dt>
+<dd>The effective TLS security level after any fallback. </dd>
+
+<dt><b>$policy</b></dt>
+<dd>The desired TLS security level before any fallback, undefined
+if no fallback took place. </dd>
+
+<dt><b>$auth</b></dt>
+<dd>The authentication level of the remote SMTP server.  One of
+"Cleartext", "Anonymous", "Untrusted", "Trusted" or "Verified".
+</dd>
+
+<dt><b>$protocol</b></dt>
+<dd>The TLS protocol version, defined only when TLS is used. </dd>
+
+<dt><b>$cipher</b></dt>
+<dd>The TLS cipher name, defined only when TLS is used. </dd>
+
+<dt><b>$cert_digest</b></dt>
+<dd>The digest of the remote SMTP server's certificate, defined
+only when TLS is used and the remote server presented a certificate.
+The digest algorithm is that specified via smtp_tls_fingerprint_digest.
+</dd>
+
+<dt><b>$spki_digest</b></dt>
+<dd>The digest of the remote SMTP server's public key (Subject
+Public Key Info or SPKI from X.509), defined only when TLS is used
+and the remote server presented a certificate.  The digest algorithm
+is that specified via smtp_tls_fingerprint_digest.  </dd>
+
+<dt><b>${name?value}</b></dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is non-empty. </dd>
+
+<dt><b>${name:value}</b></dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is empty. </dd>
+
+</dl>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/main.cf:
+    smtp_tls_audit_template =
+        tlsaudit: relay=${relay}${auth?, auth=${auth}}${level?, level=${level}}${policy?, policy=${policy}}${protocol?, protocol=${protocol}}${cipher?, cipher=${cipher}}
+</pre>
+
+<p> This feature is available in Postfix 2.12 and later. </p>
+
+%PARAM lmtp_tls_audit_template
+
+<p> The LMTP-specific version of the smtp_tls_audit_template
+configuration parameter.  See there for details. </p>
+
+<p> This feature is available in Postfix 2.12 and later. </p>
index 3b402a708ba10b6d187737c536b83723a879358f..494112014d4a6be6e218d841213505cd2ee2f82c 100644 (file)
@@ -1372,6 +1372,12 @@ extern bool var_smtp_tls_enforce_peername;
 #define DEF_LMTP_TLS_LEVEL     ""
 extern char *var_smtp_tls_level;
 
+#define VAR_SMTP_TLS_FBACK_LEVEL       "smtp_tls_fallback_level"
+#define DEF_SMTP_TLS_FBACK_LEVEL       ""
+#define VAR_LMTP_TLS_FBACK_LEVEL       "lmtp_tls_fallback_level"
+#define DEF_LMTP_TLS_FBACK_LEVEL       ""
+extern char *var_smtp_tls_fback_level;
+
 #define VAR_SMTP_TLS_SCERT_VD  "smtp_tls_scert_verifydepth"
 #define DEF_SMTP_TLS_SCERT_VD  9
 #define VAR_LMTP_TLS_SCERT_VD  "lmtp_tls_scert_verifydepth"
@@ -1543,6 +1549,12 @@ extern bool var_smtp_tls_blk_early_mail_reply;
 #define DEF_LMTP_TLS_FORCE_TLSA 0
 extern bool var_smtp_tls_force_tlsa;
 
+#define VAR_SMTP_TLS_AUDIT_TEMPLATE "smtp_tls_audit_template"
+#define DEF_SMTP_TLS_AUDIT_TEMPLATE ""
+#define VAR_LMTP_TLS_AUDIT_TEMPLATE "lmtp_tls_audit_template"
+#define DEF_LMTP_TLS_AUDIT_TEMPLATE ""
+extern char *var_smtp_tls_audit_template;
+
  /*
   * SASL authentication support, SMTP server side.
   */
index fdbab2f97c101ccb0d034cdd680c421b844bf362..e271bf7d48eb1b115718ecd4493c741d663aed08 100644 (file)
@@ -2,11 +2,11 @@ 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_tls_policy.c \
        smtp_sasl_proto.c smtp_sasl_glue.c smtp_reuse.c smtp_map11.c \
-       smtp_sasl_auth_cache.c smtp_key.c
+       smtp_sasl_auth_cache.c smtp_key.c smtp_tls_audit.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_tls_policy.o \
        smtp_sasl_proto.o smtp_sasl_glue.o smtp_reuse.o smtp_map11.o \
-       smtp_sasl_auth_cache.o smtp_key.o
+       smtp_sasl_auth_cache.o smtp_key.o smtp_tls_audit.o
 HDRS   = smtp.h smtp_sasl.h smtp_addr.h smtp_reuse.h smtp_sasl_auth_cache.h
 TESTSRC        = 
 DEFS   = -I. -I$(INC_DIR) -D$(SYSTYPE)
@@ -662,6 +662,41 @@ 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_audit.o: ../../include/argv.h
+smtp_tls_audit.o: ../../include/attr.h
+smtp_tls_audit.o: ../../include/deliver_request.h
+smtp_tls_audit.o: ../../include/dict.h
+smtp_tls_audit.o: ../../include/dns.h
+smtp_tls_audit.o: ../../include/dsn.h
+smtp_tls_audit.o: ../../include/dsn_buf.h
+smtp_tls_audit.o: ../../include/header_body_checks.h
+smtp_tls_audit.o: ../../include/header_opts.h
+smtp_tls_audit.o: ../../include/htable.h
+smtp_tls_audit.o: ../../include/mac_expand.h
+smtp_tls_audit.o: ../../include/mac_parse.h
+smtp_tls_audit.o: ../../include/mail_params.h
+smtp_tls_audit.o: ../../include/maps.h
+smtp_tls_audit.o: ../../include/match_list.h
+smtp_tls_audit.o: ../../include/mime_state.h
+smtp_tls_audit.o: ../../include/msg.h
+smtp_tls_audit.o: ../../include/msg_stats.h
+smtp_tls_audit.o: ../../include/myaddrinfo.h
+smtp_tls_audit.o: ../../include/myflock.h
+smtp_tls_audit.o: ../../include/name_code.h
+smtp_tls_audit.o: ../../include/name_mask.h
+smtp_tls_audit.o: ../../include/recipient_list.h
+smtp_tls_audit.o: ../../include/resolve_clnt.h
+smtp_tls_audit.o: ../../include/scache.h
+smtp_tls_audit.o: ../../include/sock_addr.h
+smtp_tls_audit.o: ../../include/string_list.h
+smtp_tls_audit.o: ../../include/sys_defs.h
+smtp_tls_audit.o: ../../include/tls.h
+smtp_tls_audit.o: ../../include/tok822.h
+smtp_tls_audit.o: ../../include/vbuf.h
+smtp_tls_audit.o: ../../include/vstream.h
+smtp_tls_audit.o: ../../include/vstring.h
+smtp_tls_audit.o: smtp.h
+smtp_tls_audit.o: smtp_tls_audit.c
 smtp_tls_policy.o: ../../include/argv.h
 smtp_tls_policy.o: ../../include/attr.h
 smtp_tls_policy.o: ../../include/ctable.h
index 1861e5ba5ee3af14be1f05240478ee854d5cd1b8..de2f75ad801aa1c4af0229ea450fbf085fdd31ee 100644 (file)
@@ -28,6 +28,7 @@
        VAR_LMTP_TLS_ECCERT_FILE, DEF_LMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0,
        VAR_LMTP_TLS_ECKEY_FILE, DEF_LMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0,
        VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
+       VAR_LMTP_TLS_FBACK_LEVEL, DEF_LMTP_TLS_FBACK_LEVEL, &var_smtp_tls_fback_level, 0, 0,
 #endif
        VAR_LMTP_SASL_MECHS, DEF_LMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0,
        VAR_LMTP_SASL_TYPE, DEF_LMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0,
        VAR_LMTP_DUMMY_MAIL_AUTH, DEF_LMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth,
        0,
     };
+    /* Suppress $name expansion upon loading. */
+    static const CONFIG_RAW_TABLE lmtp_raw_table[] = {
+       VAR_LMTP_TLS_AUDIT_TEMPLATE, DEF_LMTP_TLS_AUDIT_TEMPLATE, &var_smtp_tls_audit_template, 0, 0,
+       0,
+    };
index 158c273e6b8afcc96afcac39b7018d004e628a75..08ad2e53465caf5c03e26c88f1ba64d4fe94ad22 100644 (file)
 /*     RFC 6698 trust-anchor digest support in the Postfix TLS library.
 /* .IP "\fBtlsmgr_service_name (tlsmgr)\fR"
 /*     The name of the \fBtlsmgr\fR(8) service entry in master.cf.
+/* .PP
+/*     Available in Postfix version 2.12 and later:
+/* .IP "\fBsmtp_tls_audit_template (empty)\fR"
+/*     Optional template for tls audit logging at the completion of each
+/*     message data transfer.
+/* .IP "\fBsmtp_tls_fallback_level (empty)\fR"
+/*     Optional fallback levels for authenticated TLS levels.
 /* OBSOLETE STARTTLS CONTROLS
 /* .ad
 /* .fi
@@ -856,6 +863,7 @@ char   *var_smtp_tls_mand_excl;
 char   *var_smtp_tls_dcert_file;
 char   *var_smtp_tls_dkey_file;
 bool    var_smtp_tls_enforce_peername;
+char   *var_smtp_tls_fback_level;
 char   *var_smtp_tls_key_file;
 char   *var_smtp_tls_loglevel;
 bool    var_smtp_tls_note_starttls_offer;
@@ -872,6 +880,7 @@ char   *var_smtp_tls_eccert_file;
 char   *var_smtp_tls_eckey_file;
 bool    var_smtp_tls_blk_early_mail_reply;
 bool    var_smtp_tls_force_tlsa;
+char   *var_smtp_tls_audit_template;
 
 #endif
 
@@ -1285,6 +1294,8 @@ int     main(int argc, char **argv)
                       smtp_int_table : lmtp_int_table,
                       MAIL_SERVER_STR_TABLE, smtp_mode ?
                       smtp_str_table : lmtp_str_table,
+                      MAIL_SERVER_RAW_TABLE, smtp_mode ?
+                      smtp_raw_table : lmtp_raw_table,
                       MAIL_SERVER_BOOL_TABLE, smtp_mode ?
                       smtp_bool_table : lmtp_bool_table,
                       MAIL_SERVER_PRE_INIT, pre_init,
index c21aa65a5b575dbb6ddc0c2faec8987ab803c08e..14ae12d56a14d598f76b6c1d8a45598f7163d37e 100644 (file)
@@ -92,6 +92,8 @@ typedef struct SMTP_ITERATOR {
 
 typedef struct SMTP_TLS_POLICY {
     int     level;                     /* TLS enforcement level */
+    int     policy_level;              /* TLS desired policy level */
+    int     fallback_level;            /* TLS fallback level */
     char   *protocols;                 /* Acceptable SSL protocols */
     char   *grade;                     /* Cipher grade: "export", ... */
     VSTRING *exclusions;               /* Excluded SSL ciphers */
@@ -120,11 +122,13 @@ extern void smtp_tls_policy_cache_flush(void);
        SMTP_TLS_POLICY *_tls_policy_dummy_tmp = (t); \
        smtp_tls_policy_init(_tls_policy_dummy_tmp, (DSN_BUF *) 0); \
        _tls_policy_dummy_tmp->level = TLS_LEV_NONE; \
+       _tls_policy_dummy_tmp->policy_level = TLS_LEV_NONE; \
     } while (0)
 
  /* This macro is not part of the module external interface. */
 #define smtp_tls_policy_init(t, w) do { \
        SMTP_TLS_POLICY *_tls_policy_init_tmp = (t); \
+       _tls_policy_init_tmp->fallback_level = TLS_LEV_NOTFOUND; \
        _tls_policy_init_tmp->protocols = 0; \
        _tls_policy_init_tmp->grade = 0; \
        _tls_policy_init_tmp->exclusions = 0; \
@@ -341,6 +345,7 @@ typedef struct SMTP_SESSION {
     char   *tls_nexthop;               /* Nexthop domain for cert checks */
     int     tls_retry_plain;           /* Try plain when TLS handshake fails */
     SMTP_TLS_POLICY *tls;              /* TEMPORARY */
+    int     tls_level;                 /* Actual tls level */
 #endif
 
     SMTP_STATE *state;                 /* back link */
@@ -469,14 +474,16 @@ extern HBC_CALL_BACKS smtp_hbc_callbacks[];
 
 #define PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE \
        (session->tls_context == 0 \
-           && session->tls->level == TLS_LEV_MAY \
+           && (session->tls->level == TLS_LEV_MAY \
+               || session->tls->fallback_level == TLS_LEV_MAY) \
            && PREACTIVE_DELAY >= var_min_backoff_time \
            && !HAVE_SASL_CREDENTIALS)
 
 #define PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE \
        (session->tls_context != 0 \
            && SMTP_RCPT_LEFT(state) > SMTP_RCPT_MARK_COUNT(state) \
-           && session->tls->level == TLS_LEV_MAY \
+           && (session->tls->level == TLS_LEV_MAY \
+               || session->tls->fallback_level == TLS_LEV_MAY) \
            && PREACTIVE_DELAY >= var_min_backoff_time \
            && !HAVE_SASL_CREDENTIALS)
 
@@ -488,8 +495,17 @@ extern HBC_CALL_BACKS smtp_hbc_callbacks[];
 #define RETRY_AS_PLAINTEXT do { \
        session->tls_retry_plain = 1; \
        state->misc_flags &= ~SMTP_MISC_FLAG_FINAL_SERVER; \
+       (void) smtp_tls_trouble(state, session->tls_context ? \
+                               STARTTLS_SESSION_FALLBACK : \
+                               STARTTLS_HANDSHAKE_FALLBACK); \
     } while (0)
 
+#define STARTTLS_FEATURE_FALLBACK      1       /* No STARTTLS feature */
+#define STARTTLS_COMMAND_FALLBACK      2       /* Refused STARTTLS command */
+#define STARTTLS_HANDSHAKE_FALLBACK    3       /* Handshake failed */
+#define STARTTLS_VERIFY_FALLBACK       4       /* Peer verification failed */
+#define STARTTLS_SESSION_FALLBACK      5       /* Data transfer failed */
+
  /*
   * smtp_chat.c
   */
@@ -572,6 +588,11 @@ extern void PRINTFLIKE(5, 6) smtp_rcpt_fail(SMTP_STATE *, RECIPIENT *,
                                                    const char *,...);
 extern int smtp_stream_except(SMTP_STATE *, int, const char *);
 
+#ifdef USE_TLS
+extern int smtp_tls_trouble(SMTP_STATE *, int);
+
+#endif
+
  /*
   * smtp_unalias.c
   */
@@ -649,6 +670,11 @@ char   *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int);
        | COND_SASL_SMTP_KEY_FLAG_NEXTHOP | COND_SASL_SMTP_KEY_FLAG_HOSTNAME \
        | SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT)
 
+ /*
+  * smtp_tls_audit.c
+  */
+extern void smtp_tls_audit(SMTP_STATE *state);
+
  /*
   * Silly little macros.
   */
index acff1eba4cf5f9fa60dc1dc08a6dd95ab851d811..e802fd93de6b384eda7ad3cbdf9faf5a64ed02c4 100644 (file)
 #include <smtp_addr.h>
 #include <smtp_reuse.h>
 
+#ifdef USE_TLS
+#define TLS_SESS_INIT(session, state) do { \
+       session->tls_level = state->tls->level; /* Pre fallback */ \
+       session->tls = state->tls;              /* TEMPORARY */ \
+    } while (0)
+#endif
+
  /*
   * Forward declaration.
   */
@@ -522,7 +529,7 @@ static void smtp_connect_local(SMTP_STATE *state, const char *path)
     if ((state->session = session) != 0) {
        session->state = state;
 #ifdef USE_TLS
-       session->tls = state->tls;              /* TEMPORARY */
+       TLS_SESS_INIT(session, state);
        session->tls_nexthop = var_myhostname;  /* for TLS_LEV_SECURE */
        if (session->tls->level == TLS_LEV_MAY) {
            msg_warn("%s: opportunistic TLS encryption is not appropriate "
@@ -674,7 +681,7 @@ static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
            && *addr_list == 0)
            state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
 #ifdef USE_TLS
-       session->tls = state->tls;              /* TEMPORARY */
+       TLS_SESS_INIT(session, state);
 #endif
        smtp_xfer(state);
        smtp_cleanup_session(state);
@@ -734,7 +741,7 @@ static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list,
                && next == 0)
                state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
 #ifdef USE_TLS
-           session->tls = state->tls;          /* TEMPORARY */
+           TLS_SESS_INIT(session, state);
 #endif
            smtp_xfer(state);
            smtp_cleanup_session(state);
@@ -978,7 +985,7 @@ static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
            if ((state->session = session) != 0) {
                session->state = state;
 #ifdef USE_TLS
-               session->tls = state->tls;      /* TEMPORARY */
+               TLS_SESS_INIT(session, state);
                /* XXX: EAI: Convert to A-label here or in TLS library */
                session->tls_nexthop = domain;  /* for TLS_LEV_SECURE */
 #endif
index 807215dba90d35d07489af0e4c83d1404cfd2034..93144addaceffcc3eb00f0b997c16893fe8b70c6 100644 (file)
@@ -29,6 +29,7 @@
        VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0,
        VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0,
        VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0,
+       VAR_SMTP_TLS_FBACK_LEVEL, DEF_SMTP_TLS_FBACK_LEVEL, &var_smtp_tls_fback_level, 0, 0,
 #endif
        VAR_SMTP_SASL_MECHS, DEF_SMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0,
        VAR_SMTP_SASL_TYPE, DEF_SMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0,
        VAR_SMTP_DUMMY_MAIL_AUTH, DEF_SMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth,
        0,
     };
+    /* Suppress $name expansion upon loading. */
+    static const CONFIG_RAW_TABLE smtp_raw_table[] = {
+       VAR_SMTP_TLS_AUDIT_TEMPLATE, DEF_SMTP_TLS_AUDIT_TEMPLATE, &var_smtp_tls_audit_template, 0, 0,
+       0,
+    };
index 53e4c14960dbdcc0c859d83af4be4d331e6a25ee..019776b943cb049438ee375bcaf8219a8ad48d09 100644 (file)
@@ -339,6 +339,9 @@ int     smtp_helo(SMTP_STATE *state)
        /*
         * If the policy table specifies a bogus TLS security level, fail
         * now.
+        * 
+        * XXX: This should be caught in smtp_connect before we even make a
+        * connection to the host.  Change to msg_panic()?
         */
 #ifdef USE_TLS
        if (session->tls->level == TLS_LEV_INVALID)
@@ -753,37 +756,38 @@ int     smtp_helo(SMTP_STATE *state)
             * although support for it was announced in the EHLO response.
             */
            session->features &= ~SMTP_FEATURE_STARTTLS;
-           if (TLS_REQUIRED(session->tls->level))
+           if (smtp_tls_trouble(state, STARTTLS_COMMAND_FALLBACK))
                return (smtp_site_fail(state, STR(iter->host), resp,
                    "TLS is required, but host %s refused to start TLS: %s",
                                       session->namaddr,
                                       translit(resp->str, "\n", " ")));
            /* Else try to continue in plain-text mode. */
-       }
+       } else {
 
-       /*
-        * Give up if we must use TLS but can't for various reasons.
-        * 
-        * 200412 Be sure to provide the default clause at the bottom of this
-        * block. When TLS is required we must never, ever, end up in
-        * plain-text mode.
-        */
-       if (TLS_REQUIRED(session->tls->level)) {
-           if (!(session->features & SMTP_FEATURE_STARTTLS)) {
-               return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
-                                      SMTP_RESP_FAKE(&fake, "4.7.4"),
+           /*
+            * Give up if we must use TLS but can't for various reasons.
+            
+            * 200412 Be sure to provide the default clause at the bottom of
+            * this block. When TLS is required we must never, ever, end up
+            * in plain-text mode.
+            */
+           if (smtp_tls_trouble(state, STARTTLS_FEATURE_FALLBACK)) {
+               if (!(session->features & SMTP_FEATURE_STARTTLS)) {
+                   return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+                                          SMTP_RESP_FAKE(&fake, "4.7.4"),
                          "TLS is required, but was not offered by host %s",
-                                      session->namaddr));
-           } else if (smtp_tls_ctx == 0) {
-               return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
-                                      SMTP_RESP_FAKE(&fake, "4.7.5"),
+                                          session->namaddr));
+               } else if (smtp_tls_ctx == 0) {
+                   return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+                                          SMTP_RESP_FAKE(&fake, "4.7.5"),
                     "TLS is required, but our TLS engine is unavailable"));
-           } else {
-               msg_warn("%s: TLS is required but unavailable, don't know why",
-                        myname);
-               return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
-                                      SMTP_RESP_FAKE(&fake, "4.7.0"),
+               } else {
+                   msg_warn("%s: TLS is required but unavailable, don't know why",
+                            myname);
+                   return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
+                                          SMTP_RESP_FAKE(&fake, "4.7.0"),
                                       "TLS is required, but unavailable"));
+               }
            }
        }
     }
@@ -807,6 +811,7 @@ static int smtp_start_tls(SMTP_STATE *state)
     TLS_CLIENT_START_PROPS tls_props;
     VSTRING *serverid;
     SMTP_RESP fake;
+    int     tls_level;
 
     /*
      * Turn off SMTP connection caching. When the TLS handshake succeeds, we
@@ -855,6 +860,11 @@ static int smtp_start_tls(SMTP_STATE *state)
      * the TLS handshake. It records the verification and match status in the
      * resulting TLScontext. It is now up to the application to abort the TLS
      * connection if it chooses.
+     *
+     * Consequently, the TLS library need not and does not distinguish between
+     * the "dane" and "dane-only" security levels.  By the time we have TLSA
+     * records in hand, both behave identically modulo application-level
+     * fallback.  We collapse these now equivalent security levels.
      * 
      * XXX When tls_client_start() fails then we don't know what state the SMTP
      * connection is in, so we give up on this connection even if we are not
@@ -863,12 +873,14 @@ static int smtp_start_tls(SMTP_STATE *state)
      * Large parameter lists are error-prone, so we emulate a language feature
      * that C does not have natively: named parameter lists.
      */
+    if ((tls_level = session->tls->level) == TLS_LEV_DANE_ONLY)
+       tls_level = TLS_LEV_DANE;
     session->tls_context =
        TLS_CLIENT_START(&tls_props,
                         ctx = smtp_tls_ctx,
                         stream = session->stream,
                         timeout = var_smtp_starttls_tmout,
-                        tls_level = session->tls->level,
+                        tls_level = tls_level,
                         nexthop = session->tls_nexthop,
                         host = STR(iter->host),
                         namaddr = session->namaddrport,
@@ -913,24 +925,21 @@ static int smtp_start_tls(SMTP_STATE *state)
      * result, abort the delivery here. We have a usable TLS session with the
      * server, so no need to disable I/O, ... we can even be polite and send
      * "QUIT".
-     * 
-     * See src/tls/tls_level.c and src/tls/tls.h. Levels above "encrypt" require
-     * matching.  Levels >= "dane" require CA or DNSSEC trust.
-     * 
-     * When DANE TLSA records specify an end-entity certificate, the trust and
-     * match bits always coincide, but it is fine to report the wrong
-     * end-entity certificate as untrusted rather than unmatched.
      */
-    if (TLS_MUST_TRUST(session->tls->level))
-       if (!TLS_CERT_IS_TRUSTED(session->tls_context))
+    if (TLS_MUST_TRUST(session->tls_level)
+        && !TLS_CERT_IS_TRUSTED(session->tls_context)) {
+       if (smtp_tls_trouble(state, STARTTLS_VERIFY_FALLBACK))
            return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
                                   SMTP_RESP_FAKE(&fake, "4.7.5"),
                                   "Server certificate not trusted"));
-    if (TLS_MUST_MATCH(session->tls->level))
-       if (!TLS_CERT_IS_MATCHED(session->tls_context))
+    } else if (TLS_MUST_MATCH(session->tls_level)
+              && !TLS_CERT_IS_MATCHED(session->tls_context)) {
+       /* Peer certificate not matched as it should be */
+       if (smtp_tls_trouble(state, STARTTLS_VERIFY_FALLBACK))
            return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
                                   SMTP_RESP_FAKE(&fake, "4.7.5"),
                                   "Server certificate not verified"));
+    }
 
     /* At this point there must not be any pending plaintext. */
     vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
@@ -2214,6 +2223,10 @@ int     smtp_xfer(SMTP_STATE *state)
      */
     result = smtp_loop(state, send_state, recv_state);
 
+#ifdef USE_TLS
+    smtp_tls_audit(state);
+#endif
+
     if (result == 0
     /* Just in case */
        && vstream_ferror(session->stream) == 0
diff --git a/postfix/src/smtp/smtp_tls_audit.c b/postfix/src/smtp/smtp_tls_audit.c
new file mode 100644 (file)
index 0000000..3d462a1
--- /dev/null
@@ -0,0 +1,126 @@
+#ifdef USE_TLS
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <dict.h>
+#include <mac_expand.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "smtp.h"
+
+ /*
+  * The mini symbol table name and keys used for expanding macros in smtp tls
+  * audit log entries.
+  */
+#define TLS_AUDIT_DICT_TABLE   "tls_audit_template"    /* table name */
+#define TLS_AUDIT_DICT_RELAY   "relay"                 /* key */
+#define TLS_AUDIT_DICT_ALEVEL  "level"                 /* key */
+#define TLS_AUDIT_DICT_PLEVEL  "policy"                /* key */
+#define TLS_AUDIT_DICT_STATUS  "auth"                  /* key */
+#define TLS_AUDIT_DICT_PROTOCOL        "protocol"              /* key */
+#define TLS_AUDIT_DICT_CIPHER  "cipher"                /* key */
+#define TLS_AUDIT_DICT_CERT    "cert_digest"           /* key */
+#define TLS_AUDIT_DICT_SPKI    "spki_digest"           /* key */
+
+static const char *macro_names[] = {
+    TLS_AUDIT_DICT_RELAY,
+    TLS_AUDIT_DICT_ALEVEL,
+    TLS_AUDIT_DICT_PLEVEL,
+    TLS_AUDIT_DICT_STATUS,
+    TLS_AUDIT_DICT_PROTOCOL,
+    TLS_AUDIT_DICT_CIPHER,
+    TLS_AUDIT_DICT_CERT,
+    TLS_AUDIT_DICT_SPKI,
+    0,
+};
+
+/* audit_lookup - macro parser call-back routine */
+
+static const char *audit_lookup(const char *key, int unused_mode, char *dict)
+{
+    const char *value = dict_lookup(dict, key);
+
+    if (value == 0)
+       msg_warn("%s: unknown TLS audit template macro name: \"%s\"",
+                SMTP_X(TLS_AUDIT_TEMPLATE), key);
+    return value;
+}
+
+/* expand_template - expand macros in the audit template */
+
+static int expand_template(char *template, VSTRING *result)
+{
+
+#define NO_SCAN_FILTER ((const char *) 0)
+    return mac_expand(result, template, MAC_EXP_FLAG_NONE, NO_SCAN_FILTER,
+                     audit_lookup, TLS_AUDIT_DICT_TABLE);
+}
+
+/* smtp_tls_audit - log TLS audit trail */
+
+void    smtp_tls_audit(SMTP_STATE *state)
+{
+    DELIVER_REQUEST *request = state->request;
+    SMTP_SESSION *session = state->session;
+    SMTP_TLS_POLICY *tls = session->tls;
+    TLS_SESS_STATE *TLScontext = session->tls_context;
+    const char *policy_level;
+    const char *actual_level;
+    VSTRING *result = vstring_alloc(100);
+    int     status;
+
+    if (!*var_smtp_tls_audit_template)
+       return;
+
+#ifndef TLS_AUDIT_NONE_POLICY
+    /* Do we log policy "none" and cleartext status when TLS is disabled?  */
+    if (tls->policy_level <= TLS_LEV_NONE)
+       return;
+#endif
+
+    dict_update(TLS_AUDIT_DICT_TABLE, TLS_AUDIT_DICT_RELAY,
+               session->namaddrport);
+
+    actual_level = str_tls_level(session->tls_level);
+    policy_level = (session->tls_level == tls->policy_level) ? "" :
+       str_tls_level(tls->policy_level);
+    dict_update(TLS_AUDIT_DICT_TABLE, TLS_AUDIT_DICT_ALEVEL,
+               actual_level ? actual_level : "");
+    dict_update(TLS_AUDIT_DICT_TABLE, TLS_AUDIT_DICT_PLEVEL,
+               policy_level ? policy_level : "");
+
+    dict_update(TLS_AUDIT_DICT_TABLE, TLS_AUDIT_DICT_STATUS,
+               TLScontext == 0 ? "Cleartext" :
+               !TLS_CERT_IS_PRESENT(TLScontext) ? "Anonymous" :
+               TLS_CERT_IS_MATCHED(TLScontext) ? "Verified" :
+               TLS_CERT_IS_TRUSTED(TLScontext) ? "Trusted" :
+               "Untrusted");
+    dict_update(TLS_AUDIT_DICT_TABLE, TLS_AUDIT_DICT_PROTOCOL,
+               TLScontext == 0 ? "" : TLScontext->protocol);
+    dict_update(TLS_AUDIT_DICT_TABLE, TLS_AUDIT_DICT_CIPHER,
+               TLScontext == 0 ? "" : TLScontext->cipher_name);
+    dict_update(TLS_AUDIT_DICT_TABLE, TLS_AUDIT_DICT_CERT,
+               TLScontext == 0 ? "" : TLScontext->peer_cert_fprint);
+    dict_update(TLS_AUDIT_DICT_TABLE, TLS_AUDIT_DICT_SPKI,
+               TLScontext == 0 ? "" : TLScontext->peer_pkey_fprint);
+
+    status = expand_template(var_smtp_tls_audit_template, result);
+    if (status == 0)
+       msg_info("%s: %s", state->request->queue_id, STR(result));
+    vstring_free(result);
+}
+
+#endif                                 /* USE_TLS */
index 59118f2b36f40ceff6427b4147c62c405fe5d14a..ca401716d5741e0ed2bb4f043909ecf7fd2af353 100644 (file)
@@ -155,7 +155,7 @@ static const char *policy_name(int tls_level)
 }
 
 #define MARK_INVALID(why, levelp) do { \
-           dsb_simple((why), "4.7.5", "client TLS configuration problem"); \
+           dsb_simple((why), "4.7.0", "client TLS configuration problem"); \
            *(levelp) = TLS_LEV_INVALID; } while (0)
 
 /* tls_site_lookup - look up per-site TLS security level */
@@ -352,6 +352,34 @@ static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level,
            }
            continue;
        }
+       /* Only one instance per policy. */
+       if (!strcasecmp(name, "fallback")) {
+           if (!TLS_MUST_MATCH(*site_level)) {
+               msg_warn("%s: attribute \"%s\" invalid at security level"
+                        " \"%s\"", WHERE, name, policy_name(*site_level));
+               INVALID_RETURN(tls->why, site_level);
+           }
+           if (tls->fallback_level != TLS_LEV_NOTFOUND) {
+               msg_warn("%s: attribute \"%s\" is specified multiple times",
+                        WHERE, name);
+               INVALID_RETURN(tls->why, site_level);
+           }
+           if (*val == 0) {
+               msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+               INVALID_RETURN(tls->why, site_level);
+           }
+           switch (tls->fallback_level = tls_level_lookup(val)) {
+           case TLS_LEV_NONE:
+           case TLS_LEV_MAY:
+           case TLS_LEV_ENCRYPT:
+               break;
+           default:
+               msg_warn("%s: attribute \"%s\" invalid fallback level: \"%s\"",
+                        WHERE, name, val);
+               INVALID_RETURN(tls->why, site_level);
+           }
+           continue;
+       }
        msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
        INVALID_RETURN(tls->why, site_level);
     }
@@ -433,6 +461,7 @@ static void set_cipher_grade(SMTP_TLS_POLICY *tls)
        break;
 
     case TLS_LEV_DANE:
+    case TLS_LEV_DANE_ONLY:
     case TLS_LEV_FPRINT:
     case TLS_LEV_VERIFY:
     case TLS_LEV_SECURE:
@@ -462,6 +491,51 @@ static void set_cipher_grade(SMTP_TLS_POLICY *tls)
     ADD_EXCLUDE(tls->exclusions, also_exclude);
 }
 
+static int global_fallback(SMTP_TLS_POLICY *tls)
+{
+    static int l = TLS_LEV_NOTFOUND;
+    const char *lname = str_tls_level(tls->level);
+    const char *err;
+    char   *saved;
+    char   *fback;
+    char   *tok;
+    char   *name;
+    char   *val;
+
+    /*
+     * Silently ignore any spurious fallback setting for unauthenticated TLS.
+     */
+    if (!*var_smtp_tls_fback_level || tls->level <= TLS_LEV_ENCRYPT)
+       return l;
+
+    saved = fback = mystrdup(var_smtp_tls_fback_level);
+    while ((tok = mystrtok(&fback, "\t\n\r ,")) != 0) {
+       if ((err = split_nameval(tok, &name, &val)) != 0) {
+           msg_warn("malformed %s: \"%s\": %s", SMTP_X(TLS_FBACK_LEVEL),
+                    saved, err);
+           MARK_INVALID(tls->why, &tls->level);
+           l = TLS_LEV_INVALID;
+           break;
+       }
+       if (strcmp(name, lname) == 0) {
+           switch (l = tls_level_lookup(val)) {
+           case TLS_LEV_MAY:
+           case TLS_LEV_ENCRYPT:
+               break;
+           default:
+               msg_warn("%s: bad fallback mapping: %s=%s",
+                        SMTP_X(TLS_FBACK_LEVEL), name, val);
+               MARK_INVALID(tls->why, &tls->level);
+               l = TLS_LEV_INVALID;
+               break;
+           }
+           break;
+       }
+    }
+    myfree(saved);
+    return (l);
+}
+
 /* policy_create - create SMTP TLS policy cache object (ctable call-back) */
 
 static void *policy_create(const char *unused_key, void *context)
@@ -522,6 +596,19 @@ static void *policy_create(const char *unused_key, void *context)
        return ((void *) tls);
     }
 
+    /*
+     * Save level as policy level (may be downgraded by early fallback, and
+     * compute fallback level if not specified per-site.  If site fallback
+     * level is "none", replace with "notfound", otherwise if no site fallback
+     * level, use the global value.
+     */
+    tls->policy_level = tls->level;
+    if (tls->fallback_level == TLS_LEV_NONE)
+       tls->fallback_level = TLS_LEV_NOTFOUND;
+    else if (tls->fallback_level == TLS_LEV_NOTFOUND
+            && (tls->fallback_level = global_fallback(tls)) == TLS_LEV_INVALID)
+       return ((void *) tls);
+
     /*
      * DANE initialization may change the security level to something else,
      * so do this early, so that we use the right level below.  Note that
@@ -557,6 +644,7 @@ static void *policy_create(const char *unused_key, void *context)
     case TLS_LEV_MAY:
     case TLS_LEV_ENCRYPT:
     case TLS_LEV_DANE:
+    case TLS_LEV_DANE_ONLY:
        break;
     case TLS_LEV_FPRINT:
        if (tls->dane == 0)
@@ -707,6 +795,7 @@ static int global_tls_level(void)
 #define NONDANE_CONFIG 0               /* Administrator's fault */
 #define NONDANE_DEST   1               /* Remote server's fault */
 #define DANE_UNUSABLE  2               /* Remote server's fault */
+#define TLSA_LOOKUP_ERR        3               /* DNS lookup failed */
 
 static void PRINTFLIKE(4, 5) dane_incompat(SMTP_TLS_POLICY *tls,
                                                   SMTP_ITERATOR *iter,
@@ -716,20 +805,30 @@ static void PRINTFLIKE(4, 5) dane_incompat(SMTP_TLS_POLICY *tls,
     va_list ap;
 
     va_start(ap, fmt);
-    if (tls->level == TLS_LEV_DANE) {
-       tls->level = (errtype == DANE_UNUSABLE) ? TLS_LEV_ENCRYPT : TLS_LEV_MAY;
+    /*
+     * TLSA lookup errors are potential downgrade attacks, since they can hide
+     * the presence of usable TLSA RRs, we must fail or fallback, not downgrade
+     * to encryption-only or opportunistic TLS as with unusable or absent TLSA
+     * records.
+     */
+    if (tls->level == TLS_LEV_DANE && errtype != TLSA_LOOKUP_ERR) {
+       if (errtype == DANE_UNUSABLE) {
+           tls->level = TLS_LEV_ENCRYPT;
+           if (tls->fallback_level != TLS_LEV_MAY)
+               tls->fallback_level = TLS_LEV_NOTFOUND;
+       } else
+           tls->level = TLS_LEV_MAY;
        if (errtype == NONDANE_CONFIG)
            vmsg_warn(fmt, ap);
        else if (msg_verbose)
            vmsg_info(fmt, ap);
-    } else {                                   /* dane-only */
-       if (errtype == NONDANE_CONFIG) {
-           vmsg_warn(fmt, ap);
+    } else {
+       vmsg_warn(fmt, ap);
+       if (errtype == NONDANE_CONFIG
+           || tls->fallback_level == TLS_LEV_NOTFOUND)
            MARK_INVALID(tls->why, &tls->level);
-       } else {
-           tls->level = TLS_LEV_INVALID;
-           vdsb_simple(tls->why, "4.7.5", fmt, ap);
-       }
+       else
+           tls->level = tls->fallback_level;
     }
     va_end(ap);
 }
@@ -786,38 +885,41 @@ static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
     }
     /* When the MX name is present and insecure, DANE does not apply. */
     if (iter->mx && !iter->mx->dnssec_valid) {
-       dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination");
+       dane_incompat(tls, iter, NONDANE_DEST, "%s: non-DNSSEC destination",
+                     STR(iter->dest));
        return;
     }
-    /* When TLSA lookups fail, we defer the message */
+
+    /*
+     * When TLSA lookups fail, as with dane-only, we fall back or defer the
+     * message, the level will be set to either the fallback level or
+     * "invalid".
+     */
     if ((dane = tls_dane_resolve(iter->port, "tcp", iter->rr,
                                 var_smtp_tls_force_tlsa)) == 0) {
-       tls->level = TLS_LEV_INVALID;
-       dsb_simple(tls->why, "4.7.5", "TLSA lookup error for %s:%u",
-                  STR(iter->host), ntohs(iter->port));
+       dane_incompat(tls, iter, TLSA_LOOKUP_ERR,
+                     "%s:%u: DANE TLSA lookup error",
+                     STR(iter->host), ntohs(iter->port));
        return;
     }
     if (tls_dane_notfound(dane)) {
-       dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found");
+       dane_incompat(tls, iter, NONDANE_DEST,
+                     "%s:%u: no DANE TLSA records found",
+                     STR(iter->host), ntohs(iter->port));
        tls_dane_free(dane);
        return;
     }
-
-    /*
-     * Some TLSA records found, but none usable, per
-     * 
-     * https://tools.ietf.org/html/draft-ietf-dane-srv-02#section-4
-     * 
-     * we MUST use TLS, and SHALL use full PKIX certificate checks.  The latter
-     * would be unwise for SMTP: no human present to "click ok" and risk of
-     * non-delivery in most cases exceeds risk of interception.
-     * 
-     * We also have a form of Goedel's incompleteness theorem in play: any list
-     * of public root CA certs is either incomplete or inconsistent (for any
-     * given verifier some of the CAs are surely not trustworthy).
+    /*-
+     * Some TLSA records found, but none usable, per:
+     *
+     *    https://tools.ietf.org/html/draft-ietf-dane-smtp-with-dane
+     *
+     * we MUST use TLS.
      */
     if (tls_dane_unusable(dane)) {
-       dane_incompat(tls, iter, DANE_UNUSABLE, "TLSA records unusable");
+       dane_incompat(tls, iter, DANE_UNUSABLE,
+                     "%s:%u: all DANE TLSA records unusable",
+                     STR(iter->host), ntohs(iter->port));
        tls_dane_free(dane);
        return;
     }
@@ -838,7 +940,6 @@ static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter)
     } else if (!TLS_DANE_HASEE(dane))
        msg_panic("empty DANE match list");
     tls->dane = dane;
-    tls->level = TLS_LEV_DANE;
     return;
 }
 
index 2262e6cf04cf4298a6483fa1aa1f90585ba4c351..622aeac2d97e72a5e01e23099046727d4df80b16 100644 (file)
 /*     SMTP_STATE *state;
 /*     int     exception;
 /*     const char *description;
+/*
+/*     int     smtp_tls_trouble(state, protocol_stage)
+/*     SMTP_STATE *state;
+/*     int protocol_stage;
 /* DESCRIPTION
 /*     This module handles all non-fatal errors that can happen while
 /*     attempting to deliver mail via SMTP, and implements the policy
 /*     The session is marked as "do not cache".
 /*     The result is non-zero.
 /*
+/*     smtp_tls_trouble() handles failure to establish a TLS connection or
+/*     else failure to authenticate the peer.  The protocol_stage argument
+/*     indicates what TLS problem was detected.  The return value is 0 when
+/*     TLS is not required or a fallback strategy allows delivery to continue.
+/*     When a non-zero value is returned delivery must not continue via the
+/*     current SMTP server.  All relevant warnings are logged.
+/*
 /*     Arguments:
 /* .IP state
 /*     SMTP client state per delivery request.
@@ -467,3 +478,71 @@ int     smtp_stream_except(SMTP_STATE *state, int code, const char *description)
      */
     return (smtp_bulk_fail(state, SMTP_THROTTLE));
 }
+
+#ifdef USE_TLS
+
+/* smtp_tls_trouble - Fail or fall back when TLS state is not satisfactory. */
+
+int     smtp_tls_trouble(SMTP_STATE *state, int protocol_stage)
+{
+    SMTP_SESSION *session = state->session;
+    SMTP_TLS_POLICY *tls = session->tls;
+
+    /* Handle non-recoverable cases */
+    switch (protocol_stage) {
+    case STARTTLS_VERIFY_FALLBACK:
+       if (tls->fallback_level == TLS_LEV_NOTFOUND)
+           return (-1);
+       break;
+    case STARTTLS_FEATURE_FALLBACK:
+       /* No recovery when skipping STARTTLS due to local problems */
+       if (session->features & SMTP_FEATURE_STARTTLS)
+           return (-1);
+       /* FALLTHROUGH */
+    case STARTTLS_COMMAND_FALLBACK:
+    case STARTTLS_HANDSHAKE_FALLBACK:
+    case STARTTLS_SESSION_FALLBACK:
+       if (TLS_REQUIRED(session->tls_level)
+           && tls->fallback_level != TLS_LEV_MAY)
+           return (-1);
+       break;
+    default:
+       msg_panic("Unexpected TLS failure stage: %d", protocol_stage);
+    }
+
+    /* Log appropriate warning and perform fallback */
+    switch (protocol_stage) {
+    case STARTTLS_FEATURE_FALLBACK:
+       msg_warn("%s: cleartext fallback, host did not offer STARTTLS",
+                session->namaddrport);
+       break;
+
+    case STARTTLS_COMMAND_FALLBACK:
+       msg_warn("%s: cleartext fallback, host refused to start TLS",
+                session->namaddrport);
+       break;
+
+    case STARTTLS_HANDSHAKE_FALLBACK:
+       msg_warn("%s: cleartext fallback, TLS handshake failed",
+                session->namaddrport);
+       break;
+
+    case STARTTLS_SESSION_FALLBACK:
+       msg_warn("%s: cleartext fallback, post-handshake TLS failure",
+                session->namaddrport);
+       break;
+
+    case STARTTLS_VERIFY_FALLBACK:
+       msg_warn("%s: fallback to unathenticated TLS: %s: %s",
+                session->namaddrport,
+                TLS_CERT_IS_TRUSTED(session->tls_context) ?
+                "Server certificate failed verification" :
+                "Server certificate not trusted");
+       break;
+    }
+
+    session->tls_level = tls->fallback_level;
+    return (0);
+}
+
+#endif