summaryrefslogtreecommitdiffstats
path: root/src/xsasl/xsasl_dovecot_server.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
commitb7c15c31519dc44c1f691e0466badd556ffe9423 (patch)
treef944572f288bab482a615e09af627d9a2b6727d8 /src/xsasl/xsasl_dovecot_server.c
parentInitial commit. (diff)
downloadpostfix-b7c15c31519dc44c1f691e0466badd556ffe9423.tar.xz
postfix-b7c15c31519dc44c1f691e0466badd556ffe9423.zip
Adding upstream version 3.7.10.upstream/3.7.10upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/xsasl/xsasl_dovecot_server.c')
-rw-r--r--src/xsasl/xsasl_dovecot_server.c747
1 files changed, 747 insertions, 0 deletions
diff --git a/src/xsasl/xsasl_dovecot_server.c b/src/xsasl/xsasl_dovecot_server.c
new file mode 100644
index 0000000..1d1c570
--- /dev/null
+++ b/src/xsasl/xsasl_dovecot_server.c
@@ -0,0 +1,747 @@
+/*++
+/* NAME
+/* xsasl_dovecot_server 3
+/* SUMMARY
+/* Dovecot SASL server-side plug-in
+/* SYNOPSIS
+/* XSASL_SERVER_IMPL *xsasl_dovecot_server_init(server_type, appl_name)
+/* const char *server_type;
+/* const char *appl_name;
+/* DESCRIPTION
+/* This module implements the Dovecot SASL server-side authentication
+/* plug-in.
+/*
+/* .IP server_type
+/* The plug-in type that was specified to xsasl_server_init().
+/* The argument is ignored, because the Dovecot plug-in
+/* implements only one plug-in type.
+/* .IP path_info
+/* The location of the Dovecot authentication server's UNIX-domain
+/* socket. Note: the Dovecot plug-in uses late binding, therefore
+/* all connect operations are done with Postfix privileges.
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/*
+/* Panic: interface violation.
+/*
+/* Other: the routines log a warning and return an error result
+/* as specified in xsasl_server(3).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Initial implementation by:
+/* Timo Sirainen
+/* Procontrol
+/* Finland
+/*
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <connect.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <name_mask.h>
+#include <argv.h>
+#include <myaddrinfo.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include <xsasl.h>
+#include <xsasl_dovecot.h>
+
+#ifdef USE_SASL_AUTH
+
+/* Major version changes are not backwards compatible,
+ minor version numbers can be ignored. */
+#define AUTH_PROTOCOL_MAJOR_VERSION 1
+#define AUTH_PROTOCOL_MINOR_VERSION 0
+
+ /*
+ * Enforce read/write time limits, so that we can produce accurate
+ * diagnostics instead of getting killed by the watchdog timer.
+ */
+#define AUTH_TIMEOUT 10
+
+ /*
+ * Security property bitmasks.
+ */
+#define SEC_PROPS_NOPLAINTEXT (1 << 0)
+#define SEC_PROPS_NOACTIVE (1 << 1)
+#define SEC_PROPS_NODICTIONARY (1 << 2)
+#define SEC_PROPS_NOANONYMOUS (1 << 3)
+#define SEC_PROPS_FWD_SECRECY (1 << 4)
+#define SEC_PROPS_MUTUAL_AUTH (1 << 5)
+#define SEC_PROPS_PRIVATE (1 << 6)
+
+#define SEC_PROPS_POS_MASK (SEC_PROPS_MUTUAL_AUTH | SEC_PROPS_FWD_SECRECY)
+#define SEC_PROPS_NEG_MASK (SEC_PROPS_NOPLAINTEXT | SEC_PROPS_NOACTIVE | \
+ SEC_PROPS_NODICTIONARY | SEC_PROPS_NOANONYMOUS)
+
+ /*
+ * Security properties as specified in the Postfix main.cf file.
+ */
+static const NAME_MASK xsasl_dovecot_conf_sec_props[] = {
+ "noplaintext", SEC_PROPS_NOPLAINTEXT,
+ "noactive", SEC_PROPS_NOACTIVE,
+ "nodictionary", SEC_PROPS_NODICTIONARY,
+ "noanonymous", SEC_PROPS_NOANONYMOUS,
+ "forward_secrecy", SEC_PROPS_FWD_SECRECY,
+ "mutual_auth", SEC_PROPS_MUTUAL_AUTH,
+ 0, 0,
+};
+
+ /*
+ * Security properties as specified in the Dovecot protocol. See
+ * http://wiki.dovecot.org/Authentication_Protocol.
+ */
+static const NAME_MASK xsasl_dovecot_serv_sec_props[] = {
+ "plaintext", SEC_PROPS_NOPLAINTEXT,
+ "active", SEC_PROPS_NOACTIVE,
+ "dictionary", SEC_PROPS_NODICTIONARY,
+ "anonymous", SEC_PROPS_NOANONYMOUS,
+ "forward-secrecy", SEC_PROPS_FWD_SECRECY,
+ "mutual-auth", SEC_PROPS_MUTUAL_AUTH,
+ "private", SEC_PROPS_PRIVATE,
+ 0, 0,
+};
+
+ /*
+ * Class variables.
+ */
+typedef struct XSASL_DCSRV_MECH {
+ char *mech_name; /* mechanism name */
+ int sec_props; /* mechanism properties */
+ struct XSASL_DCSRV_MECH *next;
+} XSASL_DCSRV_MECH;
+
+typedef struct {
+ XSASL_SERVER_IMPL xsasl;
+ VSTREAM *sasl_stream;
+ char *socket_path;
+ XSASL_DCSRV_MECH *mechanism_list; /* unfiltered mechanism list */
+ unsigned int request_id_counter;
+} XSASL_DOVECOT_SERVER_IMPL;
+
+ /*
+ * The XSASL_DOVECOT_SERVER object is derived from the generic XSASL_SERVER
+ * object.
+ */
+typedef struct {
+ XSASL_SERVER xsasl; /* generic members, must be first */
+ XSASL_DOVECOT_SERVER_IMPL *impl;
+ unsigned int last_request_id;
+ char *service;
+ char *username; /* authenticated user */
+ VSTRING *sasl_line;
+ unsigned int sec_props; /* Postfix mechanism filter */
+ int tls_flag; /* TLS enabled in this session */
+ char *mechanism_list; /* filtered mechanism list */
+ ARGV *mechanism_argv; /* ditto */
+ char *client_addr; /* remote IP address */
+ char *server_addr; /* remote IP address */
+} XSASL_DOVECOT_SERVER;
+
+ /*
+ * Forward declarations.
+ */
+static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *);
+static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *,
+ XSASL_SERVER_CREATE_ARGS *);
+static void xsasl_dovecot_server_free(XSASL_SERVER *);
+static int xsasl_dovecot_server_first(XSASL_SERVER *, const char *,
+ const char *, VSTRING *);
+static int xsasl_dovecot_server_next(XSASL_SERVER *, const char *, VSTRING *);
+static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *);
+static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *);
+
+/* xsasl_dovecot_server_mech_append - append server mechanism entry */
+
+static void xsasl_dovecot_server_mech_append(XSASL_DCSRV_MECH **mech_list,
+ const char *mech_name, int sec_props)
+{
+ XSASL_DCSRV_MECH **mpp;
+ XSASL_DCSRV_MECH *mp;
+
+ for (mpp = mech_list; *mpp != 0; mpp = &mpp[0]->next)
+ /* void */ ;
+
+ mp = (XSASL_DCSRV_MECH *) mymalloc(sizeof(*mp));
+ mp->mech_name = mystrdup(mech_name);
+ mp->sec_props = sec_props;
+ mp->next = 0;
+ *mpp = mp;
+}
+
+/* xsasl_dovecot_server_mech_free - destroy server mechanism list */
+
+static void xsasl_dovecot_server_mech_free(XSASL_DCSRV_MECH *mech_list)
+{
+ XSASL_DCSRV_MECH *mp;
+ XSASL_DCSRV_MECH *next;
+
+ for (mp = mech_list; mp != 0; mp = next) {
+ myfree(mp->mech_name);
+ next = mp->next;
+ myfree((void *) mp);
+ }
+}
+
+/* xsasl_dovecot_server_mech_filter - filter server mechanism list */
+
+static char *xsasl_dovecot_server_mech_filter(ARGV *mechanism_argv,
+ XSASL_DCSRV_MECH *mechanism_list,
+ unsigned int conf_props)
+{
+ const char *myname = "xsasl_dovecot_server_mech_filter";
+ unsigned int pos_conf_props = (conf_props & SEC_PROPS_POS_MASK);
+ unsigned int neg_conf_props = (conf_props & SEC_PROPS_NEG_MASK);
+ VSTRING *mechanisms_str = vstring_alloc(10);
+ XSASL_DCSRV_MECH *mp;
+
+ /*
+ * Match Postfix properties against Dovecot server properties.
+ */
+ for (mp = mechanism_list; mp != 0; mp = mp->next) {
+ if ((mp->sec_props & pos_conf_props) == pos_conf_props
+ && (mp->sec_props & neg_conf_props) == 0) {
+ if (VSTRING_LEN(mechanisms_str) > 0)
+ VSTRING_ADDCH(mechanisms_str, ' ');
+ vstring_strcat(mechanisms_str, mp->mech_name);
+ argv_add(mechanism_argv, mp->mech_name, (char *) 0);
+ if (msg_verbose)
+ msg_info("%s: keep mechanism: %s", myname, mp->mech_name);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: skip mechanism: %s", myname, mp->mech_name);
+ }
+ }
+ return (vstring_export(mechanisms_str));
+}
+
+/* xsasl_dovecot_server_connect - initial auth server handshake */
+
+static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp)
+{
+ const char *myname = "xsasl_dovecot_server_connect";
+ VSTRING *line_str;
+ VSTREAM *sasl_stream;
+ char *line, *cmd, *mech_name;
+ unsigned int major_version, minor_version;
+ int fd, success, have_mech_line;
+ int sec_props;
+ const char *path;
+
+ if (msg_verbose)
+ msg_info("%s: Connecting", myname);
+
+ /*
+ * Not documented, but necessary for testing.
+ */
+ path = xp->socket_path;
+ if (strncmp(path, "inet:", 5) == 0) {
+ fd = inet_connect(path + 5, BLOCKING, AUTH_TIMEOUT);
+ } else {
+ if (strncmp(path, "unix:", 5) == 0)
+ path += 5;
+ fd = unix_connect(path, BLOCKING, AUTH_TIMEOUT);
+ }
+ if (fd < 0) {
+ msg_warn("SASL: Connect to Dovecot auth socket '%s' failed: %m",
+ xp->socket_path);
+ return (-1);
+ }
+ sasl_stream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(sasl_stream,
+ CA_VSTREAM_CTL_PATH(xp->socket_path),
+ CA_VSTREAM_CTL_TIMEOUT(AUTH_TIMEOUT),
+ CA_VSTREAM_CTL_END);
+
+ /* XXX Encapsulate for logging. */
+ vstream_fprintf(sasl_stream,
+ "VERSION\t%u\t%u\n"
+ "CPID\t%u\n",
+ AUTH_PROTOCOL_MAJOR_VERSION,
+ AUTH_PROTOCOL_MINOR_VERSION,
+ (unsigned int) getpid());
+ if (vstream_fflush(sasl_stream) == VSTREAM_EOF) {
+ msg_warn("SASL: Couldn't send handshake: %m");
+ return (-1);
+ }
+ success = 0;
+ have_mech_line = 0;
+ line_str = vstring_alloc(256);
+ /* XXX Encapsulate for logging. */
+ while (vstring_get_nonl(line_str, sasl_stream) != VSTREAM_EOF) {
+ line = vstring_str(line_str);
+
+ if (msg_verbose)
+ msg_info("%s: auth reply: %s", myname, line);
+
+ cmd = line;
+ line = split_at(line, '\t');
+
+ if (strcmp(cmd, "VERSION") == 0) {
+ if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) {
+ msg_warn("SASL: Protocol version error");
+ break;
+ }
+ if (major_version != AUTH_PROTOCOL_MAJOR_VERSION) {
+ /* Major version is different from ours. */
+ msg_warn("SASL: Protocol version mismatch (%d vs. %d)",
+ major_version, AUTH_PROTOCOL_MAJOR_VERSION);
+ break;
+ }
+ } else if (strcmp(cmd, "MECH") == 0 && line != NULL) {
+ mech_name = line;
+ have_mech_line = 1;
+ line = split_at(line, '\t');
+ if (line != 0) {
+ sec_props =
+ name_mask_delim_opt(myname,
+ xsasl_dovecot_serv_sec_props,
+ line, "\t",
+ NAME_MASK_ANY_CASE | NAME_MASK_IGNORE);
+ if ((sec_props & SEC_PROPS_PRIVATE) != 0)
+ continue;
+ } else
+ sec_props = 0;
+ xsasl_dovecot_server_mech_append(&xp->mechanism_list, mech_name,
+ sec_props);
+ } else if (strcmp(cmd, "SPID") == 0) {
+
+ /*
+ * Unfortunately the auth protocol handshake wasn't designed well
+ * to differentiate between auth-client/userdb/master.
+ * auth-userdb and auth-master send VERSION + SPID lines only and
+ * nothing afterwards, while auth-client sends VERSION + MECH +
+ * SPID + CUID + more. The simplest way that we can determine if
+ * we've connected to the correct socket is to see if MECH line
+ * exists or not (alternatively we'd have to have a small timeout
+ * after SPID to see if CUID is sent or not).
+ */
+ if (!have_mech_line) {
+ msg_warn("SASL: Connected to wrong auth socket (auth-master instead of auth-client)");
+ break;
+ }
+ } else if (strcmp(cmd, "DONE") == 0) {
+ /* Handshake finished. */
+ success = 1;
+ break;
+ } else {
+ /* ignore any unknown commands */
+ }
+ }
+ vstring_free(line_str);
+
+ if (!success) {
+ /* handshake failed */
+ (void) vstream_fclose(sasl_stream);
+ return (-1);
+ }
+ xp->sasl_stream = sasl_stream;
+ return (0);
+}
+
+/* xsasl_dovecot_server_disconnect - dispose of server connection state */
+
+static void xsasl_dovecot_server_disconnect(XSASL_DOVECOT_SERVER_IMPL *xp)
+{
+ if (xp->sasl_stream) {
+ (void) vstream_fclose(xp->sasl_stream);
+ xp->sasl_stream = 0;
+ }
+ if (xp->mechanism_list) {
+ xsasl_dovecot_server_mech_free(xp->mechanism_list);
+ xp->mechanism_list = 0;
+ }
+}
+
+/* xsasl_dovecot_server_init - create implementation handle */
+
+XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *server_type,
+ const char *path_info)
+{
+ XSASL_DOVECOT_SERVER_IMPL *xp;
+
+ xp = (XSASL_DOVECOT_SERVER_IMPL *) mymalloc(sizeof(*xp));
+ xp->xsasl.create = xsasl_dovecot_server_create;
+ xp->xsasl.done = xsasl_dovecot_server_done;
+ xp->socket_path = mystrdup(path_info);
+ xp->sasl_stream = 0;
+ xp->mechanism_list = 0;
+ xp->request_id_counter = 0;
+ return (&xp->xsasl);
+}
+
+/* xsasl_dovecot_server_done - dispose of implementation */
+
+static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *impl)
+{
+ XSASL_DOVECOT_SERVER_IMPL *xp = (XSASL_DOVECOT_SERVER_IMPL *) impl;
+
+ xsasl_dovecot_server_disconnect(xp);
+ myfree(xp->socket_path);
+ myfree((void *) impl);
+}
+
+/* xsasl_dovecot_server_create - create server instance */
+
+static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *impl,
+ XSASL_SERVER_CREATE_ARGS *args)
+{
+ const char *myname = "xsasl_dovecot_server_create";
+ XSASL_DOVECOT_SERVER *server;
+ struct sockaddr_storage ss;
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SOCKADDR_SIZE salen;
+ MAI_HOSTADDR_STR server_addr;
+
+ if (msg_verbose)
+ msg_info("%s: SASL service=%s, realm=%s",
+ myname, args->service, args->user_realm ?
+ args->user_realm : "(null)");
+
+ /*
+ * Extend the XSASL_SERVER_IMPL object with our own data. We use
+ * long-lived conversion buffers rather than local variables to avoid
+ * memory leaks in case of read/write timeout or I/O error.
+ */
+ server = (XSASL_DOVECOT_SERVER *) mymalloc(sizeof(*server));
+ server->xsasl.free = xsasl_dovecot_server_free;
+ server->xsasl.first = xsasl_dovecot_server_first;
+ server->xsasl.next = xsasl_dovecot_server_next;
+ server->xsasl.get_mechanism_list = xsasl_dovecot_server_get_mechanism_list;
+ server->xsasl.get_username = xsasl_dovecot_server_get_username;
+ server->impl = (XSASL_DOVECOT_SERVER_IMPL *) impl;
+ server->sasl_line = vstring_alloc(256);
+ server->username = 0;
+ server->service = mystrdup(args->service);
+ server->last_request_id = 0;
+ server->mechanism_list = 0;
+ server->mechanism_argv = 0;
+ server->tls_flag = args->tls_flag;
+ server->sec_props =
+ name_mask_opt(myname, xsasl_dovecot_conf_sec_props,
+ args->security_options,
+ NAME_MASK_ANY_CASE | NAME_MASK_FATAL);
+ server->client_addr = mystrdup(args->client_addr);
+
+ /*
+ * XXX Temporary code until smtpd_peer.c is updated.
+ */
+ if (args->server_addr && *args->server_addr) {
+ server->server_addr = mystrdup(args->server_addr);
+ } else {
+ salen = sizeof(ss);
+ if (getsockname(vstream_fileno(args->stream), sa, &salen) < 0
+ || sockaddr_to_hostaddr(sa, salen, &server_addr, 0, 0) != 0)
+ server_addr.buf[0] = 0;
+ server->server_addr = mystrdup(server_addr.buf);
+ }
+
+ return (&server->xsasl);
+}
+
+/* xsasl_dovecot_server_get_mechanism_list - get available mechanisms */
+
+static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *xp)
+{
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+
+ if (!server->impl->sasl_stream) {
+ if (xsasl_dovecot_server_connect(server->impl) < 0)
+ return (0);
+ }
+ if (server->mechanism_list == 0) {
+ server->mechanism_argv = argv_alloc(2);
+ server->mechanism_list =
+ xsasl_dovecot_server_mech_filter(server->mechanism_argv,
+ server->impl->mechanism_list,
+ server->sec_props);
+ }
+ return (server->mechanism_list[0] ? server->mechanism_list : 0);
+}
+
+/* xsasl_dovecot_server_free - destroy server instance */
+
+static void xsasl_dovecot_server_free(XSASL_SERVER *xp)
+{
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+
+ vstring_free(server->sasl_line);
+ if (server->username)
+ myfree(server->username);
+ if (server->mechanism_list) {
+ myfree(server->mechanism_list);
+ argv_free(server->mechanism_argv);
+ }
+ myfree(server->service);
+ myfree(server->server_addr);
+ myfree(server->client_addr);
+ myfree((void *) server);
+}
+
+/* xsasl_dovecot_server_auth_response - encode server first/next response */
+
+static int xsasl_dovecot_parse_reply(XSASL_DOVECOT_SERVER *server, char **line)
+{
+ char *id;
+
+ if (*line == NULL) {
+ msg_warn("SASL: Protocol error");
+ return -1;
+ }
+ id = *line;
+ *line = split_at(*line, '\t');
+
+ if (strtoul(id, NULL, 0) != server->last_request_id) {
+ /* reply to another request, shouldn't really happen.. */
+ return -1;
+ }
+ return 0;
+}
+
+static void xsasl_dovecot_parse_reply_args(XSASL_DOVECOT_SERVER *server,
+ char *line, VSTRING *reply,
+ int success)
+{
+ char *next;
+
+ if (server->username) {
+ myfree(server->username);
+ server->username = 0;
+ }
+
+ /*
+ * Note: TAB is part of the Dovecot protocol and must not appear in
+ * legitimate Dovecot usernames, otherwise the protocol would break.
+ */
+ for (; line != NULL; line = next) {
+ next = split_at(line, '\t');
+ if (strncmp(line, "user=", 5) == 0) {
+ server->username = mystrdup(line + 5);
+ printable(server->username, '?');
+ } else if (strncmp(line, "reason=", 7) == 0) {
+ if (!success) {
+ printable(line + 7, '?');
+ vstring_strcpy(reply, line + 7);
+ }
+ }
+ }
+}
+
+/* xsasl_dovecot_handle_reply - receive and process auth reply */
+
+static int xsasl_dovecot_handle_reply(XSASL_DOVECOT_SERVER *server,
+ VSTRING *reply)
+{
+ const char *myname = "xsasl_dovecot_handle_reply";
+ char *line, *cmd;
+
+ /* XXX Encapsulate for logging. */
+ while (vstring_get_nonl(server->sasl_line,
+ server->impl->sasl_stream) != VSTREAM_EOF) {
+ line = vstring_str(server->sasl_line);
+
+ if (msg_verbose)
+ msg_info("%s: auth reply: %s", myname, line);
+
+ cmd = line;
+ line = split_at(line, '\t');
+
+ if (strcmp(cmd, "OK") == 0) {
+ if (xsasl_dovecot_parse_reply(server, &line) == 0) {
+ /* authentication successful */
+ xsasl_dovecot_parse_reply_args(server, line, reply, 1);
+ if (server->username == 0) {
+ msg_warn("missing Dovecot server %s username field", cmd);
+ vstring_strcpy(reply, "Authentication backend error");
+ return XSASL_AUTH_FAIL;
+ }
+ return XSASL_AUTH_DONE;
+ }
+ } else if (strcmp(cmd, "CONT") == 0) {
+ if (xsasl_dovecot_parse_reply(server, &line) == 0) {
+ if (line == 0) {
+ msg_warn("missing Dovecot server %s reply field", cmd);
+ vstring_strcpy(reply, "Authentication backend error");
+ return XSASL_AUTH_FAIL;
+ }
+ vstring_strcpy(reply, line);
+ return XSASL_AUTH_MORE;
+ }
+ } else if (strcmp(cmd, "FAIL") == 0) {
+ if (xsasl_dovecot_parse_reply(server, &line) == 0) {
+ /* authentication failure */
+ xsasl_dovecot_parse_reply_args(server, line, reply, 0);
+ return XSASL_AUTH_FAIL;
+ }
+ } else {
+ /* ignore */
+ }
+ }
+
+ vstring_strcpy(reply, "Connection lost to authentication server");
+ return XSASL_AUTH_TEMP;
+}
+
+/* is_valid_base64 - input sanitized */
+
+static int is_valid_base64(const char *data)
+{
+
+ /*
+ * XXX Maybe use ISALNUM() (isascii && isalnum, i.e. locale independent).
+ */
+ for (; *data != '\0'; data++) {
+ if (!((*data >= '0' && *data <= '9') ||
+ (*data >= 'a' && *data <= 'z') ||
+ (*data >= 'A' && *data <= 'Z') ||
+ *data == '+' || *data == '/' || *data == '='))
+ return 0;
+ }
+ return 1;
+}
+
+/* xsasl_dovecot_server_first - per-session authentication */
+
+int xsasl_dovecot_server_first(XSASL_SERVER *xp, const char *sasl_method,
+ const char *init_response, VSTRING *reply)
+{
+ const char *myname = "xsasl_dovecot_server_first";
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+ int i;
+ char **cpp;
+
+#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3))
+
+ if (msg_verbose)
+ msg_info("%s: sasl_method %s%s%s", myname, sasl_method,
+ IFELSE(init_response, ", init_response ", ""),
+ IFELSE(init_response, init_response, ""));
+
+ if (server->mechanism_argv == 0)
+ msg_panic("%s: no mechanism list", myname);
+
+ for (cpp = server->mechanism_argv->argv; /* see below */ ; cpp++) {
+ if (*cpp == 0) {
+ vstring_strcpy(reply, "Invalid authentication mechanism");
+ return XSASL_AUTH_FAIL;
+ }
+ if (strcasecmp(sasl_method, *cpp) == 0)
+ break;
+ }
+ if (init_response)
+ if (!is_valid_base64(init_response)) {
+ vstring_strcpy(reply, "Invalid base64 data in initial response");
+ return XSASL_AUTH_FAIL;
+ }
+ for (i = 0; i < 2; i++) {
+ if (!server->impl->sasl_stream) {
+ if (xsasl_dovecot_server_connect(server->impl) < 0)
+ return XSASL_AUTH_TEMP;
+ }
+ /* send the request */
+ server->last_request_id = ++server->impl->request_id_counter;
+ /* XXX Encapsulate for logging. */
+ vstream_fprintf(server->impl->sasl_stream,
+ "AUTH\t%u\t%s\tservice=%s\tnologin\tlip=%s\trip=%s",
+ server->last_request_id, sasl_method,
+ server->service, server->server_addr,
+ server->client_addr);
+ if (server->tls_flag)
+ /* XXX Encapsulate for logging. */
+ vstream_fputs("\tsecured", server->impl->sasl_stream);
+ if (init_response) {
+
+ /*
+ * initial response is already base64 encoded, so we can send it
+ * directly.
+ */
+ /* XXX Encapsulate for logging. */
+ vstream_fprintf(server->impl->sasl_stream,
+ "\tresp=%s", init_response);
+ }
+ /* XXX Encapsulate for logging. */
+ VSTREAM_PUTC('\n', server->impl->sasl_stream);
+
+ if (vstream_fflush(server->impl->sasl_stream) != VSTREAM_EOF)
+ break;
+
+ if (i == 1) {
+ vstring_strcpy(reply, "Can't connect to authentication server");
+ return XSASL_AUTH_TEMP;
+ }
+
+ /*
+ * Reconnect and try again.
+ */
+ xsasl_dovecot_server_disconnect(server->impl);
+ }
+
+ return xsasl_dovecot_handle_reply(server, reply);
+}
+
+/* xsasl_dovecot_server_next - continue authentication */
+
+static int xsasl_dovecot_server_next(XSASL_SERVER *xp, const char *request,
+ VSTRING *reply)
+{
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+
+ if (!is_valid_base64(request)) {
+ vstring_strcpy(reply, "Invalid base64 data in continued response");
+ return XSASL_AUTH_FAIL;
+ }
+ /* XXX Encapsulate for logging. */
+ vstream_fprintf(server->impl->sasl_stream,
+ "CONT\t%u\t%s\n", server->last_request_id, request);
+ if (vstream_fflush(server->impl->sasl_stream) == VSTREAM_EOF) {
+ vstring_strcpy(reply, "Connection lost to authentication server");
+ return XSASL_AUTH_TEMP;
+ }
+ return xsasl_dovecot_handle_reply(server, reply);
+}
+
+/* xsasl_dovecot_server_get_username - get authenticated username */
+
+static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *xp)
+{
+ XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
+
+ return (server->username);
+}
+
+#endif