src/smtp/smtp_reuse.c, src/smtp/smtp_session.c,
src/smtp/smtp_tls_sess.c.
+20130401
+
+ Refactoring: allow smtp_session_alloc() to fail gracefully
+ and report an error.
Cleanup: "zero time limit" corner case in read_wait() and
write_wait() emulation. Files: util/poll_fd.c, util/iostuff.h.
# Non-production: needs thorough testing, or major changes are still
# needed before the code stabilizes.
-#CCARGS="$CCARGS -DNONPROD"
+CCARGS="$CCARGS -DNONPROD"
sed 's/ / /g' <<EOF
SYSTYPE = $SYSTYPE
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20130331"
+#define MAIL_RELEASE_DATE "20130401"
#define MAIL_VERSION_NUMBER "2.11"
#ifdef SNAPSHOT
#define SMTP_MISC_FLAG_COMPLETE_SESSION (1<<8)
#define SMTP_MISC_FLAG_PREF_IPV6 (1<<9)
#define SMTP_MISC_FLAG_PREF_IPV4 (1<<10)
+#define SMTP_MISC_FLAG_NO_TLS (1<<11)
#define SMTP_MISC_FLAG_CONN_CACHE_MASK \
(SMTP_MISC_FLAG_CONN_LOAD | SMTP_MISC_FLAG_CONN_STORE)
SMTP_STATE *state; /* back link */
} SMTP_SESSION;
-extern SMTP_SESSION *smtp_session_alloc(VSTREAM *, const char *, const char *,
- const char *, unsigned, time_t, int);
+extern SMTP_SESSION *smtp_session_alloc(DSN_BUF *, const char *, const char *,
+ const char *, unsigned, int);
+extern void smtp_session_new_stream(SMTP_SESSION *, VSTREAM *, time_t, int);
+extern int smtp_sess_tls_check(const char *, const char *, unsigned, int);
extern void smtp_session_free(SMTP_SESSION *);
extern int smtp_session_passivate(SMTP_SESSION *, VSTRING *, VSTRING *);
extern SMTP_SESSION *smtp_session_activate(int, VSTRING *, VSTRING *);
#ifdef USE_TLS
-extern void smtp_tls_list_init(void);
/*
* smtp_tls_sess.c
*/
-extern SMTP_TLS_SESS *smtp_tls_sess_alloc(const char *, const char *,
+extern void smtp_tls_list_init(void);
+extern SMTP_TLS_SESS *smtp_tls_sess_alloc(DSN_BUF *, const char *, const char *,
unsigned, int);
extern SMTP_TLS_SESS *smtp_tls_sess_free(SMTP_TLS_SESS *);
int saved_errno;
VSTREAM *stream;
time_t start_time;
+ SMTP_SESSION *session;
+
+ /*
+ * Session construction is cheap, and can now tempfail when TLSA lookups
+ * don't work at the DANE security level. This also handles table lookup
+ * errors more gracefully. So construct the session, and then connect. If
+ * the connection fails, tear down the session.
+ */
+ if ((session = smtp_session_alloc(why, destination, name, addr,
+ port, sess_flags)) == 0)
+ return (0);
start_time = time((time_t *) 0);
if (var_smtp_conn_tmout > 0) {
else
dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr);
close(sock);
+ smtp_session_free(session);
return (0);
}
stream = vstream_fdopen(sock, O_RDWR);
vstream_tweak_tcp(stream);
/*
- * Bundle up what we have into a nice SMTP_SESSION object.
+ * Update the SMTP_SESSION state with this newly-created stream, and make
+ * it subject to the new-stream connection caching policy (as opposed to
+ * the reused-stream caching policy).
*/
- return (smtp_session_alloc(stream, destination, name, addr,
- port, start_time, sess_flags));
+ smtp_session_new_stream(session, stream, start_time, sess_flags);
+
+ return (session);
}
/* smtp_parse_destination - parse host/port destination */
* credentials or the wrong TLS policy.
*/
if ((var_smtp_tls_per_site && *var_smtp_tls_per_site)
+ || (var_smtp_tls_policy && *var_smtp_tls_policy)
|| (var_smtp_sasl_passwd && *var_smtp_sasl_passwd))
return (0);
/* SYNOPSIS
/* #include "smtp.h"
/*
-/* SMTP_SESSION *smtp_session_alloc(stream, dest, host, addr,
-/* port, start, flags)
-/* VSTREAM *stream;
+/* SMTP_SESSION *smtp_session_alloc(why, dest, host, addr,
+/* port, flags)
+/* DSN_BUF *why;
/* char *dest;
/* char *host;
/* char *addr;
/* unsigned port;
+/* int flags;
+/*
+/* void smtp_session_new_stream(session, stream, start, flags)
+/* SMTP_SESSION *session;
+/* VSTREAM *stream;
/* time_t start;
/* int flags;
/*
+/* int smtp_sess_tls_check(dest, host, port, valid)
+/* char *dest;
+/* char *host;
+/* unsigned port;
+/* int valid;
+/*
/* void smtp_session_free(session)
/* SMTP_SESSION *session;
/*
/* VSTRING *endp_prop;
/* DESCRIPTION
/* smtp_session_alloc() allocates memory for an SMTP_SESSION structure
-/* and initializes it with the given stream and destination, host name
-/* and address information. The host name and address strings are
-/* copied. The port is in network byte order.
-/* When TLS is enabled, smtp_session_alloc() looks up the
-/* per-site TLS policies for TLS enforcement and certificate
-/* verification. The resulting policy is stored into the
-/* SMTP_SESSION object.
+/* and initializes it with the given destination, host name and address
+/* information. The host name and address strings are copied. The port
+/* is in network byte order. When TLS is enabled, smtp_session_alloc()
+/* looks up the per-site TLS policies for TLS enforcement and certificate
+/* verification. The resulting policy is stored into the SMTP_SESSION
+/* object. Table and DNS lookups can fail during TLS policy creation,
+/* when this happens, "why" is updated with the error reason and a null
+/* session pointer is returned.
+/*
+/* smtp_session_new_stream() updates an SMTP_SESSION structure
+/* with a newly-created stream that was created at the specified
+/* start time, and makes it subject to the specified connection
+/* caching policy.
+/*
+/* smtp_sess_tls_check() returns true if TLS use is mandatory, invalid
+/* or indeterminate. The return value is false only if TLS is optional.
+/* This is not yet used anywhere, it can be used to safely enable TLS
+/* policy with smtp_reuse_addr().
/*
/* smtp_session_free() destroys an SMTP_SESSION structure and its
/* members, making memory available for reuse. It will handle the
/* The address of the host that we are connected to.
/* .IP port
/* The remote port, network byte order.
+/* .IP valid
+/* The DNSSEC validation status of the host name.
/* .IP start
/* The time when this connection was opened.
/* .IP flags
/* Enable re-use of cached SMTP or LMTP connections.
/* .IP SMTP_MISC_FLAG_CONN_STORE
/* Enable saving of cached SMTP or LMTP connections.
+/* .IP SMTP_MISC_FLAG_NO_TLS
+/* Used only internally in smtp_session.c
/* .RE
/* SMTP_MISC_FLAG_CONN_MASK corresponds with both _LOAD and _STORE.
/* .IP dest_prop
#include <vstream.h>
#include <stringops.h>
#include <valid_hostname.h>
-#include <name_code.h>
/* Global library. */
/* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */
-SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, const char *dest,
+SMTP_SESSION *smtp_session_alloc(DSN_BUF *why, const char *dest,
const char *host, const char *addr,
- unsigned port, time_t start,
- int flags)
+ unsigned port, int flags)
{
SMTP_SESSION *session;
+ int valid = (flags & SMTP_MISC_FLAG_TLSA_HOST);
session = (SMTP_SESSION *) mymalloc(sizeof(*session));
- session->stream = stream;
+ session->stream = 0;
session->dest = mystrdup(dest);
session->host = mystrdup(host);
session->addr = mystrdup(addr);
session->send_proto_helo = 0;
- if (flags & SMTP_MISC_FLAG_CONN_STORE)
- CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time);
- else
- DONT_CACHE_THIS_SESSION;
- session->reuse_count = 0;
USE_NEWBORN_SESSION; /* He's not dead Jim! */
#ifdef USE_SASL_AUTH
smtp_sasl_connect(session);
#endif
+ /*
+ * XXX Not documented: calling smtp_session_alloc() with a null pointer
+ * DSN_BUF argument.
+ *
+ * XXX Not documented: calling smtp_tls_sess_alloc() with a null pointer
+ * host argument.
+ */
#ifdef USE_TLS
session->tls_context = 0;
session->tls_retry_plain = 0;
session->tls_nexthop = 0;
- session->tls =
- smtp_tls_sess_alloc(dest, host, port, flags & SMTP_MISC_FLAG_TLSA_HOST);
+#define NO_DSN_BUF (DSN_BUF *) 0
+#define NO_DEST (char *) 0
+#define NO_HOST (char *) 0
+#define NO_PORT 0
+#define NO_FLAGS 0
+ if (flags & SMTP_MISC_FLAG_NO_TLS)
+ session->tls = smtp_tls_sess_alloc(NO_DSN_BUF, NO_DEST, NO_HOST,
+ NO_PORT, NO_FLAGS);
+ else
+ session->tls = smtp_tls_sess_alloc(why, dest, host, port, valid);
+ if (!session->tls) {
+ smtp_session_free(session);
+ return (0);
+ }
#endif
session->state = 0;
debug_peer_check(host, addr);
return (session);
}
+/* smtp_session_new_stream - finalize session with newly-created connection */
+
+void smtp_session_new_stream(SMTP_SESSION *session, VSTREAM *stream,
+ time_t start, int flags)
+{
+ const char *myname = "smtp_session_new_stream";
+
+ /*
+ * Sanity check.
+ */
+ if (session->stream != 0)
+ msg_panic("%s: session exists", myname);
+
+ session->stream = stream;
+
+ /*
+ * Make the session subject to the new-stream connection caching policy,
+ * as opposed to the reused-stream connection caching policy at the
+ * bottom of this module. Both policies are enforced in this file, not
+ * one policy here and the other at some random place in smtp_connect.c.
+ */
+ if (flags & SMTP_MISC_FLAG_CONN_STORE)
+ CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time);
+ else
+ DONT_CACHE_THIS_SESSION;
+ session->reuse_count = 0;
+}
+
/* smtp_session_free - destroy SMTP_SESSION structure and contents */
void smtp_session_free(SMTP_SESSION *session)
myfree((char *) session);
}
+/* smtp_sess_tls_check - does session require tls */
+
+int smtp_sess_tls_check(const char *dest, const char *host, unsigned port,
+ int valid)
+{
+#ifdef USE_TLS
+ static DSN_BUF *why;
+ SMTP_TLS_SESS *tls;
+
+ if (!why)
+ why = dsb_create();
+
+ tls = smtp_tls_sess_alloc(why, dest, host, ntohs(port), valid);
+ dsb_reset(why);
+
+ if (tls && tls->level >= TLS_LEV_NONE && tls->level <= TLS_LEV_MAY)
+ return (0);
+ if (tls)
+ smtp_tls_sess_free(tls);
+ return (1);
+#else
+ return (0);
+#endif
+}
+
+
/* smtp_session_passivate - passivate an SMTP_SESSION object */
int smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop,
/*
* Allright, bundle up what we have sofar.
+ *
+ * Caller is responsible for not reusing plain-text connections when TLS is
+ * required. We disable TLS policy lookups, and therefore
+ * smtp_session_alloc() will never fail?!?
*/
-#define NO_FLAGS 0
-
- session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), dest, host,
- addr, port, (time_t) 0, NO_FLAGS);
+ session = smtp_session_alloc(NO_DSN_BUF, dest, host, addr, port,
+ SMTP_MISC_FLAG_NO_TLS);
+ session->stream = vstream_fdopen(fd, O_RDWR);
session->features = (features | SMTP_FEATURE_FROM_CACHE);
CACHE_THIS_SESSION_UNTIL(expire_time);
session->reuse_count = ++reuse_count;
/*
/* void smtp_tls_list_init()
/*
-/* SMTP_TLS_SESS *smtp_tls_sess_alloc(dest, host, port, valid)
+/* SMTP_TLS_SESS *smtp_tls_sess_alloc(why, dest, host, port, valid)
+/* DSN_BUF *why;
/* char *dest;
/* char *host;
/* unsigned port;
/* policy engine.
/*
/* smtp_tls_sess_alloc() allocates memory for an SMTP_TLS_SESS structure
-/* and initializes it based on the given information. NOTE: the
-/* port is in network byte order.
+/* and initializes it based on the given information. Any required
+/* table and DNS lookups can fail. When this happens, "why" is updated
+/* with the error reason and a null pointer is returned. NOTE: the
+/* port is in network byte order. If "dest" is null, no policy checks are
+/* made, rather a trivial policy with tls disabled is returned.
/*
/* smtp_tls_sess_free() destroys an SMTP_TLS_SESS structure and its
/* members. A null pointer is returned for convenience.
/* tls_site_lookup - look up per-site TLS security level */
-static void tls_site_lookup(int *site_level, const char *site_name,
- const char *site_class)
+static void tls_site_lookup(SMTP_TLS_SESS *tls, int *site_level,
+ const char *site_name, const char *site_class,
+ DSN_BUF *why)
{
const char *lookup;
if (*site_level < TLS_LEV_VERIFY)
*site_level = TLS_LEV_VERIFY;
} else {
- msg_warn("Table %s: ignoring unknown TLS policy '%s' for %s %s",
- var_smtp_tls_per_site, lookup, site_class, site_name);
+ msg_warn("%s: unknown TLS policy '%s' for %s %s",
+ tls_per_site->title, lookup, site_class, site_name);
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
+ *site_level = TLS_LEV_INVALID;
+ return;
}
} else if (tls_per_site->error) {
- msg_fatal("%s lookup error for %s", tls_per_site->title, site_name);
+ msg_warn("%s: %s \"%s\": per-site table lookup error",
+ tls_per_site->title, site_class, site_name);
+ dsb_simple(why, "4.3.0", "Temporary lookup error");
+ *site_level = TLS_LEV_INVALID;
+ return;
}
+ return;
}
/* tls_policy_lookup_one - look up destination TLS policy */
-static int tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
- const char *site_name,
- const char *site_class)
+static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level,
+ const char *site_name,
+ const char *site_class, DSN_BUF *why)
{
const char *lookup;
char *policy;
static VSTRING *cbuf;
#undef FREE_RETURN
-#define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0)
+#define FREE_RETURN do { myfree(saved_policy); return; } while (0)
+
+#define WHERE \
+ vstring_str(vstring_sprintf(cbuf, "%s, %s \"%s\"", \
+ tls_policy->title, site_class, site_name))
+
+ if (cbuf == 0)
+ cbuf = vstring_alloc(10);
if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) {
if (tls_policy->error) {
- msg_fatal("%s lookup error for %s",
- tls_policy->title, site_name);
- /* XXX session->stream has no longjmp context yet. */
+ msg_warn("%s: policy table lookup error", WHERE);
+ dsb_simple(why, "4.3.0", "Temporary lookup error");
+ *site_level = TLS_LEV_INVALID;
}
- return (0);
+ return;
}
- if (cbuf == 0)
- cbuf = vstring_alloc(10);
-
-#define WHERE \
- vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \
- site_class, site_name))
-
saved_policy = policy = mystrdup(lookup);
if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) {
msg_warn("%s: invalid empty policy", WHERE);
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
*site_level = TLS_LEV_INVALID;
- FREE_RETURN(1); /* No further lookups */
+ FREE_RETURN;
}
*site_level = tls_level_lookup(tok);
if (*site_level == TLS_LEV_INVALID) {
/* tls_level_lookup() logs no warning. */
msg_warn("%s: invalid security level \"%s\"", WHERE, tok);
- FREE_RETURN(1); /* No further lookups */
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
+ FREE_RETURN;
}
/*
while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0)
msg_warn("%s: ignoring attribute \"%s\" with TLS disabled",
WHERE, tok);
- FREE_RETURN(1);
+ FREE_RETURN;
}
/*
*/
while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) {
if ((err = split_nameval(tok, &name, &val)) != 0) {
- *site_level = TLS_LEV_INVALID;
msg_warn("%s: malformed attribute/value pair \"%s\": %s",
WHERE, tok, err);
- break;
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
+ *site_level = TLS_LEV_INVALID;
+ FREE_RETURN;
}
/* Only one instance per policy. */
if (!strcasecmp(name, "ciphers")) {
if (*val == 0) {
msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
*site_level = TLS_LEV_INVALID;
- break;
+ FREE_RETURN;
}
if (tls->grade) {
msg_warn("%s: attribute \"%s\" is specified multiple times",
WHERE, name);
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
*site_level = TLS_LEV_INVALID;
- break;
+ FREE_RETURN;
}
tls->grade = mystrdup(val);
continue;
if (tls->protocols) {
msg_warn("%s: attribute \"%s\" is specified multiple times",
WHERE, name);
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
*site_level = TLS_LEV_INVALID;
- break;
+ FREE_RETURN;
}
tls->protocols = mystrdup(val);
continue;
if (*site_level <= TLS_LEV_ENCRYPT) {
msg_warn("%s: attribute \"%s\" invalid at security level "
"\"%s\"", WHERE, name, policy_name(*site_level));
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
*site_level = TLS_LEV_INVALID;
- break;
+ FREE_RETURN;
}
if (*val == 0) {
msg_warn("%s: attribute \"%s\" has empty value", WHERE, name);
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
*site_level = TLS_LEV_INVALID;
- break;
+ FREE_RETURN;
}
if (tls->matchargv == 0)
tls->matchargv = argv_split(val, delim);
if (tls->exclusions) {
msg_warn("%s: attribute \"%s\" is specified multiple times",
WHERE, name);
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
*site_level = TLS_LEV_INVALID;
- break;
+ FREE_RETURN;
}
tls->exclusions = vstring_strcpy(vstring_alloc(10), val);
continue;
- } else {
- msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
- *site_level = TLS_LEV_INVALID;
- break;
}
+ msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
+ dsb_simple(why, "4.7.5", "client TLS configuration problem");
+ *site_level = TLS_LEV_INVALID;
+ FREE_RETURN;
}
- FREE_RETURN(1);
+
+ FREE_RETURN;
}
/* tls_policy_lookup - look up destination TLS policy */
static void tls_policy_lookup(SMTP_TLS_SESS *tls, int *site_level,
const char *site_name,
- const char *site_class)
+ const char *site_class, DSN_BUF *why)
{
/*
* sub-domains of the recipient domain.
*/
if (!valid_hostname(site_name, DONT_GRIPE)) {
- tls_policy_lookup_one(tls, site_level, site_name, site_class);
+ tls_policy_lookup_one(tls, site_level, site_name, site_class, why);
return;
}
-
- /*
- * XXX For clarity consider using ``do { .. } while'', instead of using
- * ``while { .. }'' with loop control at the bottom.
- */
- while (1) {
- /* Try the given domain */
- if (tls_policy_lookup_one(tls, site_level, site_name, site_class))
- return;
- /* Re-try with parent domain */
- if ((site_name = strchr(site_name + 1, '.')) == 0)
- return;
- }
+ do {
+ tls_policy_lookup_one(tls, site_level, site_name, site_class, why);
+ } while (*site_level == TLS_LEV_NOTFOUND
+ && (site_name = strchr(site_name + 1, '.')) != 0);
}
/* set_cipher_grade - Set cipher grade and exclusions */
/* smtp_tls_sess_alloc - session TLS policy parameters */
-SMTP_TLS_SESS *smtp_tls_sess_alloc(const char *dest, const char *host,
- unsigned port, int valid)
+SMTP_TLS_SESS *smtp_tls_sess_alloc(DSN_BUF *why, const char *dest,
+ const char *host, unsigned port, int valid)
{
- const char *myname = "session_tls_init";
+ const char *myname = "smtp_tls_sess_alloc";
int global_level;
int site_level;
SMTP_TLS_SESS *tls = (SMTP_TLS_SESS *) mymalloc(sizeof(*tls));
tls->exclusions = 0;
tls->matchargv = 0;
+ if (!dest)
+ return (tls);
+
/*
* Compute the global TLS policy. This is the default policy level when
* no per-site policy exists. It also is used to override a wild-card
site_level = TLS_LEV_NOTFOUND;
if (tls_policy) {
- tls_policy_lookup(tls, &site_level, dest, "next-hop destination");
+ tls_policy_lookup(tls, &site_level, dest, "next-hop destination", why);
} else if (tls_per_site) {
- tls_site_lookup(&site_level, dest, "next-hop destination");
- if (strcasecmp(dest, host) != 0)
- tls_site_lookup(&site_level, host, "server hostname");
- if (msg_verbose)
- msg_info("%s TLS level: %s", "site", policy_name(site_level));
+ tls_site_lookup(tls, &site_level, dest, "next-hop destination", why);
+ if (site_level != TLS_LEV_INVALID
+ && strcasecmp(dest, host) != 0)
+ tls_site_lookup(tls, &site_level, host, "server hostname", why);
/*
* Override a wild-card per-site policy with a more specific global
if (site_level == TLS_LEV_MAY && global_level > TLS_LEV_MAY)
site_level = global_level;
}
- if (site_level == TLS_LEV_NOTFOUND)
+ switch (site_level) {
+ case TLS_LEV_INVALID:
+ return (smtp_tls_sess_free(tls));
+ case TLS_LEV_NOTFOUND:
tls->level = global_level;
- else
+ break;
+ default:
tls->level = site_level;
+ break;
+ }
/*
* Use main.cf protocols setting if not set in per-destination table.