diff options
Diffstat (limited to 'src/xsasl/xsasl_cyrus_server.c')
-rw-r--r-- | src/xsasl/xsasl_cyrus_server.c | 641 |
1 files changed, 641 insertions, 0 deletions
diff --git a/src/xsasl/xsasl_cyrus_server.c b/src/xsasl/xsasl_cyrus_server.c new file mode 100644 index 0000000..89e1fc9 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_server.c @@ -0,0 +1,641 @@ +/*++ +/* NAME +/* xsasl_cyrus_server 3 +/* SUMMARY +/* Cyrus SASL server-side plug-in +/* SYNOPSIS +/* #include <xsasl_cyrus_server.h> +/* +/* XSASL_SERVER_IMPL *xsasl_cyrus_server_init(server_type, path_info) +/* const char *server_type; +/* const char *path_info; +/* DESCRIPTION +/* This module implements the Cyrus SASL server-side authentication +/* plug-in. +/* +/* xsasl_cyrus_server_init() initializes the Cyrus SASL library and +/* returns an implementation handle that can be used to generate +/* SASL server instances. +/* +/* Arguments: +/* .IP server_type +/* The server type (cyrus). This argument is ignored, but it +/* could be used when one implementation provides multiple +/* variants. +/* .IP path_info +/* The base name of the SASL server configuration file (example: +/* smtpd becomes /usr/lib/sasl2/smtpd.conf). +/* 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: +/* 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 <sys/socket.h> +#include <stdlib.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <name_mask.h> +#include <stringops.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Application-specific. */ + +#include <xsasl.h> +#include <xsasl_cyrus.h> +#include <xsasl_cyrus_common.h> + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#include <sasl.h> +#include <saslutil.h> + +/* + * Silly little macros. + */ +#define STR(s) vstring_str(s) + + /* + * Macros to handle API differences between SASLv1 and SASLv2. Specifics: + * + * The SASL_LOG_* constants were renamed in SASLv2. + * + * SASLv2's sasl_server_new takes two new parameters to specify local and + * remote IP addresses for auth mechs that use them. + * + * SASLv2's sasl_server_start and sasl_server_step no longer have the errstr + * parameter. + * + * SASLv2's sasl_decode64 function takes an extra parameter for the length of + * the output buffer. + * + * The other major change is that SASLv2 now takes more responsibility for + * deallocating memory that it allocates internally. Thus, some of the + * function parameters are now 'const', to make sure we don't try to free + * them too. This is dealt with in the code later on. + */ + +#if SASL_VERSION_MAJOR < 2 +/* SASL version 1.x */ +#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ + sasl_server_new(srv, fqdn, rlm, cb, secflags, pconn) +#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen, err) +#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_step(conn, clin, clinlen, srvout, srvoutlen, err) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outlen) +typedef char *MECHANISM_TYPE; +typedef unsigned MECHANISM_COUNT_TYPE; +typedef char *SERVEROUT_TYPE; +typedef void *VOID_SERVEROUT_TYPE; + +#endif + +#if SASL_VERSION_MAJOR >= 2 +/* SASL version > 2.x */ +#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ + sasl_server_new(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) +#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen) +#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_step(conn, clin, clinlen, srvout, srvoutlen) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outmaxlen, outlen) +typedef const char *MECHANISM_TYPE; +typedef int MECHANISM_COUNT_TYPE; +typedef const char *SERVEROUT_TYPE; +typedef const void *VOID_SERVEROUT_TYPE; + +#endif + +#ifndef NO_IP_CYRUS_SASL_AUTH +#define USE_IP_CYRUS_SASL_AUTH +#endif + + /* + * The XSASL_CYRUS_SERVER object is derived from the generic XSASL_SERVER + * object. + */ +typedef struct { + XSASL_SERVER xsasl; /* generic members, must be first */ + VSTREAM *stream; /* client-server connection */ + sasl_conn_t *sasl_conn; /* SASL context */ + VSTRING *decoded; /* decoded challenge or response */ + char *username; /* authenticated user */ + char *mechanism_list; /* applicable mechanisms */ +} XSASL_CYRUS_SERVER; + + /* + * Forward declarations. + */ +static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *); +static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *, + XSASL_SERVER_CREATE_ARGS *); +static void xsasl_cyrus_server_free(XSASL_SERVER *); +static int xsasl_cyrus_server_first(XSASL_SERVER *, const char *, + const char *, VSTRING *); +static int xsasl_cyrus_server_next(XSASL_SERVER *, const char *, VSTRING *); +static int xsasl_cyrus_server_set_security(XSASL_SERVER *, const char *); +static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *); +static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *); + + /* + * SASL callback interface structure. These call-backs have no per-session + * context. + */ +#define NO_CALLBACK_CONTEXT 0 + +static sasl_callback_t callbacks[] = { + {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, NO_CALLBACK_CONTEXT}, + {SASL_CB_LIST_END, 0, 0} +}; + +/* xsasl_cyrus_server_init - create implementation handle */ + +XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *unused_server_type, + const char *path_info) +{ + const char *myname = "xsasl_cyrus_server_init"; + XSASL_SERVER_IMPL *xp; + int sasl_status; + +#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ + || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) + int sasl_major; + int sasl_minor; + int sasl_step; + + /* + * DLL hell guard. + */ + sasl_version_info((const char **) 0, (const char **) 0, + &sasl_major, &sasl_minor, + &sasl_step, (int *) 0); + if (sasl_major != SASL_VERSION_MAJOR +#if 0 + || sasl_minor != SASL_VERSION_MINOR + || sasl_step != SASL_VERSION_STEP +#endif + ) { + msg_warn("incorrect SASL library version. " + "Postfix was built with include files from version %d.%d.%d, " + "but the run-time library version is %d.%d.%d", + SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, + sasl_major, sasl_minor, sasl_step); + return (0); + } +#endif + + if (*var_cyrus_conf_path) { +#ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ + if (sasl_set_path(SASL_PATH_TYPE_CONFIG, + var_cyrus_conf_path) != SASL_OK) + msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", + var_cyrus_conf_path); +#else + msg_warn("%s is not empty, but setting the Cyrus SASL configuration " + "path is not supported with SASL library version %d.%d.%d", + VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, + SASL_VERSION_MINOR, SASL_VERSION_STEP); +#endif + } + + /* + * Initialize the library: load SASL plug-in routines, etc. + */ + if (msg_verbose) + msg_info("%s: SASL config file is %s.conf", myname, path_info); + if ((sasl_status = sasl_server_init(callbacks, path_info)) != SASL_OK) { + msg_warn("SASL per-process initialization failed: %s", + xsasl_cyrus_strerror(sasl_status)); + return (0); + } + + /* + * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it + * with our own methods or data. + */ + xp = (XSASL_SERVER_IMPL *) mymalloc(sizeof(*xp)); + xp->create = xsasl_cyrus_server_create; + xp->done = xsasl_cyrus_server_done; + return (xp); +} + +/* xsasl_cyrus_server_done - dispose of implementation */ + +static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *impl) +{ + myfree((void *) impl); + sasl_done(); +} + +/* xsasl_cyrus_server_create - create server instance */ + +static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *unused_impl, + XSASL_SERVER_CREATE_ARGS *args) +{ + const char *myname = "xsasl_cyrus_server_create"; + char *server_addr_port = 0; + char *client_addr_port = 0; + sasl_conn_t *sasl_conn = 0; + XSASL_CYRUS_SERVER *server = 0; + int sasl_status; + + if (msg_verbose) + msg_info("%s: SASL service=%s, realm=%s", + myname, args->service, args->user_realm ? + args->user_realm : "(null)"); + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \ + do { \ + if (server) { \ + xsasl_cyrus_server_free(&server->xsasl); \ + } else { \ + if (sasl_conn) \ + sasl_dispose(&sasl_conn); \ + } \ + XSASL_CYRUS_SERVER_CREATE_RETURN(x); \ + } while (0) + +#define XSASL_CYRUS_SERVER_CREATE_RETURN(x) \ + do { \ + if (server_addr_port) \ + myfree(server_addr_port); \ + if (client_addr_port) \ + myfree(client_addr_port); \ + return (x); \ + } while (0) + + /* + * Set up a new server context. + */ +#define NO_SECURITY_LAYERS (0) +#define NO_SESSION_CALLBACKS ((sasl_callback_t *) 0) +#define NO_AUTH_REALM ((char *) 0) + +#if SASL_VERSION_MAJOR >= 2 && defined(USE_IP_CYRUS_SASL_AUTH) + + /* + * Get IP address and port of local and remote endpoints for SASL. Some + * implementation supports "[ipv6addr]:port" and "ipv4addr:port" (e.g., + * https://illumos.org/man/3sasl/sasl_server_new), They still support the + * historical "address;port" syntax, so we stick with that for now. + */ + server_addr_port = (*args->server_addr && *args->server_port ? + concatenate(args->server_addr, ";", + args->server_port, (char *) 0) : 0); + client_addr_port = (*args->client_addr && *args->client_port ? + concatenate(args->client_addr, ";", + args->client_port, (char *) 0) : 0); +#else + + /* + * Don't give any IP address information to SASL. + */ +#endif + + if ((sasl_status = + SASL_SERVER_NEW(args->service, var_myhostname, + args->user_realm ? args->user_realm : NO_AUTH_REALM, + server_addr_port, client_addr_port, + NO_SESSION_CALLBACKS, NO_SECURITY_LAYERS, + &sasl_conn)) != SASL_OK) { + msg_warn("SASL per-connection server initialization: %s", + xsasl_cyrus_strerror(sasl_status)); + XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); + } + + /* + * Extend the XSASL_SERVER 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_CYRUS_SERVER *) mymalloc(sizeof(*server)); + server->xsasl.free = xsasl_cyrus_server_free; + server->xsasl.first = xsasl_cyrus_server_first; + server->xsasl.next = xsasl_cyrus_server_next; + server->xsasl.get_mechanism_list = xsasl_cyrus_server_get_mechanism_list; + server->xsasl.get_username = xsasl_cyrus_server_get_username; + server->stream = args->stream; + server->sasl_conn = sasl_conn; + server->decoded = vstring_alloc(20); + server->username = 0; + server->mechanism_list = 0; + + if (xsasl_cyrus_server_set_security(&server->xsasl, args->security_options) + != XSASL_AUTH_OK) + XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); + + XSASL_CYRUS_SERVER_CREATE_RETURN(&server->xsasl); +} + +/* xsasl_cyrus_server_set_security - set security properties */ + +static int xsasl_cyrus_server_set_security(XSASL_SERVER *xp, + const char *sasl_opts_val) +{ + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + sasl_security_properties_t sec_props; + int sasl_status; + + /* + * Security options. Some information can be found in the sasl.h include + * file. + */ + memset(&sec_props, 0, sizeof(sec_props)); + sec_props.min_ssf = 0; + sec_props.max_ssf = 0; /* don't allow real SASL + * security layer */ + if (*sasl_opts_val == 0) { + sec_props.security_flags = 0; + } else { + sec_props.security_flags = + xsasl_cyrus_security_parse_opts(sasl_opts_val); + if (sec_props.security_flags == 0) { + msg_warn("bad per-session SASL security properties"); + return (XSASL_AUTH_FAIL); + } + } + sec_props.maxbufsize = 0; + sec_props.property_names = 0; + sec_props.property_values = 0; + + if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_SEC_PROPS, + &sec_props)) != SASL_OK) { + msg_warn("SASL per-connection security setup; %s", + xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_server_get_mechanism_list - get available mechanisms */ + +static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *xp) +{ + const char *myname = "xsasl_cyrus_server_get_mechanism_list"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + MECHANISM_TYPE mechanism_list; + MECHANISM_COUNT_TYPE mechanism_count; + int sasl_status; + + /* + * Get the list of authentication mechanisms. + */ +#define UNSUPPORTED_USER ((char *) 0) +#define IGNORE_MECHANISM_LEN ((unsigned *) 0) + + if ((sasl_status = sasl_listmech(server->sasl_conn, UNSUPPORTED_USER, + "", " ", "", + &mechanism_list, + IGNORE_MECHANISM_LEN, + &mechanism_count)) != SASL_OK) { + msg_warn("%s: %s", myname, xsasl_cyrus_strerror(sasl_status)); + return (0); + } + if (mechanism_count <= 0) { + msg_warn("%s: no applicable SASL mechanisms", myname); + return (0); + } + server->mechanism_list = mystrdup(mechanism_list); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(mechanism_list); +#endif + return (server->mechanism_list); +} + +/* xsasl_cyrus_server_free - destroy server instance */ + +static void xsasl_cyrus_server_free(XSASL_SERVER *xp) +{ + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + + sasl_dispose(&server->sasl_conn); + vstring_free(server->decoded); + if (server->username) + myfree(server->username); + if (server->mechanism_list) + myfree(server->mechanism_list); + myfree((void *) server); +} + +/* xsasl_cyrus_server_auth_response - encode server first/next response */ + +static int xsasl_cyrus_server_auth_response(int sasl_status, + SERVEROUT_TYPE serverout, + unsigned serveroutlen, + VSTRING *reply) +{ + const char *myname = "xsasl_cyrus_server_auth_response"; + unsigned enc_length; + unsigned enc_length_out; + + /* + * Encode the server first/next non-error response; otherwise return the + * unencoded error text that corresponds to the SASL error status. + * + * Regarding the hairy expression below: output from sasl_encode64() comes + * in multiples of four bytes for each triple of input bytes, plus four + * bytes for any incomplete last triple, plus one byte for the null + * terminator. + */ + if (sasl_status == SASL_OK) { + vstring_strcpy(reply, ""); + return (XSASL_AUTH_DONE); + } else if (sasl_status == SASL_CONTINUE) { + if (msg_verbose) + msg_info("%s: uncoded server challenge: %.*s", + myname, (int) serveroutlen, serverout); + enc_length = ((serveroutlen + 2) / 3) * 4 + 1; + VSTRING_RESET(reply); /* Fix 200512 */ + VSTRING_SPACE(reply, enc_length); + if ((sasl_status = sasl_encode64(serverout, serveroutlen, + STR(reply), vstring_avail(reply), + &enc_length_out)) != SASL_OK) + msg_panic("%s: sasl_encode64 botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_MORE); + } else { + if (sasl_status == SASL_NOUSER) /* privacy */ + sasl_status = SASL_BADAUTH; + vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); + switch (sasl_status) { + case SASL_FAIL: + case SASL_NOMEM: + case SASL_TRYAGAIN: + case SASL_UNAVAIL: + return XSASL_AUTH_TEMP; + default: + return (XSASL_AUTH_FAIL); + } + } +} + +/* xsasl_cyrus_server_first - per-session authentication */ + +int xsasl_cyrus_server_first(XSASL_SERVER *xp, const char *sasl_method, + const char *init_response, VSTRING *reply) +{ + const char *myname = "xsasl_cyrus_server_first"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + char *dec_buffer; + unsigned dec_length; + unsigned reply_len; + unsigned serveroutlen; + int sasl_status; + SERVEROUT_TYPE serverout = 0; + int xsasl_status; + +#if SASL_VERSION_MAJOR < 2 + const char *errstr = 0; + +#endif + +#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, "")); + + /* + * SASL authentication protocol start-up. Process any initial client + * response that was sent along in the AUTH command. + */ + if (init_response) { + reply_len = strlen(init_response); + VSTRING_RESET(server->decoded); /* Fix 200512 */ + VSTRING_SPACE(server->decoded, reply_len); + if ((sasl_status = SASL_DECODE64(init_response, reply_len, + dec_buffer = STR(server->decoded), + vstring_avail(server->decoded), + &dec_length)) != SASL_OK) { + vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FORM); + } + if (msg_verbose) + msg_info("%s: decoded initial response %s", myname, dec_buffer); + } else { + dec_buffer = 0; + dec_length = 0; + } + sasl_status = SASL_SERVER_START(server->sasl_conn, sasl_method, dec_buffer, + dec_length, &serverout, + &serveroutlen, &errstr); + xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, + serveroutlen, reply); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(serverout); +#endif + return (xsasl_status); +} + +/* xsasl_cyrus_server_next - continue authentication */ + +static int xsasl_cyrus_server_next(XSASL_SERVER *xp, const char *request, + VSTRING *reply) +{ + const char *myname = "xsasl_cyrus_server_next"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + unsigned dec_length; + unsigned request_len; + unsigned serveroutlen; + int sasl_status; + SERVEROUT_TYPE serverout = 0; + int xsasl_status; + +#if SASL_VERSION_MAJOR < 2 + const char *errstr = 0; + +#endif + + request_len = strlen(request); + VSTRING_RESET(server->decoded); /* Fix 200512 */ + VSTRING_SPACE(server->decoded, request_len); + if ((sasl_status = SASL_DECODE64(request, request_len, + STR(server->decoded), + vstring_avail(server->decoded), + &dec_length)) != SASL_OK) { + vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FORM); + } + if (msg_verbose) + msg_info("%s: decoded response: %.*s", + myname, (int) dec_length, STR(server->decoded)); + sasl_status = SASL_SERVER_STEP(server->sasl_conn, STR(server->decoded), + dec_length, &serverout, + &serveroutlen, &errstr); + xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, + serveroutlen, reply); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(serverout); +#endif + return (xsasl_status); +} + +/* xsasl_cyrus_server_get_username - get authenticated username */ + +static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *xp) +{ + const char *myname = "xsasl_cyrus_server_get_username"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + VOID_SERVEROUT_TYPE serverout = 0; + int sasl_status; + + /* + * XXX Do not free(serverout). + */ + sasl_status = sasl_getprop(server->sasl_conn, SASL_USERNAME, &serverout); + if (sasl_status != SASL_OK || serverout == 0) { + msg_warn("%s: sasl_getprop SASL_USERNAME botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); + return (0); + } + if (server->username) + myfree(server->username); + server->username = mystrdup(serverout); + printable(server->username, '?'); + return (server->username); +} + +#endif |