xsasl_cyrus_client.c [plain text]
#include <sys_defs.h>
#include <stdlib.h>
#include <string.h>
#include <msg.h>
#include <mymalloc.h>
#include <stringops.h>
#include <mail_params.h>
#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>
#define STR(s) vstring_str(s)
#if SASL_VERSION_MAJOR < 2
#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
#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
typedef struct {
XSASL_CLIENT xsasl;
VSTREAM *stream;
sasl_conn_t *sasl_conn;
VSTRING *decoded;
sasl_callback_t *callbacks;
char *username;
char *password;
} XSASL_CYRUS_CLIENT;
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 *);
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);
if (client->password == 0)
msg_panic("%s: no username looked up", myname);
*result = client->username;
if (len)
*len = strlen(client->username);
return (SASL_OK);
}
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);
if (!conn || !psecret || id != SASL_CB_PASS)
return (SASL_BADPARAM);
if (client->password == 0)
msg_panic("%s: no password looked up", myname);
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_CLIENT_IMPL *xsasl_cyrus_client_init(const char *unused_client_type,
const char *unused_path_info)
{
XSASL_CLIENT_IMPL *xp;
int sasl_status;
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;
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
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
}
if ((sasl_status = sasl_client_init(callbacks)) != SASL_OK) {
msg_warn("SASL library initialization error: %s",
xsasl_cyrus_strerror(sasl_status));
return (0);
}
xp = (XSASL_CLIENT_IMPL *) mymalloc(sizeof(*xp));
xp->create = xsasl_cyrus_client_create;
xp->done = xsasl_cyrus_client_done;
return (xp);
}
static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *impl)
{
myfree((char *) impl);
sasl_done();
}
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;
#define XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(x) \
do { \
if (client) { \
xsasl_cyrus_client_free(&client->xsasl); \
} else { \
if (custom_callbacks) \
myfree((char *) custom_callbacks); \
if (sasl_conn) \
sasl_dispose(&sasl_conn); \
} \
return (x); \
} while (0)
#define NULL_SECFLAGS 0
custom_callbacks = (sasl_callback_t *) mymalloc(sizeof(callbacks));
memcpy((char *) 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);
}
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);
}
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;
memset(&sec_props, 0, sizeof(sec_props));
sec_props.min_ssf = 0;
sec_props.max_ssf = 0;
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);
}
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
if (client->username)
myfree(client->username);
client->username = mystrdup(username);
if (client->password)
myfree(client->password);
client->password = mystrdup(password);
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);
}
#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);
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_AT_OFFSET(init_resp, enc_length_out);
#if SASL_VERSION_MAJOR < 2
free(clientout);
#endif
} else {
vstring_strcpy(init_resp, "");
}
return (XSASL_AUTH_OK);
}
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;
serverinlen = strlen(server_reply);
VSTRING_RESET(client->decoded);
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);
}
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);
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
free(clientout);
#endif
} else {
vstring_strcpy(client_reply, "");
}
return (XSASL_AUTH_OK);
}
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((char *) client->callbacks);
vstring_free(client->decoded);
myfree((char *) client);
}
#endif