mech-cram-md5.c revision 73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0fae
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen/* Copyright (C) 2002,2003 Timo Sirainen / Joshua Goodall */
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen/* CRAM-MD5 SASL authentication, see RFC-2195
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen Joshua Goodall <joshua@roughtrade.net> */
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include "common.h"
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include "ioloop.h"
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include "buffer.h"
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include "hex-binary.h"
edf7428147facc11ddb43b9a874a99b96486d42dTimo Sirainen#include "hmac-md5.h"
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include "randgen.h"
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include "mech.h"
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include "passdb.h"
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include "hostpid.h"
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include <stdlib.h>
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen#include <time.h>
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainenstruct cram_auth_request {
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen struct auth_request auth_request;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen pool_t pool;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen /* requested: */
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen char *challenge;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen /* received: */
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen char *username;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen char *response;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen unsigned long maxbuf;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen};
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainenstatic const char *get_cram_challenge(void)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen{
6c07b8ddc5e894feead4d422075b079451721241Timo Sirainen unsigned char buf[17];
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen size_t i;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen hostpid_init();
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen random_fill(buf, sizeof(buf)-1);
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen for (i = 0; i < sizeof(buf)-1; i++)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen buf[i] = (buf[i] % 10) + '0';
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen buf[sizeof(buf)-1] = '\0';
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen return t_strdup_printf("<%s.%s@%s>", (const char *)buf,
6c07b8ddc5e894feead4d422075b079451721241Timo Sirainen dec2str(ioloop_time), my_hostname);
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen}
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainenstatic int verify_credentials(struct cram_auth_request *request,
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen const char *credentials)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen{
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
edf7428147facc11ddb43b9a874a99b96486d42dTimo Sirainen unsigned char digest[16], context_digest[32];
edf7428147facc11ddb43b9a874a99b96486d42dTimo Sirainen struct hmac_md5_context ctx;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen buffer_t *context_digest_buf;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen const char *response_hex;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen if (credentials == NULL)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen return FALSE;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen context_digest_buf =
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen buffer_create_data(pool_datastack_create(),
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen context_digest, sizeof(context_digest));
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
4b058f90f9e8a2c6b2eed275de4eb8cc5195a71dTimo Sirainen if (hex_to_binary(credentials, context_digest_buf) < 0)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen return FALSE;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
edf7428147facc11ddb43b9a874a99b96486d42dTimo Sirainen hmac_md5_set_cram_context(&ctx, context_digest);
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen hmac_md5_update(&ctx, request->challenge, strlen(request->challenge));
edf7428147facc11ddb43b9a874a99b96486d42dTimo Sirainen hmac_md5_final(&ctx, digest);
edf7428147facc11ddb43b9a874a99b96486d42dTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen response_hex = binary_to_hex(digest, 16);
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen if (memcmp(response_hex, request->response, 32) != 0) {
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen if (verbose) {
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen i_info("cram-md5(%s): password mismatch",
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen get_log_prefix(&request->auth_request));
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen }
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen return FALSE;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen }
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen return TRUE;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen}
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainenstatic int parse_cram_response(struct cram_auth_request *request,
244fcb971a4a38b476f733bfd5ed5d18b2c831f7Timo Sirainen const unsigned char *data, size_t size,
244fcb971a4a38b476f733bfd5ed5d18b2c831f7Timo Sirainen const char **error_r)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen{
3a734c9c3efd87864d1ad860826fd4e787457bedTimo Sirainen size_t i, space;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
244fcb971a4a38b476f733bfd5ed5d18b2c831f7Timo Sirainen *error_r = NULL;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
3a734c9c3efd87864d1ad860826fd4e787457bedTimo Sirainen /* <username> SPACE <response>. Username may contain spaces, so assume
3a734c9c3efd87864d1ad860826fd4e787457bedTimo Sirainen the rightmost space is the response separator. */
3a734c9c3efd87864d1ad860826fd4e787457bedTimo Sirainen for (i = space = 0; i < size; i++) {
244fcb971a4a38b476f733bfd5ed5d18b2c831f7Timo Sirainen if (data[i] == ' ')
3a734c9c3efd87864d1ad860826fd4e787457bedTimo Sirainen space = i;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen }
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
3a734c9c3efd87864d1ad860826fd4e787457bedTimo Sirainen if (space == 0) {
244fcb971a4a38b476f733bfd5ed5d18b2c831f7Timo Sirainen *error_r = "missing digest";
244fcb971a4a38b476f733bfd5ed5d18b2c831f7Timo Sirainen return FALSE;
244fcb971a4a38b476f733bfd5ed5d18b2c831f7Timo Sirainen }
244fcb971a4a38b476f733bfd5ed5d18b2c831f7Timo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen request->username = p_strndup(request->pool, data, space);
3a734c9c3efd87864d1ad860826fd4e787457bedTimo Sirainen space++;
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen request->response =
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen p_strndup(request->pool, data + space, size - space);
244fcb971a4a38b476f733bfd5ed5d18b2c831f7Timo Sirainen return TRUE;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen}
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainenstatic void credentials_callback(const char *result,
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen struct auth_request *auth_request)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen{
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen struct cram_auth_request *request =
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen (struct cram_auth_request *)auth_request;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen if (verify_credentials(request, result))
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen mech_auth_finish(auth_request, NULL, 0, TRUE);
3685c7c8ca227960360c720b8bd515801a9e5ad6Timo Sirainen else {
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen if (verbose) {
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen i_info("cram-md5(%s): authentication failed",
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen get_log_prefix(auth_request));
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen }
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen mech_auth_finish(auth_request, NULL, 0, FALSE);
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen }
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen}
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainenstatic void
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainenmech_cram_md5_auth_continue(struct auth_request *auth_request,
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen const unsigned char *data, size_t data_size,
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen mech_callback_t *callback)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen{
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen struct cram_auth_request *request =
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen (struct cram_auth_request *)auth_request;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen const char *error;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen if (parse_cram_response(request, data, data_size, &error)) {
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen auth_request->callback = callback;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen auth_request->user =
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen p_strdup(auth_request->pool, request->username);
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
9abfe876fa81576f130f3f82f622ae936c21a716Timo Sirainen if (mech_fix_username(auth_request->user, &error)) {
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen passdb->lookup_credentials(auth_request,
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen PASSDB_CREDENTIALS_CRAM_MD5,
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen credentials_callback);
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen return;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen }
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen }
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen if (error == NULL)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen error = "authentication failed";
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen if (verbose)
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen i_info("cram-md5(%s): %s", get_log_prefix(auth_request), error);
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen /* failed */
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen mech_auth_finish(auth_request, NULL, 0, FALSE);
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen}
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainenstatic void
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainenmech_cram_md5_auth_initial(struct auth_request *auth_request,
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen const unsigned char *data __attr_unused__,
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen size_t data_size __attr_unused__,
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen mech_callback_t *callback)
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen{
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen struct cram_auth_request *request =
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen (struct cram_auth_request *)auth_request;
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen request->challenge = p_strdup(request->pool, get_cram_challenge());
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen callback(auth_request, AUTH_CLIENT_RESULT_CONTINUE,
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen request->challenge, strlen(request->challenge));
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen}
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainenstatic void mech_cram_md5_auth_free(struct auth_request *request)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen{
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen pool_unref(request->pool);
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen}
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainenstatic struct auth_request *mech_cram_md5_auth_new(void)
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen{
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen struct cram_auth_request *request;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen pool_t pool;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen pool = pool_alloconly_create("cram_md5_auth_request", 2048);
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen request = p_new(pool, struct cram_auth_request, 1);
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen request->pool = pool;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen request->auth_request.refcount = 1;
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen request->auth_request.pool = pool;
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen return &request->auth_request;
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen}
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainenstruct mech_module mech_cram_md5 = {
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen "CRAM-MD5",
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen MEMBER(flags) MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE,
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen MEMBER(passdb_need_plain) FALSE,
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen MEMBER(passdb_need_credentials) TRUE,
d1414c09cf0d58ac983054e2f4e1a1f329272dcfTimo Sirainen
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen mech_cram_md5_auth_new,
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen mech_cram_md5_auth_initial,
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen mech_cram_md5_auth_continue,
73bfdbe28c2ce6d143eadf0bab8ccfbe4cab0faeTimo Sirainen mech_cram_md5_auth_free
94a78eb438622fa53abef1e1726714dacad4b61cTimo Sirainen};