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;
<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 ≥ 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>
# 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 ≥ 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
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
+≥ 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
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
+≥ 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
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
+≥ 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
parameter value when no optional "match" attribute is specified).
With Postfix ≥ 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 ≥ 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,
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 ≥ 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>
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
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>
[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
</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>
#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"
#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.
*/
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)
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
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,
+ };
/* 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
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;
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
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,
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 */
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; \
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 */
#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)
#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
*/
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
*/
| 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.
*/
#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.
*/
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 "
&& *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);
&& 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);
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
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,
+ };
/*
* 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)
* 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"));
+ }
}
}
}
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
* 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
* 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,
* 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);
*/
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
--- /dev/null
+#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 */
}
#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 */
}
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);
}
break;
case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
case TLS_LEV_FPRINT:
case TLS_LEV_VERIFY:
case TLS_LEV_SECURE:
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)
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
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)
#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,
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);
}
}
/* 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;
}
} else if (!TLS_DANE_HASEE(dane))
msg_panic("empty DANE match list");
tls->dane = dane;
- tls->level = TLS_LEV_DANE;
return;
}
/* 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.
*/
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