-TXSASL_SERVER
-TXSASL_SERVER_IMPL
-TXSASL_SERVER_IMPL_INFO
+-Tcipher_probe
-Tregex_t
-Tregmatch_t
-Tsasl_conn_t
Duchovni. Files: smtpd/smtpd_check.c.
Workaround: don't insert header/body blank line separator
- in malformed attachments, to avoid breaking digital signatures.
- Switch from header to body state, for robust MIME parsing.
- People concerned about MIME evasion can use a MIME normalizer
- to corrupt their user's legitimate email. File:
- global/mime_state.c.
+ into malformed MIME attachments, to avoid breaking digital
+ signatures. File: global/mime_state.c.
+
+20070118
+
+ Bugfix: match lists didn't implement ![ipv6address]. Problem
+ reported by Paulo Pacheco. File: util/match_list.c.
+
+200070129
+
+ Workaround: OpenSSL falsely concludes that AES256 support
+ is present when only AES128 is available. Code by Victor
+ Duchovni. File: tls/tls_misc.c.
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20070113"
-#define MAIL_VERSION_NUMBER "2.3.7-RC2"
+#define MAIL_RELEASE_DATE "20070129"
+#define MAIL_VERSION_NUMBER "2.3.7-RC3"
#ifdef SNAPSHOT
# define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
/* Volatile members. */
state->err_flags = 0;
+ state->body_offset = 0; /* XXX */
SET_MIME_STATE(state, MIME_STATE_PRIMARY,
MIME_CTYPE_TEXT, MIME_STYPE_PLAIN,
MIME_ENC_7BIT, MIME_ENC_7BIT);
* messages. Otherwise, treat such headers as part of the "body". Set
* the proper encoding information for the multipart prolog.
*
+ * XXX We parse headers inside message/* content even when the encoding
+ * is invalid (encoding != domain). With base64 we won't recognize
+ * any headers, and with quoted-printable we won't recognize MIME
+ * boundary strings, but the MIME processor will still resynchronize
+ * when it runs into the higher-level boundary string at the end of
+ * the message/* content. Although we will treat some headers as body
+ * text, we will still do a better job than if we were treating the
+ * entire message/* content as body text.
+ *
* XXX This changes state to MIME_STATE_NESTED and then outputs a body
* line, so that the body offset is not properly reset.
*
* separator, nor does the Milter protocol pass it on to Milter
* applications.
*
- * XXX We don't insert a blank line separator with attachments, as
- * this breaks digital signatures. Postfix shall not do a worse
- * mail delivery job than crappy MTAs that can't even parse MIME.
- * But we switch to the body state anyway.
+ * XXX We don't insert a blank line separator into attachments, to
+ * avoid breaking digital signatures. Postfix shall not do a
+ * worse mail delivery job than MTAs that can't even parse MIME.
+ * We switch to body state anyway, to avoid treating body text as
+ * header text, and mis-interpreting or truncating it. The code
+ * below for initial From_ lines is for educational purposes.
*
- * People who worry about MIME evasion can use a MIME normalizer,
- * and knowlingly corrupt legitimate email for their users.
+ * Sites concerned about MIME evasion can use a MIME normalizer.
* Postfix has a different mission.
*/
else {
state->curr_state == MIME_STATE_PRIMARY ? "primary" :
state->curr_state == MIME_STATE_NESTED ? "nested" :
"other");
- if (state->curr_state == MIME_STATE_PRIMARY)
+ switch (state->curr_state) {
+ case MIME_STATE_PRIMARY:
BODY_OUT(state, REC_TYPE_NORM, "", 0);
- SET_CURR_STATE(state, MIME_STATE_BODY);
+ SET_CURR_STATE(state, MIME_STATE_BODY);
+ break;
+#if 0
+ case MIME_STATE_NESTED:
+ if (state->body_offset <= 1
+ && rec_type == REC_TYPE_NORM
+ && len > 7
+ && (strncmp(text + (*text == '>'), "From ", 5) == 0
+ || strncmp(text, "=46rom ", 7) == 0))
+ break;
+ /* FALLTHROUGH */
+#endif
+ default:
+ SET_CURR_STATE(state, MIME_STATE_BODY);
+ break;
+ }
}
}
/* and REC_FLAG_DEFAULT for normal use.
/*
/* rec_get() is a wrapper around rec_get_raw() that always
-/* enables the REC_FLAG_FOLLOW_PTR and REC_FLAG_SKIP_DTXT
-/* features.
+/* enables the REC_FLAG_FOLLOW_PTR, REC_FLAG_SKIP_DTXT
+/* and REC_FLAG_SEEK_END features.
/*
/* rec_put() stores the specified record and returns the record
/* type, or REC_TYPE_ERROR in case of problems.
smtp_chat.o: ../../include/dsn_buf.h
smtp_chat.o: ../../include/dsn_util.h
smtp_chat.o: ../../include/htable.h
+smtp_chat.o: ../../include/int_filt.h
smtp_chat.o: ../../include/line_wrap.h
smtp_chat.o: ../../include/mail_addr.h
smtp_chat.o: ../../include/mail_error.h
* OpenSSL client state.
*/
SSL_CTX *smtp_tls_ctx;
+int smtp_tls_mand_level;
#endif
props.CAfile = var_smtp_tls_CAfile;
props.CApath = var_smtp_tls_CApath;
+ /*
+ * If the administrator set an invalid grade, use "medium" instead.
+ * The TLS library requires a valid setting.
+ */
+ smtp_tls_mand_level = tls_cipher_level(var_smtp_tls_mand_ciph);
+ if (smtp_tls_mand_level == TLS_CIPHER_NONE) {
+ smtp_tls_mand_level = TLS_CIPHER_MEDIUM;
+ msg_warn("invalid '%s' value '%s', using 'medium'",
+ strcmp(var_procname, "smtp") == 0 ?
+ VAR_SMTP_TLS_MAND_CIPH : VAR_LMTP_TLS_MAND_CIPH,
+ var_smtp_tls_mand_ciph);
+ }
smtp_tls_ctx = tls_client_init(&props);
smtp_tls_list_init();
#else
#ifdef USE_TLS
extern SSL_CTX *smtp_tls_ctx; /* client-side TLS engine */
+extern int smtp_tls_mand_level; /* TLS_CIPHER_EXPORT, ... */
#endif
case TLS_LEV_ENCRYPT:
also_exclude = "eNULL";
if (cipher_level == TLS_CIPHER_NONE)
- cipher_level = tls_cipher_level(var_smtp_tls_mand_ciph);
+ cipher_level = smtp_tls_mand_level;
mand_exclude = var_smtp_tls_mand_excl;
break;
case TLS_LEV_SECURE:
also_exclude = "aNULL";
if (cipher_level == TLS_CIPHER_NONE)
- cipher_level = tls_cipher_level(var_smtp_tls_mand_ciph);
+ cipher_level = smtp_tls_mand_level;
mand_exclude = var_smtp_tls_mand_excl;
break;
}
- cipherlist = tls_cipher_list(cipher_level, exclude, mand_exclude,
- also_exclude, TLS_END_EXCLUDE);
- if (cipherlist == 0) {
- msg_warn("unknown '%s' value '%s' ignored, using 'medium'",
- lmtp ? VAR_LMTP_TLS_MAND_CIPH : VAR_SMTP_TLS_MAND_CIPH,
- var_smtp_tls_mand_ciph);
- cipherlist = tls_cipher_list(TLS_CIPHER_MEDIUM, exclude, mand_exclude,
- also_exclude, TLS_END_EXCLUDE);
- if (cipherlist == 0)
- msg_panic("NULL medium cipherlist");
- }
+ cipherlist = tls_cipher_list(cipher_level, TLS_CIPH_EXCL_LIST,
+ exclude, mand_exclude, also_exclude,
+ TLS_CIPH_EXCL_END);
session->tls_cipherlist = mystrdup(cipherlist);
}
if (use_tls) {
#ifdef USE_TLS
tls_server_props props;
+ ARGV *cipher_exclusions;
int havecert;
int oknocert;
int wantcert;
if (!enforce_tls && var_smtpd_tls_req_ccert)
msg_warn("Can't require client certs unless TLS is required");
- props.cipherlist =
- tls_cipher_list(enforce_tls ?
- tls_cipher_level(var_smtpd_tls_mand_ciph) :
- TLS_CIPHER_EXPORT,
- var_smtpd_tls_excl_ciph,
- havecert ? "" : "aRSA aDSS",
- wantcert ? "aNULL" : "",
- enforce_tls ? var_smtpd_tls_mand_excl :
- TLS_END_EXCLUDE,
- TLS_END_EXCLUDE);
-
- if (props.cipherlist == 0) {
- msg_warn("unknown '%s' value '%s' ignored, using 'export'",
- VAR_SMTPD_TLS_MAND_CIPH, var_smtpd_tls_mand_ciph);
- props.cipherlist =
- tls_cipher_list(TLS_CIPHER_EXPORT,
- var_smtpd_tls_excl_ciph,
- havecert ? "" : "aRSA aDSS",
- wantcert ? "aNULL" : "",
- enforce_tls ? var_smtpd_tls_mand_excl :
- TLS_END_EXCLUDE,
- TLS_END_EXCLUDE);
- }
- if (havecert || oknocert)
+ if (havecert || oknocert) {
+ cipher_exclusions = argv_alloc(3);
+ argv_add(cipher_exclusions, var_smtpd_tls_excl_ciph, ARGV_END);
+ if (wantcert)
+ argv_add(cipher_exclusions, "aNULL", ARGV_END);
+
+ /*
+ * Detect problem configurations early, a certificate-less
+ * handshake can't use ciphers that need server certificates,
+ * so we want to fail now while setting up the cipherlist,
+ * not later. Also this detects any conflict between wantcert
+ * and !havecert.
+ */
+ if (!havecert)
+ argv_add(cipher_exclusions, "aRSA", "aDSS", ARGV_END);
+ if (enforce_tls) {
+ argv_add(cipher_exclusions,
+ var_smtpd_tls_mand_excl, ARGV_END);
+
+ /*
+ * If the administrator set an invalid grade, use
+ * "medium" instead. The TLS library requires a valid
+ * setting.
+ */
+ props.cipher_level =
+ tls_cipher_level(var_smtpd_tls_mand_ciph);
+ if (props.cipher_level == TLS_CIPHER_NONE) {
+ props.cipher_level = TLS_CIPHER_MEDIUM;
+ msg_warn("invalid '%s' value '%s', using 'medium'",
+ VAR_SMTPD_TLS_MAND_CIPH,
+ var_smtpd_tls_mand_ciph);
+ }
+ } else
+ props.cipher_level = TLS_CIPHER_EXPORT;
+ props.cipher_exclusions = cipher_exclusions->argv;
smtpd_tls_ctx = tls_server_init(&props);
- else if (enforce_tls)
+ argv_free(cipher_exclusions);
+ } else if (enforce_tls)
msg_fatal("No server certs available. TLS can't be enabled");
else
msg_warn("No server certs available. TLS won't be enabled");
>>> mail sname@sdomain
OK
>>> rcpt rname@rdomain
-./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 Service unavailable; Helo command [example.tld] blocked using abuse.rfc-ignorant.org; Not supporting abuse@example.tld; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<example.tld>
-554 5.7.1 Service unavailable; Helo command [example.tld] blocked using abuse.rfc-ignorant.org; Not supporting abuse@example.tld
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 5.7.1 Service unavailable; Helo command [example.tld] blocked using abuse.rfc-ignorant.org; Not supporting abuse@domain; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<example.tld>
+554 5.7.1 Service unavailable; Helo command [example.tld] blocked using abuse.rfc-ignorant.org; Not supporting abuse@domain
>>> #
>>> # Check MX access
>>> #
@$(EXPORT) make -f Makefile.in Makefile 1>&2
# do not edit below this line - it is generated by 'make depend'
+tls_bio_ops.o: ../../include/argv.h
tls_bio_ops.o: ../../include/iostuff.h
tls_bio_ops.o: ../../include/msg.h
tls_bio_ops.o: ../../include/name_code.h
tls_bio_ops.o: ../../include/vstring.h
tls_bio_ops.o: tls.h
tls_bio_ops.o: tls_bio_ops.c
+tls_certkey.o: ../../include/argv.h
tls_certkey.o: ../../include/msg.h
tls_certkey.o: ../../include/name_code.h
tls_certkey.o: ../../include/name_mask.h
tls_client.o: tls.h
tls_client.o: tls_client.c
tls_client.o: tls_mgr.h
+tls_dh.o: ../../include/argv.h
tls_dh.o: ../../include/msg.h
tls_dh.o: ../../include/name_code.h
tls_dh.o: ../../include/name_mask.h
tls_dh.o: ../../include/vstring.h
tls_dh.o: tls.h
tls_dh.o: tls_dh.c
+tls_level.o: ../../include/argv.h
tls_level.o: ../../include/name_code.h
tls_level.o: ../../include/name_mask.h
tls_level.o: ../../include/sys_defs.h
tls_mgr.o: ../../include/vstring.h
tls_mgr.o: tls_mgr.c
tls_mgr.o: tls_mgr.h
+tls_misc.o: ../../include/argv.h
tls_misc.o: ../../include/msg.h
tls_misc.o: ../../include/mymalloc.h
tls_misc.o: ../../include/name_code.h
tls_prng_file.o: ../../include/sys_defs.h
tls_prng_file.o: tls_prng.h
tls_prng_file.o: tls_prng_file.c
+tls_rsa.o: ../../include/argv.h
tls_rsa.o: ../../include/name_code.h
tls_rsa.o: ../../include/name_mask.h
tls_rsa.o: ../../include/sys_defs.h
tls_scache.o: ../../include/vstring.h
tls_scache.o: tls_scache.c
tls_scache.o: tls_scache.h
+tls_seed.o: ../../include/argv.h
tls_seed.o: ../../include/msg.h
tls_seed.o: ../../include/name_code.h
tls_seed.o: ../../include/name_mask.h
tls_server.o: tls.h
tls_server.o: tls_mgr.h
tls_server.o: tls_server.c
+tls_session.o: ../../include/argv.h
tls_session.o: ../../include/msg.h
tls_session.o: ../../include/mymalloc.h
tls_session.o: ../../include/name_code.h
tls_session.o: ../../include/vstring.h
tls_session.o: tls.h
tls_session.o: tls_session.c
+tls_stream.o: ../../include/argv.h
tls_stream.o: ../../include/iostuff.h
tls_stream.o: ../../include/msg.h
tls_stream.o: ../../include/name_code.h
tls_stream.o: ../../include/vstring.h
tls_stream.o: tls.h
tls_stream.o: tls_stream.c
+tls_verify.o: ../../include/argv.h
tls_verify.o: ../../include/msg.h
tls_verify.o: ../../include/mymalloc.h
tls_verify.o: ../../include/name_code.h
#include <vstream.h>
#include <name_mask.h>
#include <name_code.h>
+#include <argv.h>
#define TLS_BIO_BUFSIZE 8192
#define tls_cipher_level(str) \
name_code(tls_cipher_level_table, NAME_CODE_FLAG_NONE, (str))
-#define TLS_END_EXCLUDE ((char *)0)
-extern const char *tls_cipher_list(int,...);
+#define TLS_CIPH_EXCL_ARRAY 1
+#define TLS_CIPH_EXCL_LIST 2
+#define TLS_CIPH_EXCL_END ((char *) 0)
+extern const char *tls_cipher_list(int, int,...);
/*
* tls_client.c
const char *dkey_file;
const char *CAfile;
const char *CApath;
- const char *cipherlist;
+ int cipher_level; /* TLS_CIPHER_EXPORT, ... */
+ char **cipher_exclusions;
int protocols; /* protocols, 0 => all */
const char *dh1024_param_file;
const char *dh512_param_file;
* the caller to salt the session lookup key with the cipher list, so
* that sessions found in the cache are always acceptable.
*/
- if (props->cipherlist != 0)
- if (SSL_set_cipher_list(TLScontext->con, props->cipherlist) == 0) {
- msg_warn("Could not set cipherlist: %s", props->cipherlist);
- tls_print_errors();
- tls_free_context(TLScontext);
- return (0);
- }
+ if (SSL_set_cipher_list(TLScontext->con, props->cipherlist) == 0) {
+ msg_warn("Could not set cipherlist: %s", props->cipherlist);
+ tls_print_errors();
+ tls_free_context(TLScontext);
+ return (0);
+ }
/*
* Try to load an existing session from the TLS session cache.
/*
/* long tls_bug_bits()
/*
-/* const char *tls_cipher_list(cipher_level, ...)
+/* const char *tls_cipher_list(cipher_level, options, ...)
/* int cipher_level;
+/* int options;
/*
/* void tls_print_errors()
/*
/* tls_cipher_list() generates a cipher list from the specified
/* grade, minus any ciphers specified via a null-terminated
/* list of string-valued exclusions. The result is overwritten
-/* upon each call.
+/* upon each call. The options argument specifies how exceptions
+/* are specified: TLS_CIPH_EXCL_ARRAY (null-terminated character
+/* pointer array) or TLS_CIPH_EXCL_LIST (variadic parameter
+/* list terminated with TLS_CIPH_EXCL_END).
/*
/* tls_print_errors() queries the OpenSSL error stack,
/* logs the error messages, and clears the error stack.
0, TLS_CIPHER_NONE,
};
+typedef struct {
+ char *algorithm;
+ char *exclusion;
+} cipher_probe;
+
+static cipher_probe cipher_probe_list[] = {
+
+ /*
+ * Check for missing AES256, OpenSSL only checks for AES128, and then
+ * enables both, because they only have one "is AES" boolean flag in the
+ * cipher property mask. The implementation cannot distinguish between
+ * AES128 and AES256. When some O/S distributions play games with
+ * libcrypto and exclude just the AES256 ciphers, they break the OpenSSL
+ * cipherlist construction code, with clients and servers potentially
+ * negotiating unimplemented ciphers.
+ *
+ * This problem is peculiar to AES, which is not a single cipher, but a
+ * family of related ciphers. The other OpenSSL symmetric ciphers are
+ * atomic, either implemented or not. We expect that future ciphers will
+ * either also be atomic, or will have one property bit per family member
+ * and will be filtered accurately by OpenSSL.
+ *
+ * If all else fails, this table can be expanded :-(
+ *
+ * XXX: the probe for AES256 is enclosed in #ifdef. OpenSSL 0.9.6 and and
+ * earlier don't have AES 256, this requires 0.9.7 or later. We recommend
+ * against use of 0.9.6, it has open issues solved in 0.9.7l and 0.9.8d,
+ * but we are not yet prepared to drop support for 0.9.6.
+ */
+#ifdef SN_aes_256_cbc
+ SN_aes_256_cbc, SSL_TXT_AES "+HIGH",
+#endif
+ 0, 0,
+};
+
/*
* Parsed OpenSSL version number.
*/
/* tls_cipher_list - Cipherlist for given grade, less exclusions */
-const char *tls_cipher_list(int cipher_level,...)
+const char *tls_cipher_list(int cipher_level, int options,...)
{
const char *myname = "tls_cipher_list";
static VSTRING *buf;
+ static ARGV *exclude_unavailable;
+ cipher_probe *probe;
+ int i;
va_list ap;
const char *exclude;
char *tok;
char *save;
char *cp;
+ char **ex_array = 0;
buf = buf ? buf : vstring_alloc(10);
VSTRING_RESET(buf);
case TLS_CIPHER_NULL:
vstring_strcpy(buf, var_tls_null_clist);
break;
- case TLS_CIPHER_NONE:
- return 0;
default:
msg_panic("%s: invalid cipher grade: %d", myname, cipher_level);
}
-
if (VSTRING_LEN(buf) == 0)
msg_panic("%s: empty cipherlist", myname);
- va_start(ap, cipher_level);
- while ((exclude = va_arg(ap, char *)) != 0) {
+ /*
+ * Exclude ciphers that clueless distributions leave out of libcrypto.
+ */
+ if (exclude_unavailable == 0) {
+ exclude_unavailable = argv_alloc(1);
+ for (probe = cipher_probe_list; probe->algorithm; ++probe)
+ if (!EVP_get_cipherbyname(probe->algorithm))
+ argv_add(exclude_unavailable, probe->exclusion, (char *) 0);
+ }
+ for (i = 0; i < exclude_unavailable->argc; ++i)
+ vstring_sprintf_append(buf, ":!%s", exclude_unavailable->argv[i]);
+
+ va_start(ap, options);
+ if (options == TLS_CIPH_EXCL_ARRAY)
+ ex_array = va_arg(ap, char **);
+ else if (options != TLS_CIPH_EXCL_LIST)
+ msg_panic("%s: bad argument list option: %d", myname, options);
+ while ((exclude = ex_array ? *ex_array++ : va_arg(ap, char *)) != 0) {
if (*exclude == '\0')
continue;
save = cp = mystrdup(exclude);
int verify_flags = SSL_VERIFY_NONE;
SSL_CTX *server_ctx;
int cachable;
+ const char *cipher_list;
/* See skeleton at OpenSSL apps/s_server.c. */
/*
* Override the default cipher list with our own list.
*/
- if (*props->cipherlist != 0)
- if (SSL_CTX_set_cipher_list(server_ctx, props->cipherlist) == 0) {
- tls_print_errors();
- SSL_CTX_free(server_ctx); /* 200411 */
- return (0);
- }
+ cipher_list = tls_cipher_list(props->cipher_level, TLS_CIPH_EXCL_ARRAY,
+ props->cipher_exclusions);
+ if (SSL_CTX_set_cipher_list(server_ctx, cipher_list) == 0) {
+ tls_print_errors();
+ msg_warn("Invalid cipherlist: %s", cipher_list);
+ SSL_CTX_free(server_ctx); /* 200411 */
+ return (0);
+ }
/*
* Load the CA public key certificates for both the server cert and for
};
#define MATCH_DICTIONARY(pattern) \
- ((pattern)[0] != '[' && strchr((pattern), ':') != 0)
+ ((pattern + strspn(pattern, "!"))[0] != '[' && strchr((pattern), ':') != 0)
/* match_list_parse - parse buffer, destroy buffer */