view src/auth/mech-cram-md5.c @ 1873:ed5e808d934f HEAD

CRAM-MD5 mechanism by Joshua Goodall, plus some cleanups.
author Timo Sirainen <tss@iki.fi>
date Mon, 10 Nov 2003 22:36:02 +0200
parents
children 445fc3dfecad
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 "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)
{
	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", 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], *cdp;
	struct md5_context ctxo, ctxi;
	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;

#define CDGET(p, c) STMT_START { \
	(c)  = (*p++);           \
	(c) += (*p++ << 8);      \
	(c) += (*p++ << 16);     \
	(c) += (*p++ << 24);     \
} STMT_END

	cdp = context_digest;
	CDGET(cdp, ctxo.a);
	CDGET(cdp, ctxo.b);
	CDGET(cdp, ctxo.c);
	CDGET(cdp, ctxo.d);
	CDGET(cdp, ctxi.a);
	CDGET(cdp, ctxi.b);
	CDGET(cdp, ctxi.c);
	CDGET(cdp, ctxi.d);

	ctxo.lo = ctxi.lo = 64;
	ctxo.hi = ctxi.hi = 0;

	md5_update(&ctxi, auth->challenge, strlen(auth->challenge));
	md5_final(&ctxi, digest);
	md5_update(&ctxo, digest, 16);
	md5_final(&ctxo, 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",
			       auth->username);
		}
		return FALSE;
	}

	return TRUE;
}

static int parse_cram_response(struct cram_auth_request *auth,
			       const char *data, const char **error)
{
	char *digest;
	int failed;

	*error = NULL;
	failed = FALSE;

	digest = strchr(data, ' ');
	if (digest != NULL) {
		auth->username = p_strdup_until(auth->pool, data, digest);
		digest++;
		auth->response = p_strdup(auth->pool, digest);
	} else {
		*error = "missing digest";
		failed = TRUE;
	}

	return !failed;
}

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)) {
		if (verbose) {
			i_info("cram-md5(%s): authenticated",
			       auth->username == NULL ? "" : auth->username);
		}
		mech_auth_finish(request, NULL, 0, TRUE);
	} else {
		if (verbose) {
			i_info("cram-md5(%s): authentication failed",
			       auth->username == NULL ? "" : auth->username);
		}
		mech_auth_finish(request, NULL, 0, FALSE);
	}
}

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

	/* unused */
	(void)request;

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

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

		if (mech_is_valid_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",
		       auth->username == NULL ? "" : auth->username, error);
	}

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

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(struct auth_client_connection *conn,
		       unsigned int id, mech_callback_t *callback)
{
	struct auth_client_request_reply reply;
	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_continue = mech_cram_md5_auth_continue;
	auth->auth_request.auth_free = mech_cram_md5_auth_free;

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

	/* initialize reply */
	mech_init_auth_client_reply(&reply);
	reply.id = 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, conn);

	return &auth->auth_request;
}

struct mech_module mech_cram_md5 = {
	AUTH_MECH_CRAM_MD5,
	mech_cram_md5_auth_new
};