in the anvil server, TLS request rate error message in the
smtp server, and documentation, but no changes in code.
Files: anvil/anvil.c, smtpd/smtpd.c.
-
+
20051013
Horror: some systems have basename() and dirname() and some
smtp/smtp.h, smtp/smtp_rcpt.c.
Logging: the TLS client logged that an "Untrusted" TLS
- connection was established instead of "Anonymous".
- Viktor Dukhovni. File: tls/tls_client.c.
+ connection was established instead of "Anonymous". Viktor
+ Dukhovni. File: tls/tls_client.c.
Documentation: new self-signed certificate example and
updated private CA example. File: proto/TLS_README.html.
20140323
Feature: initial merge of Debian-style dynamic linking.
- Viktor Dukhovni.
+ Viktor Dukhovni.
20140406
20140508
Cleanup: dynamicmaps.cf is now installed into $daemon_directory
- because the file is shared among Postfix instances just like
- postfix-files and other files. Files: conf/dynamicmaps.cf,
+ because the file is shared among Postfix instances just
+ like postfix-files and other files. Files: conf/dynamicmaps.cf,
Makefile.in, conf/postfix-files.
Cleanup: INSTALL is now plain ASCII instead of README format,
Support for "make shared=yes" and "make dynamicmaps=yes".
New plugin_directory parameter for the location of the
dynamicmaps.cf file and for plugins with a relative pathname.
- See RELEASE_NOTES and INSTALL for details. Files:
- postfix.c, mail_params.[hc], dynamicmaps.c, mail_dict.c,
- makedefs, postfix-files, dynamicmaps.cf, Makefile.in,
- util/Makefile.in, global/Makefile.in, postlink, postconf.proto.
- INSTALL.html, RELEASE_NOTES.
+ See RELEASE_NOTES and INSTALL for details. Files: postfix.c,
+ mail_params.[hc], dynamicmaps.c, mail_dict.c, makedefs,
+ postfix-files, dynamicmaps.cf, Makefile.in, util/Makefile.in,
+ global/Makefile.in, postlink, postconf.proto. INSTALL.html,
+ RELEASE_NOTES.
20140523
Cleanup: change extract_addr() API to indicate that an
address is parsed in SMTPUTF8 context. File: smtpd/smtpd.c.
- Cleanup: shared-library build fixes. Viktor Dukhovni.
- Files: makedefs, dns/Makefile.in, global/Makefile.in,
- master/Makefile.in, tls/Makefile.in, util/Makefile.in.
+ Cleanup: shared-library build fixes. Viktor Dukhovni. Files:
+ makedefs, dns/Makefile.in, global/Makefile.in, master/Makefile.in,
+ tls/Makefile.in, util/Makefile.in.
First general release with SMTPUTF8 support; see RELEASE_NOTES
for an initial writeup. The last pre-SMTPUTF8 release is
20140731
- Feature: the Postfix SMTP server now logs at the end of
- a session how many times each SMTP command was successfully
+ Feature: the Postfix SMTP server now logs at the end of a
+ session how many times each SMTP command was successfully
invoked, followed by the total number of invocations if it
is different. File: smtpd/smtpd.c.
Cleanup: dict_db and dict_lmdb global settings. Files:
global/mail_params.c, util/dict_open.c.
-
+
Feature: unionmap, based on contribution by Roel van Meer.
Files: mantools/postlink, postconf/postconf.c (manpage),
proto/DATABASE_README.html, util/dict_open.c, util/dict_union.[hc].
20140927
- Cleanup: specify { name = value } in per-Milter settings, to support
- space around the "=" or comma/space within the value. Files:
- global/attr_over.[hc].
+ Cleanup: specify { name = value } in per-Milter settings,
+ to support space around the "=" or comma/space within the
+ value. Files: global/attr_over.[hc].
Cleanup: "postconf -n" now only shows config_directory when
an override is in effect (environment, -c or -o).
Cleanup: force LANG=C to prevent groff from outputting
non-ASCII cruft into the HTML-ized manpages. Files:
- html/Makefile.in, proto/Makefile.in, many HTML output
- files.
+ html/Makefile.in, proto/Makefile.in, many HTML output files.
20140929
Cleanup: the table-driven code for per-Milter and per-policy
- overrides now updates arbitrary variables, so that it can also
- be used for, say, TLS policies. Files: global/attr_override.[hc],
- smtpd/smtpd_check.c, milter/milter.c.
+ overrides now updates arbitrary variables, so that it can
+ also be used for, say, TLS policies. Files:
+ global/attr_override.[hc], smtpd/smtpd_check.c, milter/milter.c.
Documentation: support for "{ argument with whitespace }"
in master(5) and pipe(8). Files: proto/master, src/pipe/pipe.c.
20141001
- Safety: backwards-compatibility safety net that forces Postfix
- to run with backwards-compatible default settings after an
- upgrade to a newer Postfix version. Postfix logs all uses
- of those backwards-compatible default settings so that the
- system administator can determine whether or not some
- backwards-compatible default settings need to be made
+ Safety: backwards-compatibility safety net that forces
+ Postfix to run with backwards-compatible default settings
+ after an upgrade to a newer Postfix version. Postfix logs
+ all uses of those backwards-compatible default settings so
+ that the system administator can determine whether or not
+ some backwards-compatible default settings need to be made
permanent in main.cf or master.cf. All this is controlled
with a new compatibility_level parameter, default value 0.
Files: global/mail_params.[hc], trivial-rewrite/rewrite.c,
20141003
- Workaround: kludge for multiple paragraphs of text in indented
- paragraphs. Files: mantools/postconf2html, mantools/postconf2man,
- proto/Makefile.in, proto/postconf.proto
+ Workaround: kludge for multiple paragraphs of text in
+ indented paragraphs. Files: mantools/postconf2html,
+ mantools/postconf2man, proto/Makefile.in, proto/postconf.proto
20141005
a new COMPATIBILITY_README document. Files: proto/postconf.proto,
proto/COMPATIBILITY_README.html html/index.html.
- Documentation: update the conf/main.cf compatibility_level setting
- for new Postfix installs, and updated a reminder in mail_params.h.
+ Documentation: update the conf/main.cf compatibility_level
+ setting for new Postfix installs, and updated a reminder
+ in mail_params.h.
20141010
about that later. Files: smtpd/smtpd_check.c, smtp/smtp_addr.c,
dns/dns.h, dns/dns_lookup.c.
- Cleanup: eliminate TLS state duplication from state->tls
+ Cleanup: eliminate TLS state duplication from state->tls
to session->tls. Viktor Dukhovni. Files: src/smtp/smtp.h,
src/smtp/smtp_connect.c, src/smtp/smtp_proto.c,
src/smtp/smtp_reuse.c, src/smtp/smtp_session.c.
20141208
- Bugfix (introduced: 20141207): in new #ifdef, && should be ||.
- File: smtpd.c.
+ Bugfix (introduced: 20141207): in new #ifdef, && should be
+ ||. File: smtpd.c.
20141210
Cleanup: instead of making up new names, use a consistent
CA_ prefix for macros that implement compile-time argument
- typechecks for non-protocol attribute-value APIs. This
+ typechecks for non-protocol attribute-value APIs. This
transformation and its verification are mechanical.
Bugfix (introduced: Postfix 1.1, but latent before 2.12):
20141228
- Cleanup: the IDNA conversion routines now accept both
- ASCII and UTF8 inputs. The functions als verify that
- either their result is a valid ASCII domain name or that
- it converts into a valid ASCII domain name. Files:
- util/midna.c, util/midna_test.in, util/midna_test.ref.
+ Cleanup: the IDNA conversion routines now accept both ASCII
+ and UTF8 inputs. The functions als verify that either their
+ result is a valid ASCII domain name or that it converts
+ into a valid ASCII domain name. Files: util/midna.c,
+ util/midna_test.in, util/midna_test.ref.
20141230
byte values, and UTF-8 case folding. As recommended at
http://www.w3.org/International/wiki/Case_folding for
caseless string comparison, this uses the en_US locale to
- avoid surprises. The implementatin handles
- the entire RFC 3629 Unicode range (code points U+0000..U+10FFFF
- including surrogates) and is chroot(2) safe. Files: casefold.c, stringops.h.
+ avoid surprises. The implementatin handles the entire RFC
+ 3629 Unicode range (code points U+0000..U+10FFFF including
+ surrogates) and is chroot(2) safe. Files: casefold.c,
+ stringops.h.
Infrastructure: revised the midna_domain_to_ascii and
midna_domain_to_utf8 domain name conversion functions after
Cleanup: missing " in \%s\" in postscreen(8) fatal error
messages. Iain Hibbert. File: postconf/postconf_master.c.
+
+20150118
+
+ Bugfix (introduced: 20140731): when a connection timed out
+ before any command was received, the Postfix SMTP server
+ "disconnect from" logging would show the content of the
+ last SMTP server response (421 4.4.2 $myhostname error:
+ timeout exceeded) instead of per-command statistics, because
+ there were no statistics to report. The Postfix SMTP server
+ now always logs the total number of commands (commands=x/y)
+ even when the client did not send any. This helps logfile
+ analyzers to recognize sessions without commands. File:
+ smtpd/smtpd.c.
the backwards-compatible setting "relay_domains = $mydestination" permanent in
main.cf:
- # p\bpo\bos\bst\btc\bco\bon\bnf\bf r\bre\bel\bla\bay\by_\b_d\bdo\bom\bma\bai\bin\bns\bs=\b=$\b$m\bmy\byd\bde\bes\bst\bti\bin\bna\bat\bti\bio\bon\bn
+ # p\bpo\bos\bst\btc\bco\bon\bnf\bf '\b'r\bre\bel\bla\bay\by_\b_d\bdo\bom\bma\bai\bin\bns\bs=\b=$\b$m\bmy\byd\bde\bes\bst\bti\bin\bna\bat\bti\bio\bon\bn'\b'
# p\bpo\bos\bst\btf\bfi\bix\bx r\bre\bel\blo\boa\bad\bd
+Note: quotes are required as indicated above.
+
Instead of $mydestination, it may be better to specify an explicit list of
domain names.
Things to do after the stable release:
+ postconf -P: emit '{ name = value }' when editing/adding a
+ parameter whose new value contains whitespace.
+
+ In release-notes add commands=x/y logging to the command
+ statistics.
+
UTF8 DNS[BW]L domain name.
Consolidate maps flags in mail_params.h instead of having
<blockquote>
<pre>
-# <b>postconf <a href="postconf.5.html#relay_domains">relay_domains</a>=$<a href="postconf.5.html#mydestination">mydestination</a></b>
+# <b>postconf '<a href="postconf.5.html#relay_domains">relay_domains</a>=$<a href="postconf.5.html#mydestination">mydestination</a>'</b>
# <b>postfix reload</b>
</pre>
</blockquote>
+<p> Note: quotes are required as indicated above. </p>
+
<p> Instead of $<a href="postconf.5.html#mydestination">mydestination</a>, it may be better to specify an
explicit list of domain names. </p>
<blockquote>
<pre>
-# <b>postconf relay_domains=$mydestination</b>
+# <b>postconf 'relay_domains=$mydestination'</b>
# <b>postfix reload</b>
</pre>
</blockquote>
+<p> Note: quotes are required as indicated above. </p>
+
<p> Instead of $mydestination, it may be better to specify an
explicit list of domain names. </p>
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20150117"
+#define MAIL_RELEASE_DATE "20150118"
#define MAIL_VERSION_NUMBER "2.12"
#ifdef SNAPSHOT
/* .IP "\fB-v\fR"
/* Enable verose Postfix logging. Specify more than once to increase
/* the level of verbose logging.
+/* .IP "\fB-w\fR"
+/* Enable outgoing TLS wrapper mode, or SMTPS support. This is typically
+/* provided on port 465 by servers that are compatible with the ad-hoc
+/* SMTP in SSL protocol, rather than the standard STARTTLS protocol.
+/* The destination \fIdomain\fR:\fIport\fR should of course provide such
+/* a service.
/* .IP "[\fBinet:\fR]\fIdomain\fR[:\fIport\fR]"
/* Connect via TCP to domain \fIdomain\fR, port \fIport\fR. The default
/* port is \fBsmtp\fR (or 24 with LMTP). With SMTP an MX lookup is
VSTRING *buffer; /* Response buffer */
VSTREAM *stream; /* Open connection */
int level; /* TLS security level */
+ int wrapper_mode; /* SMTPS support */
#ifdef USE_TLS
char *mdalg; /* fingerprint digest algorithm */
char *CAfile; /* Trusted public CAs */
}
}
+/* greeting - read server's 220 greeting */
+
+static int greeting(STATE *state)
+{
+ VSTREAM *stream = state->stream;
+ int except;
+ RESPONSE *resp;
+
+ /*
+ * Prepare for disaster.
+ */
+ smtp_stream_setup(stream, conn_tmout, 1);
+ if ((except = vstream_setjmp(stream)) != 0) {
+ msg_info("%s while reading server greeting", exception_text(except));
+ return (1);
+ }
+
+ /*
+ * Read and parse the server's SMTP greeting banner.
+ */
+ if (((resp = response(state, 1))->code / 100) != 2) {
+ msg_info("SMTP service not available: %d %s", resp->code, resp->str);
+ return (1);
+ }
+ return (0);
+}
+
/* ehlo - send EHLO/LHLO */
static RESPONSE *ehlo(STATE *state)
VSTREAM *stream = state->stream;
TLS_CLIENT_START_PROPS tls_props;
- /* SMTP stream with deadline timeouts */
- smtp_stream_setup(stream, smtp_tmout, 1);
- if ((except = vstream_setjmp(stream)) != 0) {
- msg_fatal("%s while sending STARTTLS", exception_text(except));
- return (1);
- }
- command(state, state->pass == 1, "STARTTLS");
-
- resp = response(state, state->pass == 1);
- if (resp->code / 100 != 2) {
- msg_info("STARTTLS rejected: %d %s", resp->code, resp->str);
- return (1);
- }
+ if (state->wrapper_mode == 0) {
+ /* SMTP stream with deadline timeouts */
+ smtp_stream_setup(stream, smtp_tmout, 1);
+ if ((except = vstream_setjmp(stream)) != 0) {
+ msg_fatal("%s while sending STARTTLS", exception_text(except));
+ return (1);
+ }
+ command(state, state->pass == 1, "STARTTLS");
- /*
- * Discard any plain-text data that may be piggybacked after the server's
- * 220 STARTTLS reply. Should we abort the session instead?
- */
- vstream_fpurge(stream, VSTREAM_PURGE_READ);
+ resp = response(state, state->pass == 1);
+ if (resp->code / 100 != 2) {
+ msg_info("STARTTLS rejected: %d %s", resp->code, resp->str);
+ return (1);
+ }
+ /*
+ * Discard any plain-text data that may be piggybacked after the
+ * server's 220 STARTTLS reply. Should we abort the session instead?
+ */
+ vstream_fpurge(stream, VSTREAM_PURGE_READ);
+ }
#define ADD_EXCLUDE(vstr, str) \
do { \
if (*(str)) \
state->stream = 0;
return (1);
}
+ if (state->wrapper_mode && greeting(state) != 0)
+ return (1);
+
if (state->pass == 1) {
ehlo(state);
if (!TLS_CERT_IS_PRESENT(state->tls_context))
int except;
int n;
char *lines;
- char *words;
+ char *words = 0;
char *word;
- /*
- * Prepare for disaster.
- */
- smtp_stream_setup(stream, conn_tmout, 1);
- if ((except = vstream_setjmp(stream)) != 0)
- msg_fatal("%s while reading server greeting", exception_text(except));
-
- /*
- * Read and parse the server's SMTP greeting banner.
- */
- if (((resp = response(state, 1))->code / 100) != 2) {
- msg_info("SMTP service not available: %d %s", resp->code, resp->str);
- return (1);
- }
-
- /*
- * Send the standard greeting with our hostname
- */
- if ((resp = ehlo(state)) == 0)
- return (1);
+ if (!state->wrapper_mode) {
+ if (greeting(state) != 0)
+ return (1);
+ if ((resp = ehlo(state)) == 0)
+ return (1);
- lines = resp->str;
- for (n = 0; (words = mystrtok(&lines, "\n")) != 0; ++n) {
- if ((word = mystrtok(&words, " \t=")) != 0) {
- if (n == 0)
- state->helo = mystrdup(word);
- if (strcasecmp(word, "STARTTLS") == 0)
- break;
+ lines = resp->str;
+ for (n = 0; (words = mystrtok(&lines, "\n")) != 0; ++n) {
+ if ((word = mystrtok(&words, " \t=")) != 0) {
+ if (n == 0)
+ state->helo = mystrdup(word);
+ if (strcasecmp(word, "STARTTLS") == 0)
+ break;
+ }
}
}
-
#ifdef USE_TLS
- if (words && state->tls_ctx)
+ if ((state->wrapper_mode || words) && state->tls_ctx)
if (starttls(state))
return (1);
#endif
}
command(state, 1, "QUIT");
(void) response(state, 1);
-
return (0);
}
int level = state->level;
#ifdef USE_TLS
- if (level == TLS_LEV_DANE) {
+ if (TLS_DANE_BASED(level)) {
if (state->mx == 0 || state->mx->dnssec_valid) {
if (state->log_mask & (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE))
tls_dane_verbose(1);
HNAME(addr), ntohs(state->port));
level = TLS_LEV_INVALID;
} else if (tls_dane_notfound(state->ddane)
- || tls_dane_unusable(state->ddane)) {
+ || tls_dane_unusable(state->ddane)
+ || level == TLS_LEV_DANE_ONLY) {
if (msg_verbose)
msg_info("no %sTLSA records found, "
"resorting to \"secure\"",
level = TLS_LEV_SECURE;
} else if (!TLS_DANE_HASTA(state->ddane)
&& !TLS_DANE_HASEE(state->ddane)) {
- msg_panic("empty DANE match list");
+ msg_panic("DANE activated with no TLSA records to match");
} else {
if (state->match)
argv_free(state->match);
#ifdef USE_TLS
fprintf(stderr, "usage: %s %s \\\n\t%s \\\n\t%s \\\n\t%s"
" destination [match ...]\n", var_procname,
- "[-acCfSv] [-t conn_tmout] [-T cmd_tmout] [-L logopts]",
+ "[-acCfSvw] [-t conn_tmout] [-T cmd_tmout] [-L logopts]",
"[-h host_lookup] [-l level] [-d mdalg] [-g grade] [-p protocols]",
"[-A tafile] [-F CAfile.pem] [-P CApath/] [-m count] [-r delay]",
"[-o name=value]");
state->pass = 1;
state->reconnect = -1;
state->max_reconnect = 5;
+ state->wrapper_mode = 0;
#ifdef USE_TLS
state->protocols = mystrdup("!SSLv2");
state->grade = mystrdup("medium");
#define OPTS "a:ch:o:St:T:v"
#ifdef USE_TLS
-#define TLSOPTS "A:Cd:fF:g:l:L:m:p:P:r:"
+#define TLSOPTS "A:Cd:fF:g:l:L:m:p:P:r:w"
state->mdalg = mystrdup("sha1");
state->CApath = mystrdup("");
case 'r':
state->reconnect = atoi(optarg);
break;
+ case 'w':
+ state->wrapper_mode = 1;
+ break;
#endif
}
}
state->level = tls_level_lookup(state->options.level);
switch (state->level) {
- case TLS_LEV_DANE_ONLY:
- state->level = TLS_LEV_DANE;
- break;
case TLS_LEV_NONE:
+ if (state->wrapper_mode)
+ msg_fatal("SSL wrapper mode requires that TLS not be disabled");
return;
case TLS_LEV_INVALID:
msg_fatal("Invalid TLS level \"%s\"", state->options.level);
* required for DANE support.
*/
tls_init(state);
- if (state->level == TLS_LEV_DANE && !tls_dane_avail()) {
- msg_warn("The \"dane\" TLS security level is not available");
+ if (TLS_DANE_BASED(state->level) && !tls_dane_avail()) {
+ msg_warn("DANE TLS support is not available, resorting to \"secure\"");
state->level = TLS_LEV_SECURE;
}
state->tls_bio = 0;
state->mdalg, *argv++, "");
break;
case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
state->match = argv_alloc(2);
argv_add(state->match, "nexthop", "hostname", ARGV_END);
break;
break;
case TLS_LEV_DANE:
+ case TLS_LEV_DANE_ONLY:
case TLS_LEV_FPRINT:
case TLS_LEV_VERIFY:
case TLS_LEV_SECURE:
* "dane-only" changes to "dane" once we obtain the requisite TLSA
* records.
*/
- if (tls->level == TLS_LEV_DANE || tls->level == TLS_LEV_DANE_ONLY)
+ if (TLS_DANE_BASED(tls->level))
dane_init(tls, iter);
if (tls->level == TLS_LEV_INVALID)
return ((void *) tls);
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)
} else if (!TLS_DANE_HASEE(dane))
msg_panic("empty DANE match list");
tls->dane = dane;
- tls->level = TLS_LEV_DANE;
return;
}
static char *smtpd_format_cmd_stats(VSTRING *buf)
{
SMTPD_CMD *cmdp;
+ int all_success = 0;
+ int all_total = 0;
+ /*
+ * Log the statistics. Note that this loop produces no output when no
+ * command was received. We address that after the loop.
+ */
VSTRING_RESET(buf);
for (cmdp = smtpd_cmd_table; /* see below */ ; cmdp++) {
if (cmdp->total_count > 0) {
cmdp->success_count);
if (cmdp->success_count != cmdp->total_count)
vstring_sprintf_append(buf, "/%d", cmdp->total_count);
+ all_success += cmdp->success_count;
+ all_total += cmdp->total_count;
}
if (cmdp->name == 0)
break;
}
+
+ /*
+ * Log total numbers, so that logfile analyzers will see something even
+ * if the above loop produced no output. When no commands were received
+ * log "0/0" to simplify the identification of abnormal sessions: any
+ * statistics with [0-9]/ indicate that there was a problem.
+ */
+ vstring_sprintf_append(buf, " commands=%d", all_success);
+ if (all_success != all_total || all_total == 0)
+ vstring_sprintf_append(buf, "/%d", all_total);
return (lowercase(STR(buf)));
}
#define TLS_MUST_MATCH(l) ((l) > TLS_LEV_ENCRYPT)
#define TLS_MUST_TRUST(l) ((l) >= TLS_LEV_DANE)
#define TLS_MUST_PKIX(l) ((l) >= TLS_LEV_VERIFY)
+#define TLS_OPPORTUNISTIC(l) ((l) == TLS_LEV_MAY || (l) == TLS_LEV_DANE)
+#define TLS_DANE_BASED(l) ((l) == TLS_LEV_DANE || (l) == TLS_LEV_DANE_ONLY)
extern const NAME_CODE tls_level_table[];
/*
* When certificate verification is required, log trust chain validation
* errors even when disabled by default for opportunistic sessions. For
- * "dane" this only applies when using trust-anchor associations.
+ * DANE this only applies when using trust-anchor associations.
*/
if (TLS_MUST_TRUST(props->tls_level)
- && (props->tls_level != TLS_LEV_DANE || TLS_DANE_HASTA(props->dane)))
+ && (!TLS_DANE_BASED(props->tls_level) || TLS_DANE_HASTA(props->dane)))
log_mask |= TLS_LOG_UNTRUSTED;
if (log_mask & TLS_LOG_VERBOSE)
props->namaddr, props->protocols);
return (0);
}
- /* The DANE level requires SSLv3 or later, not SSLv2. */
- if (props->tls_level == TLS_LEV_DANE)
+ /* DANE requires SSLv3 or later, not SSLv2. */
+ if (TLS_DANE_BASED(props->tls_level))
protomask |= TLS_PROTOCOL_SSLv2;
/*
}
}
#ifdef TLSEXT_MAXLEN_host_name
- if (props->tls_level == TLS_LEV_DANE
+ if (TLS_DANE_BASED(props->tls_level)
&& strlen(props->host) <= TLSEXT_MAXLEN_host_name) {
/*
#if 0
digest_dane(props->dane, ee); /* See above */
#endif
- digest_string(props->tls_level == TLS_LEV_DANE ? props->host : "");
+ digest_string(TLS_DANE_BASED(props->tls_level) ? props->host : "");
}
checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len));
EVP_MD_CTX_destroy(mdctx);