diff -ruN dovecot/src/auth/Makefile.am dovecot-crammd5/src/auth/Makefile.am --- dovecot/src/auth/Makefile.am Thu Oct 30 01:10:20 2003 +++ dovecot-crammd5/src/auth/Makefile.am Mon Nov 10 18:35:23 2003 @@ -28,6 +28,7 @@ mech-anonymous.c \ mech-cyrus-sasl2.c \ mech-plain.c \ + mech-cram-md5.c \ mech-digest-md5.c \ mycrypt.c \ passdb.c \ diff -ruN dovecot/src/auth/auth-client-interface.h dovecot-crammd5/src/auth/auth-client-interface.h --- dovecot/src/auth/auth-client-interface.h Fri Aug 22 12:42:13 2003 +++ dovecot-crammd5/src/auth/auth-client-interface.h Mon Nov 10 18:35:34 2003 @@ -12,6 +12,7 @@ AUTH_MECH_PLAIN = 0x01, AUTH_MECH_DIGEST_MD5 = 0x02, AUTH_MECH_ANONYMOUS = 0x04, + AUTH_MECH_CRAM_MD5 = 0x08, AUTH_MECH_COUNT }; diff -ruN dovecot/src/auth/auth-mech-desc.h dovecot-crammd5/src/auth/auth-mech-desc.h --- dovecot/src/auth/auth-mech-desc.h Thu May 8 13:24:57 2003 +++ dovecot-crammd5/src/auth/auth-mech-desc.h Mon Nov 10 18:35:34 2003 @@ -10,6 +10,7 @@ static struct auth_mech_desc auth_mech_desc[AUTH_MECH_COUNT] = { { AUTH_MECH_PLAIN, "PLAIN", TRUE, FALSE }, + { AUTH_MECH_CRAM_MD5, "CRAM-MD5", FALSE, TRUE }, { AUTH_MECH_DIGEST_MD5, "DIGEST-MD5", FALSE, TRUE }, { AUTH_MECH_ANONYMOUS, "ANONYMOUS", FALSE, TRUE } }; diff -ruN dovecot/src/auth/mech-cram-md5.c dovecot-crammd5/src/auth/mech-cram-md5.c --- dovecot/src/auth/mech-cram-md5.c Thu Jan 1 10:00:00 1970 +++ dovecot-crammd5/src/auth/mech-cram-md5.c Tue Nov 11 01:43:53 2003 @@ -0,0 +1,225 @@ +/* CRAM-MD5 SASL authentication, see RFC-2195 + Joshua Goodall + + Derived from mech-digest-md5.c by Timo Sirainen. + + Copyright (C) 2002,2003 Timo Sirainen / Joshua Goodall + */ + +#include "common.h" +#include "buffer.h" +#include "hex-binary.h" +#include "md5.h" +#include "randgen.h" +#include "str.h" +#include "mech.h" +#include "passdb.h" +#include "hostpid.h" +#include "safe-memset.h" + +#include +#include + +struct cram_auth_request { + struct auth_request auth_request; + + pool_t pool; + + /* requested: */ + char *challenge; + + /* received: */ + char *username; + char *response; + unsigned long maxbuf; +}; + +static string_t *get_cram_challenge(struct cram_auth_request *auth) +{ + string_t *str; + struct { + uint64_t v1,v2; + } challenge; + + random_fill(&challenge, sizeof(challenge)); + hostpid_init(); + str = t_str_new(256); + str_printfa(str, "<%llu%llu.%u@%s>", challenge.v1, challenge.v2, time(NULL), my_hostname); + auth->challenge = p_strdup(auth->pool, str_data(str)); + + return str; +} + +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) do { \ + (c) = (*p++); \ + (c) += (*p++ << 8); \ + (c) += (*p++ << 16); \ + (c) += (*p++ << 24); \ +} while (0) + + 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; + string_t *challenge; + + 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; + + /* 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; + challenge = get_cram_challenge(auth); + reply.data_size = str_len(challenge); + callback(&reply, str_data(challenge), conn); + + return &auth->auth_request; +} + +struct mech_module mech_cram_md5 = { + AUTH_MECH_CRAM_MD5, + mech_cram_md5_auth_new +}; diff -ruN dovecot/src/auth/mech.c dovecot-crammd5/src/auth/mech.c --- dovecot/src/auth/mech.c Mon Oct 20 14:15:16 2003 +++ dovecot-crammd5/src/auth/mech.c Mon Nov 10 18:35:34 2003 @@ -215,6 +215,7 @@ } extern struct mech_module mech_plain; +extern struct mech_module mech_cram_md5; extern struct mech_module mech_digest_md5; extern struct mech_module mech_anonymous; @@ -242,6 +243,8 @@ while (*mechanisms != NULL) { if (strcasecmp(*mechanisms, "PLAIN") == 0) mech_register_module(&mech_plain); + else if (strcasecmp(*mechanisms, "CRAM-MD5") == 0) + mech_register_module(&mech_cram_md5); else if (strcasecmp(*mechanisms, "DIGEST-MD5") == 0) mech_register_module(&mech_digest_md5); else if (strcasecmp(*mechanisms, "ANONYMOUS") == 0) { @@ -293,6 +296,7 @@ void mech_deinit(void) { mech_unregister_module(&mech_plain); + mech_unregister_module(&mech_cram_md5); mech_unregister_module(&mech_digest_md5); mech_unregister_module(&mech_anonymous); } diff -ruN dovecot/src/auth/passdb.c dovecot-crammd5/src/auth/passdb.c --- dovecot/src/auth/passdb.c Thu Oct 30 01:10:20 2003 +++ dovecot-crammd5/src/auth/passdb.c Mon Nov 10 18:35:34 2003 @@ -24,6 +24,8 @@ return "PLAIN"; case PASSDB_CREDENTIALS_CRYPT: return "CRYPT"; + case PASSDB_CREDENTIALS_CRAM_MD5: + return "CRAM-MD5"; case PASSDB_CREDENTIALS_DIGEST_MD5: return "DIGEST-MD5"; } @@ -132,6 +134,10 @@ if ((auth_mechanisms & AUTH_MECH_PLAIN) && passdb->verify_plain == NULL) i_fatal("Passdb %s doesn't support PLAIN method", name); + + if ((auth_mechanisms & AUTH_MECH_CRAM_MD5) && + passdb->lookup_credentials == NULL) + i_fatal("Passdb %s doesn't support CRAM-MD5 method", name); if ((auth_mechanisms & AUTH_MECH_DIGEST_MD5) && passdb->lookup_credentials == NULL) diff -ruN dovecot/src/auth/passdb.h dovecot-crammd5/src/auth/passdb.h --- dovecot/src/auth/passdb.h Thu Oct 30 01:10:20 2003 +++ dovecot-crammd5/src/auth/passdb.h Mon Nov 10 18:35:34 2003 @@ -11,6 +11,7 @@ PASSDB_CREDENTIALS_PLAINTEXT, PASSDB_CREDENTIALS_CRYPT, + PASSDB_CREDENTIALS_CRAM_MD5, PASSDB_CREDENTIALS_DIGEST_MD5 }; diff -ruN dovecot/src/auth/password-scheme.c dovecot-crammd5/src/auth/password-scheme.c --- dovecot/src/auth/password-scheme.c Fri Apr 4 09:42:54 2003 +++ dovecot-crammd5/src/auth/password-scheme.c Mon Nov 10 18:36:51 2003 @@ -7,6 +7,7 @@ #include "mycrypt.h" #include "randgen.h" #include "password-scheme.h" +#include "safe-memset.h" static const char *salt_chars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; @@ -86,7 +87,8 @@ const char *scheme) { const char *realm, *str; - unsigned char digest[16]; + unsigned char digest[16], ipad[64], opad[64], context_digest[32], *cdp; + struct md5_context ctxo, ctxi; char salt[9]; int i; @@ -108,6 +110,51 @@ if (strcasecmp(scheme, "PLAIN") == 0) return plaintext; + + if (strcasecmp(scheme, "CRAM-MD5") == 0) { + safe_memset(ipad, '\0', sizeof(ipad)); + safe_memset(opad, '\0', sizeof(opad)); + + /* Hash excessively long passwords */ + i = strlen(plaintext); + if (i > 64) { + md5_get_digest(plaintext, i, digest); + memcpy(ipad, digest, 16); + memcpy(opad, digest, 16); + } else { + memcpy(ipad, plaintext, i); + memcpy(opad, plaintext, i); + } + + /* ipad/opad operation */ + for (i = 0; i < 64; i++) { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + md5_init(&ctxi); + md5_init(&ctxo); + md5_update(&ctxi, ipad, 64); + md5_update(&ctxo, opad, 64); + + /* Make HMAC-MD5 hex digest */ +#define CDPUT(p,c) do { \ + *p++ = (c) & 0xff; \ + *p++ = (c)>>8 & 0xff; \ + *p++ = (c)>>16 & 0xff; \ + *p++ = (c)>>24 & 0xff; \ +} while (0) + cdp = context_digest; + CDPUT(cdp, ctxo.a); + CDPUT(cdp, ctxo.b); + CDPUT(cdp, ctxo.c); + CDPUT(cdp, ctxo.d); + CDPUT(cdp, ctxi.a); + CDPUT(cdp, ctxi.b); + CDPUT(cdp, ctxi.c); + CDPUT(cdp, ctxi.d); + + return binary_to_hex(context_digest, sizeof(context_digest)); + } if (strcasecmp(scheme, "DIGEST-MD5") == 0) { /* user:realm:passwd */