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