From: Viktor Dukhovni Date: Thu, 27 Feb 2014 07:40:28 +0000 (-0500) Subject: Configurable fallback to unauthenticated TLS X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=097eca3fe240c1be9a1941b973a359e78fd05fab;p=thirdparty%2Fpostfix.git Configurable fallback to unauthenticated TLS 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. --- diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index 4d314730c..731bec18d 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -631,6 +631,10 @@ while (<>) { s;\bsmtp_starttls_timeout\b;$&;g; s;\bsmtp_tls_CAfile\b;$&;g; s;\bsmtp_tls_CApath\b;$&;g; + s;\bsmtp_tls_fallback_level\b;$&;g; + s;\blmtp_tls_fallback_level\b;$&;g; + s;\bsmtp_tls_audit_template\b;$&;g; + s;\blmtp_tls_audit_template\b;$&;g; s;\bsmtp_tls_cert_file\b;$&;g; s;\bsmtp_tls_fingerprint_digest\b;$&;g; s;\bsmtp_tls_protocols\b;$&;g; diff --git a/postfix/proto/TLS_README.html b/postfix/proto/TLS_README.html index bc492ebd5..af9bd7cf8 100644 --- a/postfix/proto/TLS_README.html +++ b/postfix/proto/TLS_README.html @@ -1373,8 +1373,13 @@ for early adopters.

  • The "example.com" destination uses DANE, but if TLSA records are not present or are unusable, mail is deferred.

    -
  • The "example.org" destination uses DANE if possible, but if no TLSA -records are found opportunistic TLS is used.

    +
  • 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.

    @@ -1394,26 +1399,16 @@ main.cf: # default_transport = smtp, but some destinations are special: # transport_maps = ${indexed}transport - -
    -
    -
     transport:
         example.com dane
         example.org dane
    -
    -
    -
    -
     tls_policy:
         example.com dane-only
    -
    -
    + # Postfix ≥ 2.12, per-destination smtp_tls_fallback_level override + example.org dane fallback=encrypt -
    -
     master.cf:
         dane       unix  -       -       n       -       -       smtp
           -o smtp_dns_support_level=dnssec
    @@ -2146,7 +2141,9 @@ href="#client_tls_encrypt">encrypt.  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.  
    +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.  
     
     
    dane-only
    Mandatory DANE TLS. 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.
    +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.
    fingerprint
    Certificate fingerprint verification. Available with Postfix 2.5 and @@ -2164,13 +2163,14 @@ authorities. The certificate trust chain, expiration date, ... are not checked. Instead, the optional match attribute, or else the main.cf smtp_tls_fingerprint_cert_match 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 -smtp_tls_fingerprint_digest 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.
    +(Postfix 2.9 and later). The digest algorithm used to calculate +fingerprints is selected by the smtp_tls_fingerprint_digest +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.
    verify
    Mandatory server certificate verification. 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 ≥ 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.
    +"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.
    secure
    Secure certificate verification. 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.
    +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. @@ -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
    diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index c26f1a5da..4c285eefa 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -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

    Note: The hostname strategy if listed in a non-default @@ -16184,3 +16184,132 @@ mail.

    This feature is available in Postfix 2.12 and later.

    + +%PARAM smtp_tls_fallback_level + +

    Optional fallback levels for authenticated TLS levels. Specify +a white-space or comma-separate list of +policy_level=fallback_level pairs. The policy_level +must require authentication (be one of dane, dane-only, fingerprint, +verify, secure). The fallback_level 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.

    + +

    The TLS policy 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.

    + +

    Example:

    + +
    +
    +/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
    +
    +
    + +
    +
    +/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
    +
    +
    + +

    This feature is available in Postfix 2.12 and later.

    + +%PARAM lmtp_tls_fallback_level + +

    The LMTP-specific version of the smtp_tls_fallback_level +configuration parameter. See there for details.

    + +

    This feature is available in Postfix 2.12 and later.

    + +%PARAM smtp_tls_audit_template + +

    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.

    + +

    The following $name expansions are done on smtp_tls_audit_template:

    + +
    + +
    $relay
    +
    The remote SMTP server.
    + +
    $level
    +
    The effective TLS security level after any fallback.
    + +
    $policy
    +
    The desired TLS security level before any fallback, undefined +if no fallback took place.
    + +
    $auth
    +
    The authentication level of the remote SMTP server. One of +"Cleartext", "Anonymous", "Untrusted", "Trusted" or "Verified". +
    + +
    $protocol
    +
    The TLS protocol version, defined only when TLS is used.
    + +
    $cipher
    +
    The TLS cipher name, defined only when TLS is used.
    + +
    $cert_digest
    +
    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. +
    + +
    $spki_digest
    +
    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.
    + +
    ${name?value}
    + +
    Expands to value when $name is non-empty.
    + +
    ${name:value}
    + +
    Expands to value when $name is empty.
    + +
    + +

    Example:

    + +
    +/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}}
    +
    + +

    This feature is available in Postfix 2.12 and later.

    + +%PARAM lmtp_tls_audit_template + +

    The LMTP-specific version of the smtp_tls_audit_template +configuration parameter. See there for details.

    + +

    This feature is available in Postfix 2.12 and later.

    diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 3b402a708..494112014 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -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. */ diff --git a/postfix/src/smtp/Makefile.in b/postfix/src/smtp/Makefile.in index fdbab2f97..e271bf7d4 100644 --- a/postfix/src/smtp/Makefile.in +++ b/postfix/src/smtp/Makefile.in @@ -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 diff --git a/postfix/src/smtp/lmtp_params.c b/postfix/src/smtp/lmtp_params.c index 1861e5ba5..de2f75ad8 100644 --- a/postfix/src/smtp/lmtp_params.c +++ b/postfix/src/smtp/lmtp_params.c @@ -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, @@ -119,3 +120,8 @@ 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, + }; diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c index 158c273e6..08ad2e534 100644 --- a/postfix/src/smtp/smtp.c +++ b/postfix/src/smtp/smtp.c @@ -462,6 +462,13 @@ /* 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, diff --git a/postfix/src/smtp/smtp.h b/postfix/src/smtp/smtp.h index c21aa65a5..14ae12d56 100644 --- a/postfix/src/smtp/smtp.h +++ b/postfix/src/smtp/smtp.h @@ -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. */ diff --git a/postfix/src/smtp/smtp_connect.c b/postfix/src/smtp/smtp_connect.c index acff1eba4..e802fd93d 100644 --- a/postfix/src/smtp/smtp_connect.c +++ b/postfix/src/smtp/smtp_connect.c @@ -108,6 +108,13 @@ #include #include +#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 diff --git a/postfix/src/smtp/smtp_params.c b/postfix/src/smtp/smtp_params.c index 807215dba..93144adda 100644 --- a/postfix/src/smtp/smtp_params.c +++ b/postfix/src/smtp/smtp_params.c @@ -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, @@ -123,3 +124,8 @@ 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, + }; diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index 53e4c1496..019776b94 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -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 index 000000000..3d462a106 --- /dev/null +++ b/postfix/src/smtp/smtp_tls_audit.c @@ -0,0 +1,126 @@ +#ifdef USE_TLS + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* 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 */ diff --git a/postfix/src/smtp/smtp_tls_policy.c b/postfix/src/smtp/smtp_tls_policy.c index 59118f2b3..ca401716d 100644 --- a/postfix/src/smtp/smtp_tls_policy.c +++ b/postfix/src/smtp/smtp_tls_policy.c @@ -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; } diff --git a/postfix/src/smtp/smtp_trouble.c b/postfix/src/smtp/smtp_trouble.c index 2262e6cf0..622aeac2d 100644 --- a/postfix/src/smtp/smtp_trouble.c +++ b/postfix/src/smtp/smtp_trouble.c @@ -32,6 +32,10 @@ /* 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 @@ -105,6 +109,13 @@ /* 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