Workaround: don't insert empty-line header/body separator
into malformed MIME attachments, to avoid breaking digital
- signatures. This change introduces ambiguity. Postfix still
- treats the remainder of the attachment as body content;
- header_checks rules will not detect forbidden MIME types
- inside a message/rfc822 attachment. With the empty-line
- header/body separator no longer inserted by Postfix, other
- software may process the malformed attachment differently,
- and thus may become exposed to forbidden MIME types. This
- is back-ported from Postfix 2.4. File: global/mime_state.c.
+ signatures. This change introduces ambiguity. As before,
+ Postfix treats the remainder of the attachment as body
+ content, and header_checks rules will not detect forbidden
+ MIME types inside a malformed message/rfc822 attachment.
+ With the empty-line header/body separator no longer inserted
+ by Postfix, other software may process the malformed
+ attachment differently, and thus may now become exposed to
+ forbidden MIME types. This is back-ported from Postfix
+ 2.4. File: global/mime_state.c.
20070118
Bugfix: match lists didn't implement ![ipv6address]. Problem
reported by Paulo Pacheco. File: util/match_list.c.
+
+20070224
+
+ Workaround: GNU POP3D creates a new mailbox and deletes the
+ old one. Postfix now backs off and retries delivery later,
+ instead of appending mail to a deleted file. File:
+ global/mbox_open.c.
+
+20070225
+
+ Workaround: Disable SSL/TLS ciphers when the underlying
+ symmetric algorithm is not available in the OpenSSL crypto
+ library at the required bit strength. Problem observed with
+ SunOS 5.10's bundled OpenSSL 0.9.7 and AES 256. Also possible
+ with OpenSSL 0.9.8 and CAMELLIA 256. Root cause fixed in
+ upcoming OpenSSL 0.9.7m, 0.9.8e and 0.9.9 releases. Victor
+ Duchovni, Morgan Stanley. Files: src/smtp/smtp_proto.c,
+ src/smtpd/smtpd.c, src/tls/tls.h, src/tls/tls_client.c,
+ src/tls/tls_misc.c and src/tls/tls_server.c.
Postfix no longer inserts an empty-line header/body separator into
malformed MIME attachments, to avoid breaking digital signatures.
-This change introduces ambiguity. Postfix still treats the remainder
-of the attachment as body content; header_checks rules will therefore
-not detect forbidden MIME types inside a message/rfc822 attachment.
+This change introduces ambiguity. As before, Postfix treats the
+remainder of the attachment as body content, and header_checks rules
+will not detect forbidden MIME types inside a malformed message/rfc822
+attachment.
With the empty-line header/body separator no longer inserted by
Postfix, other software may process the malformed attachment
-differently, and thus may become exposed to forbidden MIME types.
+differently, and thus may now become exposed to forbidden MIME types
+that they would not have been exposed to earlier.
Incompatible changes with Postfix 2.3.6
---------------------------------------
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20070130"
-#define MAIL_VERSION_NUMBER "2.3.7"
+#define MAIL_RELEASE_DATE "20070225"
+#define MAIL_VERSION_NUMBER "2.3.8-RC1"
#ifdef SNAPSHOT
# define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
return (0);
}
}
+
+ /*
+ * Sanity check: reportedly, GNU POP3D creates a new mailbox file and
+ * deletes the old one. This does not play well with software that opens
+ * the mailbox first and then locks it.
+ *
+ * To detect that GNU POP3D deletes the mailbox file we look at the target
+ * file hard-link count. Note that safe_open() guarantees a hard-link
+ * count of 1, so any change in this count is a sign of trouble.
+ */
+ if (S_ISREG(st->st_mode)
+ && (fstat(vstream_fileno(fp), st) < 0 || st->st_nlink != 1)) {
+ vstring_sprintf(why->reason, "target file status changed unexpectedly");
+ dsb_status(why, mbox_dsn(EAGAIN, def_dsn));
+ msg_warn("%s: file status changed unexpectedly", path);
+ if (locked & MBOX_DOT_LOCK)
+ dot_unlockfile(path);
+ vstream_fclose(fp);
+ return (0);
+ }
mp = (MBOX *) mymalloc(sizeof(*mp));
mp->path = mystrdup(path);
mp->fp = fp;
vstring_sprintf_append(serverid, "&p=%s",
tls_protocol_names(VAR_SMTP_TLS_MAND_PROTO,
session->tls_protocols));
- if (session->tls_level >= TLS_LEV_ENCRYPT && session->tls_cipherlist)
+ if (session->tls_level >= TLS_LEV_ENCRYPT)
vstring_sprintf_append(serverid, "&c=%s", session->tls_cipherlist);
tls_props.ctx = smtp_tls_ctx;
enforce_tls ? var_smtpd_tls_mand_excl :
TLS_END_EXCLUDE,
TLS_END_EXCLUDE);
+ if (props.cipherlist == 0)
+ msg_panic("NULL export cipherlist");
}
if (havecert || oknocert)
smtpd_tls_ctx = tls_server_init(&props);
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
#define TLS_END_EXCLUDE ((char *)0)
extern const char *tls_cipher_list(int,...);
+extern const char *tls_set_cipher_list(SSL_CTX *, const char *);
/*
* tls_client.c
if (props->log_level >= 1)
msg_info("setting up TLS connection to %s", props->host);
+ /*
+ * Before we create an SSL, update the SSL_CTX cipherlist if necessary.
+ */
+ if (tls_set_cipher_list(props->ctx, props->cipherlist) == 0) {
+ msg_warn("Invalid cipherlist \"%s\": aborting TLS session",
+ props->cipherlist);
+ return (0);
+ }
+
/*
* Allocate a new TLScontext for the new connection and get an SSL
* structure. Add the location of TLScontext to the SSL to later retrieve
}
/*
- * Per session cipher selection for sessions with mandatory encryption
+ * Try to load an existing session from the TLS session cache.
*
* By the time a TLS client is negotiating ciphers it has already offered to
* re-use a session, it is too late to renege on the offer. So we must
* not attempt to re-use sessions whose ciphers are too weak. We expect
* 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);
- }
-
- /*
- * Try to load an existing session from the TLS session cache.
*
* XXX To avoid memory leaks we must always call SSL_SESSION_free() after
* calling SSL_set_session(), regardless of whether or not the session
/*
/* long tls_bug_bits()
/*
+/* const char *tls_set_cipher_list(ssl_ctx, cipher_list)
+/* SSL_CTX *ssl_ctx;
+/* char *cipher_list;
+/*
/* const char *tls_cipher_list(cipher_level, ...)
/* int cipher_level;
/*
/* for the run-time library. Some of the bug work-arounds are
/* not appropriate for some library versions.
/*
+/* tls_set_cipher_list() updates the cipher list of the specified SSL
+/* context. Returns the new cipherlist on success, otherwise logs a
+/* suitable warning and returns 0. The storage for the return value
+/* is overwritted with each call.
+/*
/* 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
#include <mymalloc.h>
#include <vstring.h>
#include <stringops.h>
+#include <argv.h>
/* TLS library. */
int status;
} TLS_VINFO;
+ /*
+ * OpenSSL adopted the cipher selection patch, so we don't expect any more
+ * broken ciphers other than AES and CAMELLIA.
+ */
+typedef struct {
+ char *ssl_name;
+ int alg_bits;
+ char *evp_name;
+} cipher_probe_t;
+
+static cipher_probe_t cipher_probes[] = {
+ "AES", 256, "AES-256-CBC",
+ "CAMELLIA", 256, "CAMELLIA-256-CBC",
+ 0, 0, 0,
+};
+
+/* tls_exclude_missing - Append exclusions for missing ciphers */
+
+static void tls_exclude_missing(SSL_CTX *ctx, VSTRING *buf)
+{
+ const char *myname = "tls_exclude_missing";
+ static ARGV *exclude; /* Cached */
+ SSL *s = 0;
+
+ STACK_OF(SSL_CIPHER) * ciphers;
+ SSL_CIPHER *c;
+ cipher_probe_t *probe;
+ int alg_bits;
+ int num;
+ int i;
+
+ /*
+ * Process a list of probes which specify:
+ *
+ * An SSL cipher-suite name for a family of ciphers that use the same
+ * symmetric algorithm at two or more key sizes, typically 128/256 bits.
+ *
+ * The key size (typically 256) that OpenSSL fails check, and assumes is
+ * available when another key size (typically 128) is usable.
+ *
+ * The OpenSSL name of the symmetric algorithm associated with the SSL
+ * cipher-suite. Typically, this is MUMBLE-256-CBC, where "MUMBLE" is the
+ * name of the SSL cipher-suite that use the MUMBLE symmetric algorithm.
+ * On systems that support the required encryption algorithm, the name is
+ * listed in the output of "openssl list-cipher-algorithms".
+ *
+ * When an encryption algorithm is not available at the given key size but
+ * the corresponding OpenSSL cipher-suite contains ciphers that have have
+ * this key size, the problem ciphers are explicitly disabled in Postfix.
+ * The list is cached in the static "exclude" array.
+ */
+ if (exclude == 0) {
+ exclude = argv_alloc(1);
+
+ /*
+ * Iterate over the probe list
+ */
+ for (probe = cipher_probes; probe->ssl_name; ++probe) {
+ /* No exclusions if evp_name is a valid algorithm */
+ if (EVP_get_cipherbyname(probe->evp_name))
+ continue;
+
+ /*
+ * Sadly there is no SSL_CTX_get_ciphers() interface, so we are
+ * forced to allocate and free an SSL object. Fatal error if we
+ * can't allocate the SSL object.
+ */
+ ERR_clear_error();
+ if (s == 0 && (s = SSL_new(ctx)) == 0) {
+ tls_print_errors();
+ msg_fatal("%s: error allocating SSL object", myname);
+ }
+
+ /*
+ * Cipher is not supported by libcrypto, nothing to do if also
+ * not supported by libssl. Flush the OpenSSL error stack.
+ *
+ * XXX: There may be additional places in pre-existing code where
+ * SSL errors are generated and ignored, that require a similar
+ * "flush". Better yet, is to always flush before calls that run
+ * tls_print_errors() on failure.
+ *
+ * Contrary to documentation, on SunOS 5.10 SSL_set_cipher_list()
+ * returns success with no ciphers selected, when this happens
+ * SSL_get_ciphers() produces a stack with 0 elements!
+ */
+ if (SSL_set_cipher_list(s, probe->ssl_name) == 0
+ || (ciphers = SSL_get_ciphers(s)) == 0
+ || (num = sk_SSL_CIPHER_num(ciphers)) == 0) {
+ ERR_clear_error(); /* flush any generated errors */
+ continue;
+ }
+ for (i = 0; i < num; ++i) {
+ c = sk_SSL_CIPHER_value(ciphers, i);
+ (void) SSL_CIPHER_get_bits(c, &alg_bits);
+ if (alg_bits == probe->alg_bits)
+ argv_add(exclude, SSL_CIPHER_get_name(c), ARGV_END);
+ }
+ }
+ if (s != 0)
+ SSL_free(s);
+ }
+ for (i = 0; i < exclude->argc; ++i)
+ vstring_sprintf_append(buf, ":!%s", exclude->argv[i]);
+}
+
+/* tls_set_cipher_list - Set SSL_CTX cipher list */
+
+const char *tls_set_cipher_list(SSL_CTX *ssl_ctx, const char *spec)
+{
+ static VSTRING *buf;
+ const char *ex_spec;
+
+ if (buf == 0)
+ buf = vstring_alloc(10);
+
+ vstring_strcpy(buf, spec);
+ tls_exclude_missing(ssl_ctx, buf);
+ ex_spec = vstring_str(buf);
+
+ ERR_clear_error();
+ if (SSL_CTX_set_cipher_list(ssl_ctx, ex_spec) != 0)
+ return (ex_spec);
+
+ tls_print_errors();
+ return (0);
+}
+
/* tls_cipher_list - Cipherlist for given grade, less exclusions */
const char *tls_cipher_list(int cipher_level,...)
case TLS_CIPHER_NONE:
return 0;
default:
+
+ /*
+ * The caller MUST provide a valid cipher grade
+ */
msg_panic("%s: invalid cipher grade: %d", myname, cipher_level);
}
+ /*
+ * The base lists for each grade can't be empty.
+ */
if (VSTRING_LEN(buf) == 0)
msg_panic("%s: empty cipherlist", myname);
if (*exclude == '\0')
continue;
save = cp = mystrdup(exclude);
- while ((tok = mystrtok(&cp, "\t\n\r ,")) != 0) {
+ while ((tok = mystrtok(&cp, "\t\n\r ,:")) != 0) {
/*
- * Can't exclude ciphers that start with modifiers, or
- * multi-element (":" separated) ciphers.
+ * Can't exclude ciphers that start with modifiers.
*/
if (strchr("!+-@", *tok)) {
msg_warn("%s: can't exclude '!+-@' modifiers, '%s' ignored",
myname, tok);
continue;
}
- if (strchr(tok, ':')) {
- msg_warn("%s: can't exclude compound ciphers, '%s' ignored",
- myname, tok);
- continue;
- }
vstring_sprintf_append(buf, ":!%s", tok);
}
myfree(save);
/*
* 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);
- }
+ if (tls_set_cipher_list(server_ctx, props->cipherlist) == 0) {
+ SSL_CTX_free(server_ctx);
+ msg_warn("Invalid cipherlist \"%s\": disabling TLS support",
+ props->cipherlist);
+ return (0); /* Already logged */
+ }
/*
* Load the CA public key certificates for both the server cert and for