view src/auth/mech-cram-md5.c @ 2708:f1e9f3ec8135 HEAD

Buffer API change: we no longer support limited sized buffers where writes past limit wouldn't kill the process. They weren't used hardly anywhere, they could have hidden bugs and the code for handling them was too complex. This also changed base64 and hex-binary APIs.
author Timo Sirainen <tss@iki.fi>
date Fri, 08 Oct 2004 20:51:47 +0300
parents cf94f1dc1ec5
children 0f31778d3c34
line wrap: on
line source

/* 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 int verify_credentials(struct cram_auth_request *auth,
			      const char *credentials)
{
	
	unsigned char digest[16], context_digest[32];
        struct hmac_md5_context ctx;
	buffer_t *context_digest_buf;
	const char *response_hex;

	if (credentials == NULL)
		return FALSE;

	context_digest_buf =
		buffer_create_data(pool_datastack_create(),
				   context_digest, sizeof(context_digest));

	if (hex_to_binary(credentials, context_digest_buf) < 0)
		return FALSE;

	hmac_md5_set_cram_context(&ctx, context_digest);
	hmac_md5_update(&ctx, auth->challenge, strlen(auth->challenge));
	hmac_md5_final(&ctx, digest);

	response_hex = binary_to_hex(digest, 16);

	if (memcmp(response_hex, auth->response, 32) != 0) {
		if (verbose) {
			i_info("cram-md5(%s): password mismatch",
			       get_log_prefix(&auth->auth_request));
		}
		return FALSE;
	}

	return TRUE;
}

static int parse_cram_response(struct cram_auth_request *auth,
			       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;
	}

	auth->username = p_strndup(auth->pool, data, space);
	space++;
	auth->response = p_strndup(auth->pool, data + space, size - space);
	return TRUE;
}

static void credentials_callback(const char *result,
				 struct auth_request *request)
{
	struct cram_auth_request *auth =
		(struct cram_auth_request *) request;

	if (verify_credentials(auth, result))
		mech_auth_finish(request, NULL, 0, TRUE);
	else {
		if (verbose) {
			i_info("cram-md5(%s): authentication failed",
			       get_log_prefix(&auth->auth_request));
		}
		mech_auth_finish(request, NULL, 0, FALSE);
	}
}

static int
mech_cram_md5_auth_continue(struct auth_request *auth_request,
			    const unsigned char *data, size_t data_size,
			    mech_callback_t *callback)
{
	struct cram_auth_request *auth =
		(struct cram_auth_request *)auth_request;
	const char *error;

	if (parse_cram_response(auth, data, data_size, &error)) {
		auth_request->callback = callback;

		auth_request->user =
			p_strdup(auth_request->pool, auth->username);

		if (mech_fix_username(auth_request->user, &error)) {
			passdb->lookup_credentials(&auth->auth_request,
						   PASSDB_CREDENTIALS_CRAM_MD5,
						   credentials_callback);
			return TRUE;
		}
	}

	if (error == NULL)
		error = "authentication failed";

	if (verbose) {
		i_info("cram-md5(%s): %s",
                       get_log_prefix(&auth->auth_request), error);
	}

	/* failed */
	mech_auth_finish(auth_request, NULL, 0, FALSE);
	return FALSE;
}

static int
mech_cram_md5_auth_initial(struct auth_request *auth_request,
			   struct auth_client_request_new *request,
			   const unsigned char *data __attr_unused__,
			   mech_callback_t *callback)
{
	struct cram_auth_request *auth =
		(struct cram_auth_request *)auth_request;

	struct auth_client_request_reply reply;

	if (AUTH_CLIENT_REQUEST_HAVE_INITIAL_RESPONSE(request)) {
		/* No initial response in CRAM-MD5 */
		return FALSE;
	}

	auth->challenge = p_strdup(auth->pool, get_cram_challenge());

	/* initialize reply */
	mech_init_auth_client_reply(&reply);
	reply.id = request->id;
	reply.result = AUTH_CLIENT_RESULT_CONTINUE;

	/* send the initial challenge */
	reply.reply_idx = 0;
	reply.data_size = strlen(auth->challenge);
	callback(&reply, auth->challenge, auth_request->conn);
	return TRUE;
}

static void mech_cram_md5_auth_free(struct auth_request *auth_request)
{
	pool_unref(auth_request->pool);
}

static struct auth_request *mech_cram_md5_auth_new(void)
{
	struct cram_auth_request *auth;
	pool_t pool;

	pool = pool_alloconly_create("cram_md5_auth_request", 2048);
	auth = p_new(pool, struct cram_auth_request, 1);
	auth->pool = pool;

	auth->auth_request.refcount = 1;
	auth->auth_request.pool = pool;
	auth->auth_request.auth_initial = mech_cram_md5_auth_initial;
	auth->auth_request.auth_continue = mech_cram_md5_auth_continue;
	auth->auth_request.auth_free = mech_cram_md5_auth_free;

	return &auth->auth_request;
}

struct mech_module mech_cram_md5 = {
	"CRAM-MD5",

	MEMBER(plaintext) FALSE,
	MEMBER(advertise) TRUE,

	MEMBER(passdb_need_plain) FALSE,
	MEMBER(passdb_need_credentials) TRUE,

	mech_cram_md5_auth_new
};