view src/auth/mech-cram-md5.c @ 2510:0f660149c7ef HEAD

Added auth_username_translation setting.
author Timo Sirainen <tss@iki.fi>
date Sat, 28 Aug 2004 16:25:42 +0300
parents 4f84a146d93a
children a07fb16b9a24
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;

	*error_r = NULL;

	for (i = 0; i < size; i++) {
		if (data[i] == ' ')
			break;
	}

	if (i == size) {
		*error_r = "missing digest";
		return FALSE;
	}

	auth->username = p_strndup(auth->pool, data, i);
	i++;
	auth->response = p_strndup(auth->pool, data + i, size - i);
	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)) {
			passdb->lookup_credentials(&auth->auth_request,
						   PASSDB_CREDENTIALS_CRAM_MD5,
						   credentials_callback);
			return TRUE;
		}

		error = "invalid username";
	}

	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
};