mech-cram-md5.c revision e80203675151ef9d4f3f850cf02041042eb13096
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (C) 2002,2003 Timo Sirainen / Joshua Goodall */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen/* CRAM-MD5 SASL authentication, see RFC-2195
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen Joshua Goodall <joshua@roughtrade.net> */
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen#include "common.h"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include "ioloop.h"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include "buffer.h"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include "hex-binary.h"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include "hmac-md5.h"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include "randgen.h"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include "mech.h"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include "passdb.h"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include "hostpid.h"
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include <stdlib.h>
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen#include <time.h>
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstruct cram_auth_request {
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen struct auth_request auth_request;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen pool_t pool;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* requested: */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen char *challenge;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* received: */
44e5c39db5002ab15db142dda6c42e0422a17437Timo Sirainen char *username;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen char *response;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen unsigned long maxbuf;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen};
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstatic const char *get_cram_challenge(void)
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen{
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen unsigned char buf[17];
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen size_t i;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen hostpid_init();
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen random_fill(buf, sizeof(buf)-1);
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen for (i = 0; i < sizeof(buf)-1; i++)
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen buf[i] = (buf[i] % 10) + '0';
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen buf[sizeof(buf)-1] = '\0';
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen return t_strdup_printf("<%s.%s@%s>", (const char *)buf,
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen dec2str(ioloop_time), my_hostname);
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen}
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstatic int verify_credentials(struct cram_auth_request *request,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen const char *credentials)
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen{
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen unsigned char digest[16], context_digest[32];
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen struct hmac_md5_context ctx;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen buffer_t *context_digest_buf;
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen const char *response_hex;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen context_digest_buf =
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen buffer_create_data(pool_datastack_create(),
a0b89f3b1df99b3a32f44623f13ad1893118825bTimo Sirainen context_digest, sizeof(context_digest));
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
13a8c553f293349248b161ff851743498916e26eTimo Sirainen if (hex_to_binary(credentials, context_digest_buf) < 0) {
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen i_error("cram-md5(%s): passdb credentials are not in hex",
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen get_log_prefix(&request->auth_request));
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return FALSE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen hmac_md5_set_cram_context(&ctx, context_digest);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen hmac_md5_update(&ctx, request->challenge, strlen(request->challenge));
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen hmac_md5_final(&ctx, digest);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen response_hex = binary_to_hex(digest, 16);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen if (memcmp(response_hex, request->response, 32) != 0) {
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen if (verbose) {
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen i_info("cram-md5(%s): password mismatch",
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen get_log_prefix(&request->auth_request));
bfdf0fd7b6186f64cbdcbf1cb2bf9c42a9007b77Timo Sirainen }
b87761f9bbef949f31dae297e619ac3f5e9c2b2eTimo Sirainen return FALSE;
b87761f9bbef949f31dae297e619ac3f5e9c2b2eTimo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return TRUE;
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen}
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstatic int parse_cram_response(struct cram_auth_request *request,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen const unsigned char *data, size_t size,
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen const char **error_r)
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen{
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen size_t i, space;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen *error_r = NULL;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen /* <username> SPACE <response>. Username may contain spaces, so assume
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen the rightmost space is the response separator. */
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen for (i = space = 0; i < size; i++) {
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen if (data[i] == ' ')
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen space = i;
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen }
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen if (space == 0) {
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen *error_r = "missing digest";
4c9c55e15f35474f53f11659e796c63b1c34e884Timo Sirainen return FALSE;
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen }
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen
0ae010139a1bb3b29fbf117c5da1a6a6c6b7b5a0Timo Sirainen request->username = p_strndup(request->pool, data, space);
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen space++;
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen request->response =
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen p_strndup(request->pool, data + space, size - space);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen return TRUE;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen}
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainenstatic void credentials_callback(enum passdb_result result,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen const char *credentials,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct auth_request *auth_request)
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen{
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen struct cram_auth_request *request =
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen (struct cram_auth_request *)auth_request;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen switch (result) {
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen case PASSDB_RESULT_OK:
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (verify_credentials(request, credentials))
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen auth_request_success(auth_request, NULL, 0);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen else
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen auth_request_fail(auth_request);
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen break;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen case PASSDB_RESULT_INTERNAL_FAILURE:
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen auth_request_internal_failure(auth_request);
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen break;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen default:
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen auth_request_fail(auth_request);
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen break;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen }
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen}
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen
3656c91dcb8336814bebd4500e81c3dde25233e6Timo Sirainenstatic void
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainenmech_cram_md5_auth_continue(struct auth_request *auth_request,
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen const unsigned char *data, size_t data_size,
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen mech_callback_t *callback)
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen{
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen struct cram_auth_request *request =
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen (struct cram_auth_request *)auth_request;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen const char *error;
1ac19c5c2b66a12f5598792aad15114ee3eb62e2Timo Sirainen
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen if (parse_cram_response(request, data, data_size, &error)) {
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen auth_request->callback = callback;
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen auth_request->user =
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen p_strdup(auth_request->pool, request->username);
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen if (mech_fix_username(auth_request->user, &error)) {
dc9de21d4375faeedbe5b7e941502ac578650da9Timo Sirainen passdb->lookup_credentials(auth_request,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen PASSDB_CREDENTIALS_CRAM_MD5,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen credentials_callback);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen return;
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen }
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen if (error == NULL)
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen error = "authentication failed";
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen if (verbose)
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen i_info("cram-md5(%s): %s", get_log_prefix(auth_request), error);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen auth_request_fail(auth_request);
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen}
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenstatic void
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainenmech_cram_md5_auth_initial(struct auth_request *auth_request,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen const unsigned char *data __attr_unused__,
6b2738c39a868ff9291867138c55029fc40cf105Timo Sirainen size_t data_size __attr_unused__,
6c2c5f20760b06bfb4a40b0ee2ef5ab016bc41f0Timo Sirainen mech_callback_t *callback)
{
struct cram_auth_request *request =
(struct cram_auth_request *)auth_request;
request->challenge = p_strdup(request->pool, get_cram_challenge());
callback(auth_request, AUTH_CLIENT_RESULT_CONTINUE,
request->challenge, strlen(request->challenge));
}
static void mech_cram_md5_auth_free(struct auth_request *request)
{
pool_unref(request->pool);
}
static struct auth_request *mech_cram_md5_auth_new(void)
{
struct cram_auth_request *request;
pool_t pool;
pool = pool_alloconly_create("cram_md5_auth_request", 2048);
request = p_new(pool, struct cram_auth_request, 1);
request->pool = pool;
request->auth_request.refcount = 1;
request->auth_request.pool = pool;
return &request->auth_request;
}
struct mech_module mech_cram_md5 = {
"CRAM-MD5",
MEMBER(flags) MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE,
MEMBER(passdb_need_plain) FALSE,
MEMBER(passdb_need_credentials) TRUE,
mech_cram_md5_auth_new,
mech_cram_md5_auth_initial,
mech_cram_md5_auth_continue,
mech_cram_md5_auth_free
};