mech-cram-md5.c revision 35136dd2baf8dc30e4e754294ed81ff48e8c1e64
/* Copyright (C) 2002,2003 Timo Sirainen / Joshua Goodall */
/* CRAM-MD5 SASL authentication, see RFC-2195
Joshua Goodall <joshua@roughtrade.net> */
#include "common.h"
#include "ioloop.h"
#include "buffer.h"
#include "hex-binary.h"
#include "hmac-md5.h"
#include "randgen.h"
#include "mech.h"
#include "passdb.h"
#include "hostpid.h"
#include <stdlib.h>
#include <time.h>
struct cram_auth_request {
struct auth_request auth_request;
pool_t pool;
/* requested: */
char *challenge;
/* received: */
char *username;
char *response;
unsigned long maxbuf;
};
static const char *get_cram_challenge(void)
{
unsigned char buf[17];
size_t i;
hostpid_init();
random_fill(buf, sizeof(buf)-1);
for (i = 0; i < sizeof(buf)-1; i++)
buf[i] = (buf[i] % 10) + '0';
buf[sizeof(buf)-1] = '\0';
return t_strdup_printf("<%s.%s@%s>", (const char *)buf,
dec2str(ioloop_time), my_hostname);
}
static bool verify_credentials(struct cram_auth_request *request,
const char *credentials)
{
unsigned char digest[16], context_digest[32];
struct hmac_md5_context ctx;
buffer_t *context_digest_buf;
const char *response_hex;
context_digest_buf =
buffer_create_data(pool_datastack_create(),
context_digest, sizeof(context_digest));
if (hex_to_binary(credentials, context_digest_buf) < 0) {
auth_request_log_error(&request->auth_request, "cram-md5",
"passdb credentials are not in hex");
return FALSE;
}
hmac_md5_set_cram_context(&ctx, context_digest);
hmac_md5_update(&ctx, request->challenge, strlen(request->challenge));
hmac_md5_final(&ctx, digest);
response_hex = binary_to_hex(digest, 16);
if (memcmp(response_hex, request->response, 32) != 0) {
auth_request_log_info(&request->auth_request, "cram-md5",
"password mismatch");
return FALSE;
}
return TRUE;
}
static bool parse_cram_response(struct cram_auth_request *request,
const unsigned char *data, size_t size,
const char **error_r)
{
size_t i, space;
*error_r = NULL;
/* <username> SPACE <response>. Username may contain spaces, so assume
the rightmost space is the response separator. */
for (i = space = 0; i < size; i++) {
if (data[i] == ' ')
space = i;
}
if (space == 0) {
*error_r = "missing digest";
return FALSE;
}
request->username = p_strndup(request->pool, data, space);
space++;
request->response =
p_strndup(request->pool, data + space, size - space);
return TRUE;
}
static void credentials_callback(enum passdb_result result,
const char *credentials,
struct auth_request *auth_request)
{
struct cram_auth_request *request =
(struct cram_auth_request *)auth_request;
switch (result) {
case PASSDB_RESULT_OK:
if (verify_credentials(request, credentials))
auth_request_success(auth_request, NULL, 0);
else
auth_request_fail(auth_request);
break;
case PASSDB_RESULT_INTERNAL_FAILURE:
auth_request_internal_failure(auth_request);
break;
default:
auth_request_fail(auth_request);
break;
}
}
static void
mech_cram_md5_auth_continue(struct auth_request *auth_request,
const unsigned char *data, size_t data_size)
{
struct cram_auth_request *request =
(struct cram_auth_request *)auth_request;
const char *error;
if (parse_cram_response(request, data, data_size, &error)) {
if (auth_request_set_username(auth_request, request->username,
&error)) {
auth_request_lookup_credentials(auth_request,
PASSDB_CREDENTIALS_CRAM_MD5,
credentials_callback);
return;
}
}
if (error == NULL)
error = "authentication failed";
auth_request_log_info(auth_request, "cram-md5", "%s", error);
auth_request_fail(auth_request);
}
static void
mech_cram_md5_auth_initial(struct auth_request *auth_request,
const unsigned char *data __attr_unused__,
size_t data_size __attr_unused__)
{
struct cram_auth_request *request =
(struct cram_auth_request *)auth_request;
request->challenge = p_strdup(request->pool, get_cram_challenge());
auth_request->callback(auth_request, AUTH_CLIENT_RESULT_CONTINUE,
request->challenge, strlen(request->challenge));
}
static struct auth_request *mech_cram_md5_auth_new(void)
{
struct cram_auth_request *request;
pool_t pool;
pool = pool_alloconly_create("cram_md5_auth_request", 2048);
request = p_new(pool, struct cram_auth_request, 1);
request->pool = pool;
request->auth_request.pool = pool;
return &request->auth_request;
}
struct mech_module mech_cram_md5 = {
"CRAM-MD5",
MEMBER(flags) MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE,
MEMBER(passdb_need_plain) FALSE,
MEMBER(passdb_need_credentials) TRUE,
MEMBER(passdb_need_set_credentials) FALSE,
mech_cram_md5_auth_new,
mech_cram_md5_auth_initial,
mech_cram_md5_auth_continue,
mech_generic_auth_free
};