summaryrefslogtreecommitdiffstats
path: root/src/smtp/smtp_sasl_glue.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/smtp/smtp_sasl_glue.c')
-rw-r--r--src/smtp/smtp_sasl_glue.c512
1 files changed, 512 insertions, 0 deletions
diff --git a/src/smtp/smtp_sasl_glue.c b/src/smtp/smtp_sasl_glue.c
new file mode 100644
index 0000000..ef8e8c4
--- /dev/null
+++ b/src/smtp/smtp_sasl_glue.c
@@ -0,0 +1,512 @@
+/*++
+/* NAME
+/* smtp_sasl_glue 3
+/* SUMMARY
+/* Postfix SASL interface for SMTP client
+/* SYNOPSIS
+/* #include smtp_sasl.h
+/*
+/* void smtp_sasl_initialize()
+/*
+/* void smtp_sasl_connect(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_sasl_start(session, sasl_opts_name, sasl_opts_val)
+/* SMTP_SESSION *session;
+/*
+/* int smtp_sasl_passwd_lookup(session)
+/* SMTP_SESSION *session;
+/*
+/* int smtp_sasl_authenticate(session, why)
+/* SMTP_SESSION *session;
+/* DSN_BUF *why;
+/*
+/* void smtp_sasl_cleanup(session)
+/* SMTP_SESSION *session;
+/*
+/* void smtp_sasl_passivate(session, buf)
+/* SMTP_SESSION *session;
+/* VSTRING *buf;
+/*
+/* int smtp_sasl_activate(session, buf)
+/* SMTP_SESSION *session;
+/* char *buf;
+/* DESCRIPTION
+/* smtp_sasl_initialize() initializes the SASL library. This
+/* routine must be called once at process startup, before any
+/* chroot operations.
+/*
+/* smtp_sasl_connect() performs per-session initialization. This
+/* routine must be called once at the start of each connection.
+/*
+/* smtp_sasl_start() performs per-session initialization. This
+/* routine must be called once per session before doing any SASL
+/* authentication. The sasl_opts_name and sasl_opts_val parameters are
+/* the postfix configuration parameters setting the security
+/* policy of the SASL authentication.
+/*
+/* smtp_sasl_passwd_lookup() looks up the username/password
+/* for the current SMTP server. The result is zero in case
+/* of failure, a long jump in case of error.
+/*
+/* smtp_sasl_authenticate() implements the SASL authentication
+/* dialog. The result is < 0 in case of protocol failure, zero in
+/* case of unsuccessful authentication, > 0 in case of success.
+/* The why argument is updated with a reason for failure.
+/* This routine must be called only when smtp_sasl_passwd_lookup()
+/* succeeds.
+/*
+/* smtp_sasl_cleanup() cleans up. It must be called at the
+/* end of every SMTP session that uses SASL authentication.
+/* This routine is a noop for non-SASL sessions.
+/*
+/* smtp_sasl_passivate() appends flattened SASL attributes to the
+/* specified buffer. The SASL attributes are not destroyed.
+/*
+/* smtp_sasl_activate() restores SASL attributes from the
+/* specified buffer. The buffer is modified. A result < 0
+/* means there was an error.
+/*
+/* Arguments:
+/* .IP session
+/* Session context.
+/* .IP mech_list
+/* String of SASL mechanisms (separated by blanks)
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Original author:
+/* Till Franke
+/* SuSE Rhein/Main AG
+/* 65760 Eschborn, Germany
+/*
+/* Adopted by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+ * Utility library
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <split_at.h>
+
+ /*
+ * Global library
+ */
+#include <mail_params.h>
+#include <string_list.h>
+#include <maps.h>
+#include <mail_addr_find.h>
+#include <smtp_stream.h>
+
+ /*
+ * XSASL library.
+ */
+#include <xsasl.h>
+
+ /*
+ * Application-specific
+ */
+#include "smtp.h"
+#include "smtp_sasl.h"
+#include "smtp_sasl_auth_cache.h"
+
+#ifdef USE_SASL_AUTH
+
+ /*
+ * Per-host login/password information.
+ */
+static MAPS *smtp_sasl_passwd_map;
+
+ /*
+ * Supported SASL mechanisms.
+ */
+STRING_LIST *smtp_sasl_mechs;
+
+ /*
+ * SASL implementation handle.
+ */
+static XSASL_CLIENT_IMPL *smtp_sasl_impl;
+
+ /*
+ * The 535 SASL authentication failure cache.
+ */
+#ifdef HAVE_SASL_AUTH_CACHE
+static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache;
+
+#endif
+
+/* smtp_sasl_passwd_lookup - password lookup routine */
+
+int smtp_sasl_passwd_lookup(SMTP_SESSION *session)
+{
+ const char *myname = "smtp_sasl_passwd_lookup";
+ SMTP_STATE *state = session->state;
+ SMTP_ITERATOR *iter = session->iterator;
+ const char *value;
+ char *passwd;
+
+ /*
+ * Sanity check.
+ */
+ if (smtp_sasl_passwd_map == 0)
+ msg_panic("%s: passwd map not initialized", myname);
+
+ /*
+ * Look up the per-server password information. Try the hostname first,
+ * then try the destination.
+ *
+ * XXX Instead of using nexthop (the intended destination) we use dest
+ * (either the intended destination, or a fall-back destination).
+ *
+ * XXX SASL authentication currently depends on the host/domain but not on
+ * the TCP port. If the port is not :25, we should append it to the table
+ * lookup key. Code for this was briefly introduced into 2.2 snapshots,
+ * but didn't canonicalize the TCP port, and did not append the port to
+ * the MX hostname.
+ */
+ smtp_sasl_passwd_map->error = 0;
+ if ((smtp_mode
+ && var_smtp_sender_auth && state->request->sender[0]
+ && (value = mail_addr_find(smtp_sasl_passwd_map,
+ state->request->sender, (char **) 0)) != 0)
+ || (smtp_sasl_passwd_map->error == 0
+ && (value = maps_find(smtp_sasl_passwd_map,
+ STR(iter->host), 0)) != 0)
+ || (smtp_sasl_passwd_map->error == 0
+ && (value = maps_find(smtp_sasl_passwd_map,
+ STR(iter->dest), 0)) != 0)) {
+ if (session->sasl_username)
+ myfree(session->sasl_username);
+ session->sasl_username = mystrdup(value);
+ passwd = split_at(session->sasl_username, ':');
+ if (session->sasl_passwd)
+ myfree(session->sasl_passwd);
+ session->sasl_passwd = mystrdup(passwd ? passwd : "");
+ if (msg_verbose)
+ msg_info("%s: host `%s' user `%s' pass `%s'",
+ myname, STR(iter->host),
+ session->sasl_username, session->sasl_passwd);
+ return (1);
+ } else if (smtp_sasl_passwd_map->error) {
+ msg_warn("%s: %s lookup error",
+ state->request->queue_id, smtp_sasl_passwd_map->title);
+ vstream_longjmp(session->stream, SMTP_ERR_DATA);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: no auth info found (sender=`%s', host=`%s')",
+ myname, state->request->sender, STR(iter->host));
+ return (0);
+ }
+}
+
+/* smtp_sasl_initialize - per-process initialization (pre jail) */
+
+void smtp_sasl_initialize(void)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (smtp_sasl_passwd_map || smtp_sasl_impl)
+ msg_panic("smtp_sasl_initialize: repeated call");
+ if (*var_smtp_sasl_passwd == 0)
+ msg_fatal("specify a password table via the `%s' configuration parameter",
+ VAR_LMTP_SMTP(SASL_PASSWD));
+
+ /*
+ * Open the per-host password table and initialize the SASL library. Use
+ * shared locks for reading, just in case someone updates the table.
+ */
+ smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD),
+ var_smtp_sasl_passwd,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+ if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type,
+ var_smtp_sasl_path)) == 0)
+ msg_fatal("SASL library initialization");
+
+ /*
+ * Initialize optional supported mechanism matchlist
+ */
+ if (*var_smtp_sasl_mechs)
+ smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS,
+ MATCH_FLAG_NONE,
+ var_smtp_sasl_mechs);
+
+ /*
+ * Initialize the 535 SASL authentication failure cache.
+ */
+ if (*var_smtp_sasl_auth_cache_name) {
+#ifdef HAVE_SASL_AUTH_CACHE
+ smtp_sasl_auth_cache =
+ smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name,
+ var_smtp_sasl_auth_cache_time);
+#else
+ msg_warn("not compiled with TLS support -- "
+ "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME));
+#endif
+ }
+}
+
+/* smtp_sasl_connect - per-session client initialization */
+
+void smtp_sasl_connect(SMTP_SESSION *session)
+{
+
+ /*
+ * This initialization happens whenever we instantiate an SMTP session
+ * object. We don't instantiate a SASL client until we actually need one.
+ */
+ session->sasl_mechanism_list = 0;
+ session->sasl_username = 0;
+ session->sasl_passwd = 0;
+ session->sasl_client = 0;
+ session->sasl_reply = 0;
+}
+
+/* smtp_sasl_start - per-session SASL initialization */
+
+void smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
+ const char *sasl_opts_val)
+{
+ XSASL_CLIENT_CREATE_ARGS create_args;
+ SMTP_ITERATOR *iter = session->iterator;
+
+ if (msg_verbose)
+ msg_info("starting new SASL client");
+ if ((session->sasl_client =
+ XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args,
+ stream = session->stream,
+ service = var_procname,
+ server_name = STR(iter->host),
+ security_options = sasl_opts_val)) == 0)
+ msg_fatal("SASL per-connection initialization failed");
+ session->sasl_reply = vstring_alloc(20);
+}
+
+/* smtp_sasl_authenticate - run authentication protocol */
+
+int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
+{
+ const char *myname = "smtp_sasl_authenticate";
+ SMTP_ITERATOR *iter = session->iterator;
+ SMTP_RESP *resp;
+ const char *mechanism;
+ int result;
+ char *line;
+ int steps = 0;
+
+ /*
+ * Sanity check.
+ */
+ if (session->sasl_mechanism_list == 0)
+ msg_panic("%s: no mechanism list", myname);
+
+ if (msg_verbose)
+ msg_info("%s: %s: SASL mechanisms %s",
+ myname, session->namaddrport, session->sasl_mechanism_list);
+
+ /*
+ * Avoid repeated login failures after a recent 535 error.
+ */
+#ifdef HAVE_SASL_AUTH_CACHE
+ if (smtp_sasl_auth_cache
+ && smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) {
+ char *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache);
+ char *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache);
+
+ if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5')
+ resp_dsn[0] = '4';
+ dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS,
+ STR(iter->host), var_procname, resp_str,
+ "SASL [CACHED] authentication failed; server %s said: %s",
+ STR(iter->host), resp_str);
+ return (0);
+ }
+#endif
+
+ /*
+ * Start the client side authentication protocol.
+ */
+ result = xsasl_client_first(session->sasl_client,
+ session->sasl_mechanism_list,
+ session->sasl_username,
+ session->sasl_passwd,
+ &mechanism, session->sasl_reply);
+ if (result != XSASL_AUTH_OK) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA,
+ DSB_DTYPE_SASL, STR(session->sasl_reply),
+ "SASL authentication failed; "
+ "cannot authenticate to server %s: %s",
+ session->namaddr, STR(session->sasl_reply));
+ return (-1);
+ }
+ /*-
+ * Send the AUTH command and the optional initial client response.
+ *
+ * https://tools.ietf.org/html/rfc4954#page-4
+ * Note that the AUTH command is still subject to the line length
+ * limitations defined in [SMTP]. If use of the initial response argument
+ * would cause the AUTH command to exceed this length, the client MUST NOT
+ * use the initial response parameter...
+ *
+ * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4
+ * The maximum total length of a command line including the command word
+ * and the <CRLF> is 512 octets.
+ *
+ * Defer the initial response if the resulting command exceeds the limit.
+ */
+ if (LEN(session->sasl_reply) > 0
+ && strlen(mechanism) + LEN(session->sasl_reply) + 8 <= 512) {
+ smtp_chat_cmd(session, "AUTH %s %s", mechanism,
+ STR(session->sasl_reply));
+ VSTRING_RESET(session->sasl_reply); /* no deferred initial reply */
+ } else {
+ smtp_chat_cmd(session, "AUTH %s", mechanism);
+ }
+
+ /*
+ * Step through the authentication protocol until the server tells us
+ * that we are done. If session->sasl_reply is non-empty we have a
+ * deferred initial reply and expect an empty initial challenge from the
+ * server. If the server's initial challenge is non-empty we have a SASL
+ * protocol violation with both sides wanting to go first.
+ */
+ while ((resp = smtp_chat_resp(session))->code / 100 == 3) {
+
+ /*
+ * Sanity check.
+ */
+ if (++steps > 100) {
+ dsb_simple(why, "4.3.0", "SASL authentication failed; "
+ "authentication protocol loop with server %s",
+ session->namaddr);
+ return (-1);
+ }
+
+ /*
+ * Process a server challenge.
+ */
+ line = resp->str;
+ (void) mystrtok(&line, "- \t\n"); /* skip over result code */
+
+ if (LEN(session->sasl_reply) > 0) {
+
+ /*
+ * Deferred initial response, the server challenge must be empty.
+ * Cleared after actual transmission to the server.
+ */
+ if (*line) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION,
+ DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error",
+ "SASL authentication failed; non-empty initial "
+ "%s challenge from server %s: %s", mechanism,
+ session->namaddr, STR(session->sasl_reply));
+ return (-1);
+ }
+ } else {
+ result = xsasl_client_next(session->sasl_client, line,
+ session->sasl_reply);
+ if (result != XSASL_AUTH_OK) {
+ dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */
+ DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply),
+ "SASL authentication failed; "
+ "cannot authenticate to server %s: %s",
+ session->namaddr, STR(session->sasl_reply));
+ return (-1); /* Fix 200512 */
+ }
+ }
+
+ /*
+ * Send a client response.
+ */
+ smtp_chat_cmd(session, "%s", STR(session->sasl_reply));
+ VSTRING_RESET(session->sasl_reply); /* clear initial reply */
+ }
+
+ /*
+ * We completed the authentication protocol.
+ */
+ if (resp->code / 100 != 2) {
+#ifdef HAVE_SASL_AUTH_CACHE
+ /* Update the 535 authentication failure cache. */
+ if (smtp_sasl_auth_cache && resp->code == 535)
+ smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp);
+#endif
+ if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5)
+ STR(resp->dsn_buf)[0] = '4';
+ dsb_update(why, resp->dsn, DSB_DEF_ACTION,
+ DSB_MTYPE_DNS, STR(iter->host),
+ var_procname, resp->str,
+ "SASL authentication failed; server %s said: %s",
+ session->namaddr, resp->str);
+ return (0);
+ }
+ return (1);
+}
+
+/* smtp_sasl_cleanup - per-session cleanup */
+
+void smtp_sasl_cleanup(SMTP_SESSION *session)
+{
+ if (session->sasl_username) {
+ myfree(session->sasl_username);
+ session->sasl_username = 0;
+ }
+ if (session->sasl_passwd) {
+ myfree(session->sasl_passwd);
+ session->sasl_passwd = 0;
+ }
+ if (session->sasl_mechanism_list) {
+ /* allocated in smtp_sasl_helo_auth */
+ myfree(session->sasl_mechanism_list);
+ session->sasl_mechanism_list = 0;
+ }
+ if (session->sasl_client) {
+ if (msg_verbose)
+ msg_info("disposing SASL state information");
+ xsasl_client_free(session->sasl_client);
+ session->sasl_client = 0;
+ }
+ if (session->sasl_reply) {
+ vstring_free(session->sasl_reply);
+ session->sasl_reply = 0;
+ }
+}
+
+/* smtp_sasl_passivate - append serialized SASL attributes */
+
+void smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf)
+{
+}
+
+/* smtp_sasl_activate - de-serialize SASL attributes */
+
+int smtp_sasl_activate(SMTP_SESSION *session, char *buf)
+{
+ return (0);
+}
+
+#endif