diff options
Diffstat (limited to 'src/xsasl/xsasl_cyrus_client.c')
-rw-r--r-- | src/xsasl/xsasl_cyrus_client.c | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/src/xsasl/xsasl_cyrus_client.c b/src/xsasl/xsasl_cyrus_client.c new file mode 100644 index 0000000..fc799c9 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_client.c @@ -0,0 +1,585 @@ +/*++ +/* NAME +/* xsasl_cyrus_client 3 +/* SUMMARY +/* Cyrus SASL client-side plug-in +/* SYNOPSIS +/* #include <xsasl_cyrus_client.h> +/* +/* XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(client_type, path_info) +/* const char *client_type; +/* DESCRIPTION +/* This module implements the Cyrus SASL client-side authentication +/* plug-in. +/* +/* xsasl_cyrus_client_init() initializes the Cyrus SASL library and +/* returns an implementation handle that can be used to generate +/* SASL client instances. +/* +/* Arguments: +/* .IP client_type +/* The plug-in SASL client type (cyrus). This argument is +/* ignored, but it could be used when one implementation +/* provides multiple variants. +/* .IP path_info +/* Implementation-specific information to specify the location +/* of a configuration file, rendez-vous point, etc. This +/* information is ignored by the Cyrus SASL client plug-in. +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* Panic: interface violation. +/* +/* Other: the routines log a warning and return an error result +/* as specified in xsasl_client(3). +/* SEE ALSO +/* xsasl_client(3) Client API +/* 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> + + /* + * 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_client_new takes two new parameters to specify local and + * remote IP addresses for auth mechs that use them. + * + * SASLv2's sasl_client_start function no longer takes the secret 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_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ + sasl_client_new(srv, fqdn, prompt, secflags, pconn) +#define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ + sasl_client_start(conn, mechlst, secret, prompt, clout, cllen, mech) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outlen) +typedef char *CLIENTOUT_TYPE; + +#endif + +#if SASL_VERSION_MAJOR >= 2 +/* SASL version > 2.x */ +#define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ + sasl_client_new(srv, fqdn, lport, rport, prompt, secflags, pconn) +#define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ + sasl_client_start(conn, mechlst, prompt, clout, cllen, mech) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outmaxlen, outlen) +typedef const char *CLIENTOUT_TYPE; + +#endif + + /* + * The XSASL_CYRUS_CLIENT object is derived from the generic XSASL_CLIENT + * object. + */ +typedef struct { + XSASL_CLIENT xsasl; /* generic members, must be first */ + VSTREAM *stream; /* client-server connection */ + sasl_conn_t *sasl_conn; /* SASL context */ + VSTRING *decoded; /* decoded server challenge */ + sasl_callback_t *callbacks; /* user/password lookup */ + char *username; + char *password; +} XSASL_CYRUS_CLIENT; + + /* + * Forward declarations. + */ +static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *); +static XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *, + XSASL_CLIENT_CREATE_ARGS *); +static int xsasl_cyrus_client_set_security(XSASL_CLIENT *, const char *); +static int xsasl_cyrus_client_first(XSASL_CLIENT *, const char *, const char *, + const char *, const char **, VSTRING *); +static int xsasl_cyrus_client_next(XSASL_CLIENT *, const char *, VSTRING *); +static void xsasl_cyrus_client_free(XSASL_CLIENT *); + +/* xsasl_cyrus_client_get_user - username lookup call-back routine */ + +static int xsasl_cyrus_client_get_user(void *context, int unused_id, + const char **result, + unsigned *len) +{ + const char *myname = "xsasl_cyrus_client_get_user"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context; + + if (msg_verbose) + msg_info("%s: %s", myname, client->username); + + /* + * Sanity check. + */ + if (client->password == 0) + msg_panic("%s: no username looked up", myname); + + *result = client->username; + if (len) + *len = strlen(client->username); + return (SASL_OK); +} + +/* xsasl_cyrus_client_get_passwd - password lookup call-back routine */ + +static int xsasl_cyrus_client_get_passwd(sasl_conn_t *conn, void *context, + int id, sasl_secret_t **psecret) +{ + const char *myname = "xsasl_cyrus_client_get_passwd"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context; + int len; + + if (msg_verbose) + msg_info("%s: %s", myname, client->password); + + /* + * Sanity check. + */ + if (!conn || !psecret || id != SASL_CB_PASS) + return (SASL_BADPARAM); + if (client->password == 0) + msg_panic("%s: no password looked up", myname); + + /* + * Convert the password into a counted string. + */ + len = strlen(client->password); + if ((*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len)) == 0) + return (SASL_NOMEM); + (*psecret)->len = len; + memcpy((*psecret)->data, client->password, len + 1); + + return (SASL_OK); +} + +/* xsasl_cyrus_client_init - initialize Cyrus SASL library */ + +XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *unused_client_type, + const char *unused_path_info) +{ + XSASL_CLIENT_IMPL *xp; + int sasl_status; + + /* + * Global callbacks. These have no per-session context. + */ + static sasl_callback_t callbacks[] = { + {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, 0}, + {SASL_CB_LIST_END, 0, 0} + }; + +#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 SASL library. + */ + if ((sasl_status = sasl_client_init(callbacks)) != SASL_OK) { + msg_warn("SASL library initialization error: %s", + xsasl_cyrus_strerror(sasl_status)); + return (0); + } + + /* + * Return a generic XSASL_CLIENT_IMPL object. We don't need to extend it + * with our own methods or data. + */ + xp = (XSASL_CLIENT_IMPL *) mymalloc(sizeof(*xp)); + xp->create = xsasl_cyrus_client_create; + xp->done = xsasl_cyrus_client_done; + return (xp); +} + +/* xsasl_cyrus_client_done - dispose of implementation */ + +static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *impl) +{ + myfree((void *) impl); + sasl_done(); +} + +/* xsasl_cyrus_client_create - per-session SASL initialization */ + +XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *unused_impl, + XSASL_CLIENT_CREATE_ARGS *args) +{ + XSASL_CYRUS_CLIENT *client = 0; + static sasl_callback_t callbacks[] = { + {SASL_CB_USER, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, + {SASL_CB_AUTHNAME, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, + {SASL_CB_PASS, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_passwd, 0}, + {SASL_CB_LIST_END, 0, 0} + }; + sasl_conn_t *sasl_conn = 0; + sasl_callback_t *custom_callbacks = 0; + sasl_callback_t *cp; + int sasl_status; + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(x) \ + do { \ + if (client) { \ + xsasl_cyrus_client_free(&client->xsasl); \ + } else { \ + if (custom_callbacks) \ + myfree((void *) custom_callbacks); \ + if (sasl_conn) \ + sasl_dispose(&sasl_conn); \ + } \ + return (x); \ + } while (0) + + /* + * Per-session initialization. Provide each session with its own callback + * context. + */ +#define NULL_SECFLAGS 0 + + custom_callbacks = (sasl_callback_t *) mymalloc(sizeof(callbacks)); + memcpy((void *) custom_callbacks, callbacks, sizeof(callbacks)); + +#define NULL_SERVER_ADDR ((char *) 0) +#define NULL_CLIENT_ADDR ((char *) 0) + + if ((sasl_status = SASL_CLIENT_NEW(args->service, args->server_name, + NULL_CLIENT_ADDR, NULL_SERVER_ADDR, + var_cyrus_sasl_authzid ? custom_callbacks : + custom_callbacks + 1, NULL_SECFLAGS, + &sasl_conn)) != SASL_OK) { + msg_warn("per-session SASL client initialization: %s", + xsasl_cyrus_strerror(sasl_status)); + XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); + } + + /* + * Extend the XSASL_CLIENT object with our own state. We use long-lived + * conversion buffers rather than local variables to avoid memory leaks + * in case of read/write timeout or I/O error. + * + * XXX If we enable SASL encryption, there needs to be a way to inform the + * application, so that they can turn off connection caching, refuse + * STARTTLS, etc. + */ + client = (XSASL_CYRUS_CLIENT *) mymalloc(sizeof(*client)); + client->xsasl.free = xsasl_cyrus_client_free; + client->xsasl.first = xsasl_cyrus_client_first; + client->xsasl.next = xsasl_cyrus_client_next; + client->stream = args->stream; + client->sasl_conn = sasl_conn; + client->callbacks = custom_callbacks; + client->decoded = vstring_alloc(20); + client->username = 0; + client->password = 0; + + for (cp = custom_callbacks; cp->id != SASL_CB_LIST_END; cp++) + cp->context = (void *) client; + + if (xsasl_cyrus_client_set_security(&client->xsasl, + args->security_options) + != XSASL_AUTH_OK) + XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); + + return (&client->xsasl); +} + +/* xsasl_cyrus_client_set_security - set security properties */ + +static int xsasl_cyrus_client_set_security(XSASL_CLIENT *xp, + const char *sasl_opts_val) +{ + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + sasl_security_properties_t sec_props; + int sasl_status; + + /* + * Per-session security properties. XXX This routine is not sufficiently + * documented. What is the purpose of all this? + */ + 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(client->sasl_conn, SASL_SEC_PROPS, + &sec_props)) != SASL_OK) { + msg_warn("set per-session SASL security properties: %s", + xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_client_first - run authentication protocol */ + +static int xsasl_cyrus_client_first(XSASL_CLIENT *xp, + const char *mechanism_list, + const char *username, + const char *password, + const char **mechanism, + VSTRING *init_resp) +{ + const char *myname = "xsasl_cyrus_client_first"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + unsigned enc_length; + unsigned enc_length_out; + CLIENTOUT_TYPE clientout; + unsigned clientoutlen; + int sasl_status; + +#define NO_SASL_SECRET 0 +#define NO_SASL_INTERACTION 0 + + /* + * Save the username and password for the call-backs. + */ + if (client->username) + myfree(client->username); + client->username = mystrdup(username); + if (client->password) + myfree(client->password); + client->password = mystrdup(password); + + /* + * Start the client side authentication protocol. + */ + sasl_status = SASL_CLIENT_START((sasl_conn_t *) client->sasl_conn, + mechanism_list, + NO_SASL_SECRET, NO_SASL_INTERACTION, + &clientout, &clientoutlen, mechanism); + if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { + vstring_strcpy(init_resp, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + + /* + * Generate the AUTH command and the optional initial client response. + * sasl_encode64() produces four bytes for each complete or incomplete + * triple of input bytes. Allocate an extra byte for string termination. + */ +#define ENCODE64_LENGTH(n) ((((n) + 2) / 3) * 4) + + if (clientoutlen > 0) { + if (msg_verbose) { + escape(client->decoded, clientout, clientoutlen); + msg_info("%s: uncoded initial reply: %s", + myname, STR(client->decoded)); + } + enc_length = ENCODE64_LENGTH(clientoutlen) + 1; + VSTRING_RESET(init_resp); /* Fix 200512 */ + VSTRING_SPACE(init_resp, enc_length); + if ((sasl_status = sasl_encode64(clientout, clientoutlen, + STR(init_resp), + vstring_avail(init_resp), + &enc_length_out)) != SASL_OK) + msg_panic("%s: sasl_encode64 botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); + vstring_set_payload_size(init_resp, enc_length_out); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(clientout); +#endif + } else { + vstring_strcpy(init_resp, ""); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_client_next - continue authentication */ + +static int xsasl_cyrus_client_next(XSASL_CLIENT *xp, const char *server_reply, + VSTRING *client_reply) +{ + const char *myname = "xsasl_cyrus_client_next"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + unsigned enc_length; + unsigned enc_length_out; + CLIENTOUT_TYPE clientout; + unsigned clientoutlen; + unsigned serverinlen; + int sasl_status; + + /* + * Process a server challenge. + */ + serverinlen = strlen(server_reply); + VSTRING_RESET(client->decoded); /* Fix 200512 */ + VSTRING_SPACE(client->decoded, serverinlen); + if ((sasl_status = SASL_DECODE64(server_reply, serverinlen, + STR(client->decoded), + vstring_avail(client->decoded), + &enc_length)) != SASL_OK) { + vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FORM); + } + if (msg_verbose) + msg_info("%s: decoded challenge: %.*s", + myname, (int) enc_length, STR(client->decoded)); + sasl_status = sasl_client_step(client->sasl_conn, STR(client->decoded), + enc_length, NO_SASL_INTERACTION, + &clientout, &clientoutlen); + if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { + vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + + /* + * Send a client response. + */ + if (clientoutlen > 0) { + if (msg_verbose) + msg_info("%s: uncoded client response %.*s", + myname, (int) clientoutlen, clientout); + enc_length = ENCODE64_LENGTH(clientoutlen) + 1; + VSTRING_RESET(client_reply); /* Fix 200512 */ + VSTRING_SPACE(client_reply, enc_length); + if ((sasl_status = sasl_encode64(clientout, clientoutlen, + STR(client_reply), + vstring_avail(client_reply), + &enc_length_out)) != SASL_OK) + msg_panic("%s: sasl_encode64 botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(clientout); +#endif + } else { + /* XXX Can't happen. */ + vstring_strcpy(client_reply, ""); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_client_free - per-session cleanup */ + +void xsasl_cyrus_client_free(XSASL_CLIENT *xp) +{ + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + + if (client->username) + myfree(client->username); + if (client->password) + myfree(client->password); + if (client->sasl_conn) + sasl_dispose(&client->sasl_conn); + myfree((void *) client->callbacks); + vstring_free(client->decoded); + myfree((void *) client); +} + +#endif |