#include <sys_defs.h>
#include <stdlib.h>
#include <string.h>
#include <msg.h>
#include <mymalloc.h>
#include <stringops.h>
#include <split_at.h>
#include <mail_params.h>
#include <string_list.h>
#include <maps.h>
#include <mail_addr_find.h>
#include <xsasl.h>
#include "smtp.h"
#include "smtp_sasl.h"
#include "smtp_sasl_auth_cache.h"
#ifdef USE_SASL_AUTH
static MAPS *smtp_sasl_passwd_map;
STRING_LIST *smtp_sasl_mechs;
static XSASL_CLIENT_IMPL *smtp_sasl_impl;
#ifdef HAVE_SASL_AUTH_CACHE
static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache;
#endif
int smtp_sasl_passwd_lookup(SMTP_SESSION *session)
{
const char *myname = "smtp_sasl_passwd_lookup";
SMTP_STATE *state = session->state;
const char *value;
char *passwd;
if (smtp_sasl_passwd_map == 0)
msg_panic("%s: passwd map not initialized", myname);
if (((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0
&& var_smtp_sender_auth && state->request->sender[0]
&& (value = mail_addr_find(smtp_sasl_passwd_map,
state->request->sender, (char **) 0)) != 0)
|| (value = maps_find(smtp_sasl_passwd_map, session->host, 0)) != 0
|| (value = maps_find(smtp_sasl_passwd_map, session->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, session->host,
session->sasl_username, session->sasl_passwd);
return (1);
} else {
if (msg_verbose)
msg_info("%s: no auth info found (sender=`%s', host=`%s')",
myname, state->request->sender, session->host);
return (0);
}
}
void smtp_sasl_initialize(void)
{
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_SMTP_SASL_PASSWD);
smtp_sasl_passwd_map = maps_create("smtp_sasl_passwd",
var_smtp_sasl_passwd,
DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type,
var_smtp_sasl_path)) == 0)
msg_fatal("SASL library initialization");
if (*var_smtp_sasl_mechs)
smtp_sasl_mechs = string_list_init(MATCH_FLAG_NONE,
var_smtp_sasl_mechs);
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 " VAR_SMTP_SASL_AUTH_CACHE_NAME " setting");
#endif
}
}
void smtp_sasl_connect(SMTP_SESSION *session)
{
session->sasl_mechanism_list = 0;
session->sasl_username = 0;
session->sasl_passwd = 0;
session->sasl_client = 0;
session->sasl_reply = 0;
}
void smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
const char *sasl_opts_val)
{
if (msg_verbose)
msg_info("starting new SASL client");
if ((session->sasl_client =
xsasl_client_create(smtp_sasl_impl, session->stream, var_procname,
session->host, sasl_opts_val)) == 0)
msg_fatal("SASL per-connection initialization failed");
session->sasl_reply = vstring_alloc(20);
}
int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
{
const char *myname = "smtp_sasl_authenticate";
SMTP_RESP *resp;
const char *mechanism;
int result;
char *line;
int steps = 0;
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);
#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,
session->host, var_procname, resp_str,
"SASL [CACHED] authentication failed; server %s said: %s",
session->host, resp_str);
return (0);
}
#endif
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);
}
if (LEN(session->sasl_reply) > 0) {
smtp_chat_cmd(session, "AUTH %s %s", mechanism,
STR(session->sasl_reply));
} else {
smtp_chat_cmd(session, "AUTH %s", mechanism);
}
while ((resp = smtp_chat_resp(session))->code / 100 == 3) {
if (++steps > 100) {
dsb_simple(why, "4.3.0", "SASL authentication failed; "
"authentication protocol loop with server %s",
session->namaddr);
return (-1);
}
line = resp->str;
(void) mystrtok(&line, "- \t\n");
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,
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);
}
smtp_chat_cmd(session, "%s", STR(session->sasl_reply));
}
if (resp->code / 100 != 2) {
#ifdef HAVE_SASL_AUTH_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, session->host,
var_procname, resp->str,
"SASL authentication failed; server %s said: %s",
session->namaddr, resp->str);
return (0);
}
return (1);
}
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) {
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;
}
}
void smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf)
{
}
int smtp_sasl_activate(SMTP_SESSION *session, char *buf)
{
return (0);
}
#endif