diff options
Diffstat (limited to 'src/smtp/smtp_sasl_glue.c')
-rw-r--r-- | src/smtp/smtp_sasl_glue.c | 512 |
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 |