xsasl_dovecot_server.c [plain text]
#include <sys_defs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <msg.h>
#include <mymalloc.h>
#include <connect.h>
#include <split_at.h>
#include <stringops.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <mail_params.h>
#include <xsasl.h>
#include <xsasl_dovecot.h>
#ifdef USE_SASL_AUTH
#define AUTH_PROTOCOL_MAJOR_VERSION 1
#define AUTH_PROTOCOL_MINOR_VERSION 0
#define AUTH_TIMEOUT 10
typedef struct {
XSASL_SERVER_IMPL xsasl;
VSTREAM *sasl_stream;
char *socket_path;
char *mechanism_list;
unsigned int request_id_counter;
} XSASL_DOVECOT_SERVER_IMPL;
typedef struct {
XSASL_SERVER xsasl;
XSASL_DOVECOT_SERVER_IMPL *impl;
unsigned int last_request_id;
char *service;
char *username;
VSTRING *sasl_line;
} XSASL_DOVECOT_SERVER;
static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *);
static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *,
VSTREAM *,
const char *,
const char *,
const char *);
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 *);
static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp)
{
const char *myname = "xsasl_dovecot_server_connect";
VSTRING *line_str, *mechanisms_str;
VSTREAM *sasl_stream;
char *line, *cmd, *mech_name;
unsigned int major_version, minor_version;
int fd, success;
if (msg_verbose)
msg_info("%s: Connecting", myname);
if ((fd = unix_connect(xp->socket_path, BLOCKING, AUTH_TIMEOUT)) < 0) {
msg_warn("SASL: Connect to %s failed: %m", xp->socket_path);
return (-1);
}
sasl_stream = vstream_fdopen(fd, O_RDWR);
vstream_control(sasl_stream,
VSTREAM_CTL_PATH, xp->socket_path,
VSTREAM_CTL_TIMEOUT, AUTH_TIMEOUT,
VSTREAM_CTL_END);
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;
line_str = vstring_alloc(256);
mechanisms_str = vstring_alloc(128);
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) {
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;
line = split_at(line, '\t');
if (VSTRING_LEN(mechanisms_str) > 0)
VSTRING_ADDCH(mechanisms_str, ' ');
vstring_strcat(mechanisms_str, mech_name);
} else if (strcmp(cmd, "DONE") == 0) {
success = 1;
break;
} else {
}
}
vstring_free(line_str);
if (!success) {
vstring_free(mechanisms_str);
(void) vstream_fclose(sasl_stream);
return (-1);
}
xp->sasl_stream = sasl_stream;
xp->mechanism_list =
translit(vstring_export(mechanisms_str), "\t", " ");
if (msg_verbose)
msg_info("%s: Mechanisms: %s", myname, xp->mechanism_list);
return (0);
}
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) {
myfree(xp->mechanism_list);
xp->mechanism_list = 0;
}
}
XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *unused_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);
}
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((char *) impl);
}
static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *impl,
VSTREAM *unused_stream,
const char *service,
const char *realm,
const char *unused_sec_props)
{
const char *myname = "xsasl_dovecot_server_create";
XSASL_DOVECOT_SERVER *server;
if (msg_verbose)
msg_info("%s: SASL service=%s, realm=%s",
myname, service, realm ? realm : "(null)");
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(service);
server->last_request_id = 0;
return (&server->xsasl);
}
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);
}
return (server->impl->mechanism_list);
}
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);
myfree(server->service);
myfree((char *) server);
}
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) {
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;
}
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);
}
}
}
}
static int xsasl_dovecot_handle_reply(XSASL_DOVECOT_SERVER *server,
VSTRING *reply)
{
const char *myname = "xsasl_dovecot_handle_reply";
char *line, *cmd;
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) {
xsasl_dovecot_parse_reply_args(server, line, reply, 1);
return XSASL_AUTH_DONE;
}
} else if (strcmp(cmd, "CONT") == 0) {
if (xsasl_dovecot_parse_reply(server, &line) == 0) {
vstring_strcpy(reply, line);
return XSASL_AUTH_MORE;
}
} else if (strcmp(cmd, "FAIL") == 0) {
if (xsasl_dovecot_parse_reply(server, &line) == 0) {
xsasl_dovecot_parse_reply_args(server, line, reply, 0);
return XSASL_AUTH_FAIL;
}
} else {
}
}
vstring_strcpy(reply, "Connection lost to authentication server");
return XSASL_AUTH_FAIL;
}
static int is_valid_base64(const char *data)
{
for (; *data != '\0'; data++) {
if (!((*data >= '0' && *data <= '9') ||
(*data >= 'a' && *data <= 'z') ||
(*data >= 'A' && *data <= 'Z') ||
*data == '+' || *data == '/' || *data == '='))
return 0;
}
return 1;
}
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;
#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 (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 (0);
}
server->last_request_id = ++server->impl->request_id_counter;
vstream_fprintf(server->impl->sasl_stream,
"AUTH\t%u\t%s\tservice=%s",
server->last_request_id, sasl_method,
server->service);
if (init_response) {
vstream_fprintf(server->impl->sasl_stream,
"\tresp=%s", init_response);
}
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_FAIL;
}
xsasl_dovecot_server_disconnect(server->impl);
}
return xsasl_dovecot_handle_reply(server, reply);
}
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;
}
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_FAIL;
}
return xsasl_dovecot_handle_reply(server, reply);
}
static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *xp)
{
XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp;
return (server->username);
}
#endif