From: Timo Sirainen Date: Mon, 10 Nov 2003 20:36:02 +0000 (+0200) Subject: CRAM-MD5 mechanism by Joshua Goodall, plus some cleanups. X-Git-Tag: 1.1.alpha1~4233 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=94a78eb438622fa53abef1e1726714dacad4b61c;p=thirdparty%2Fdovecot%2Fcore.git CRAM-MD5 mechanism by Joshua Goodall, plus some cleanups. --HG-- branch : HEAD --- diff --git a/src/auth/Makefile.am b/src/auth/Makefile.am index b4814d081c..d47d1e1ccf 100644 --- a/src/auth/Makefile.am +++ b/src/auth/Makefile.am @@ -23,11 +23,11 @@ dovecot_auth_SOURCES = \ db-pgsql.c \ db-passwd-file.c \ main.c \ - md5crypt.c \ mech.c \ mech-anonymous.c \ mech-cyrus-sasl2.c \ mech-plain.c \ + mech-cram-md5.c \ mech-digest-md5.c \ mycrypt.c \ passdb.c \ @@ -40,6 +40,8 @@ dovecot_auth_SOURCES = \ passdb-vpopmail.c \ passdb-pgsql.c \ password-scheme.c \ + password-scheme-md5crypt.c \ + password-scheme-cram-md5.c \ userdb.c \ userdb-ldap.c \ userdb-passwd.c \ @@ -59,7 +61,6 @@ noinst_HEADERS = \ db-pgsql.h \ db-passwd-file.h \ common.h \ - md5crypt.h \ mech.h \ mycrypt.h \ passdb.h \ diff --git a/src/auth/auth-client-interface.h b/src/auth/auth-client-interface.h index 3ccdcefcab..1309a759ce 100644 --- a/src/auth/auth-client-interface.h +++ b/src/auth/auth-client-interface.h @@ -12,6 +12,7 @@ enum auth_mech { AUTH_MECH_PLAIN = 0x01, AUTH_MECH_DIGEST_MD5 = 0x02, AUTH_MECH_ANONYMOUS = 0x04, + AUTH_MECH_CRAM_MD5 = 0x08, AUTH_MECH_COUNT }; diff --git a/src/auth/auth-mech-desc.h b/src/auth/auth-mech-desc.h index e4a1122e1e..fc5e74de2b 100644 --- a/src/auth/auth-mech-desc.h +++ b/src/auth/auth-mech-desc.h @@ -10,6 +10,7 @@ struct auth_mech_desc { 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 --git a/src/auth/md5crypt.h b/src/auth/md5crypt.h deleted file mode 100644 index b277c9c610..0000000000 --- a/src/auth/md5crypt.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef __MD5CRYPT_H -#define __MD5CRYPT_H - -const char *md5_crypt(const char *pw, const char *salt); - -#endif diff --git a/src/auth/mech-cram-md5.c b/src/auth/mech-cram-md5.c new file mode 100644 index 0000000000..37ee6a4517 --- /dev/null +++ b/src/auth/mech-cram-md5.c @@ -0,0 +1,230 @@ +/* Copyright (C) 2002,2003 Timo Sirainen / Joshua Goodall */ + +/* CRAM-MD5 SASL authentication, see RFC-2195 + Joshua Goodall */ + +#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 +#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 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 +}; diff --git a/src/auth/mech-digest-md5.c b/src/auth/mech-digest-md5.c index 849d7ac192..cc22b0e64d 100644 --- a/src/auth/mech-digest-md5.c +++ b/src/auth/mech-digest-md5.c @@ -464,7 +464,7 @@ static int auth_handle_response(struct digest_auth_request *auth, } static int parse_digest_response(struct digest_auth_request *auth, - const char *data, size_t size, + const unsigned char *data, size_t size, const char **error) { char *copy, *key, *value; @@ -569,8 +569,7 @@ mech_digest_md5_auth_continue(struct auth_request *auth_request, return TRUE; } - if (parse_digest_response(auth, (const char *) data, - request->data_size, &error)) { + if (parse_digest_response(auth, data, request->data_size, &error)) { auth_request->callback = callback; realm = auth->realm != NULL ? auth->realm : default_realm; diff --git a/src/auth/mech.c b/src/auth/mech.c index 401eabcb05..621b5169ed 100644 --- a/src/auth/mech.c +++ b/src/auth/mech.c @@ -215,6 +215,7 @@ int auth_request_unref(struct auth_request *request) } 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 @@ void mech_init(void) 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_init(void) 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 --git a/src/auth/passdb.c b/src/auth/passdb.c index 6e6fe281d0..5ffae69b05 100644 --- a/src/auth/passdb.c +++ b/src/auth/passdb.c @@ -24,6 +24,8 @@ passdb_credentials_to_str(enum passdb_credentials credentials) return "PLAIN"; case PASSDB_CREDENTIALS_CRYPT: return "CRYPT"; + case PASSDB_CREDENTIALS_CRAM_MD5: + return "CRAM-MD5"; case PASSDB_CREDENTIALS_DIGEST_MD5: return "DIGEST-MD5"; } @@ -133,6 +135,10 @@ void passdb_init(void) 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) i_fatal("Passdb %s doesn't support DIGEST-MD5 method", name); diff --git a/src/auth/passdb.h b/src/auth/passdb.h index 2650f52d4a..097b5369bf 100644 --- a/src/auth/passdb.h +++ b/src/auth/passdb.h @@ -11,6 +11,7 @@ enum passdb_credentials { PASSDB_CREDENTIALS_PLAINTEXT, PASSDB_CREDENTIALS_CRYPT, + PASSDB_CREDENTIALS_CRAM_MD5, PASSDB_CREDENTIALS_DIGEST_MD5 }; diff --git a/src/auth/password-scheme-cram-md5.c b/src/auth/password-scheme-cram-md5.c new file mode 100644 index 0000000000..7f00bea455 --- /dev/null +++ b/src/auth/password-scheme-cram-md5.c @@ -0,0 +1,58 @@ +/* Copyright (C) 2003 Timo Sirainen */ + +#include "lib.h" +#include "md5.h" +#include "hex-binary.h" +#include "password-scheme.h" + +const char *password_generate_cram_md5(const char *plaintext) +{ + unsigned char digest[16], ipad[64], opad[64], context_digest[32], *cdp; + struct md5_context ctxo, ctxi; + size_t len; + int i; + + memset(ipad, 0, sizeof(ipad)); + memset(opad, 0, sizeof(opad)); + + /* Hash excessively long passwords */ + len = strlen(plaintext); + if (len > 64) { + md5_get_digest(plaintext, len, digest); + memcpy(ipad, digest, 16); + memcpy(opad, digest, 16); + } else { + memcpy(ipad, plaintext, len); + memcpy(opad, plaintext, len); + } + + /* 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) STMT_START { \ + *(p)++ = (c) & 0xff; \ + *(p)++ = (c) >> 8 & 0xff; \ + *(p)++ = (c) >> 16 & 0xff; \ + *(p)++ = (c) >> 24 & 0xff; \ +} STMT_END + 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)); +} diff --git a/src/auth/md5crypt.c b/src/auth/password-scheme-md5crypt.c similarity index 97% rename from src/auth/md5crypt.c rename to src/auth/password-scheme-md5crypt.c index ae7c1d0c36..e90ccb35f6 100644 --- a/src/auth/md5crypt.c +++ b/src/auth/password-scheme-md5crypt.c @@ -19,7 +19,7 @@ #include "safe-memset.h" #include "str.h" #include "md5.h" -#include "md5crypt.h" +#include "password-scheme.h" static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; @@ -46,8 +46,7 @@ to64(string_t *str, unsigned long v, int n) * Use MD5 for what it is best at... */ -const char * -md5_crypt(const char *pw, const char *salt) +const char *password_generate_md5_crypt(const char *pw, const char *salt) { const char *sp,*ep; unsigned char final[16]; diff --git a/src/auth/password-scheme.c b/src/auth/password-scheme.c index cd189fdeac..9d746d5596 100644 --- a/src/auth/password-scheme.c +++ b/src/auth/password-scheme.c @@ -3,7 +3,6 @@ #include "lib.h" #include "hex-binary.h" #include "md5.h" -#include "md5crypt.h" #include "mycrypt.h" #include "randgen.h" #include "password-scheme.h" @@ -23,8 +22,10 @@ int password_verify(const char *plaintext, const char *password, if (strcasecmp(scheme, "CRYPT") == 0) return strcmp(mycrypt(plaintext, password), password) == 0; - if (strcasecmp(scheme, "MD5") == 0) - return strcmp(md5_crypt(plaintext, password), password) == 0; + if (strcasecmp(scheme, "MD5") == 0) { + str = password_generate_md5_crypt(plaintext, password); + return strcmp(str, password) == 0; + } if (strcasecmp(scheme, "PLAIN") == 0) return strcmp(password, plaintext) == 0; @@ -103,12 +104,15 @@ const char *password_generate(const char *plaintext, const char *user, for (i = 0; i < 8; i++) salt[i] = salt_chars[salt[i] % (sizeof(salt_chars)-1)]; salt[8] = '\0'; - return t_strdup(md5_crypt(plaintext, salt)); + return password_generate_md5_crypt(plaintext, salt); } if (strcasecmp(scheme, "PLAIN") == 0) return plaintext; + if (strcasecmp(scheme, "CRAM-MD5") == 0) + return password_generate_cram_md5(plaintext); + if (strcasecmp(scheme, "DIGEST-MD5") == 0) { /* user:realm:passwd */ realm = strchr(user, '@'); diff --git a/src/auth/password-scheme.h b/src/auth/password-scheme.h index 959af7e9b8..0cd7750bb2 100644 --- a/src/auth/password-scheme.h +++ b/src/auth/password-scheme.h @@ -12,4 +12,8 @@ const char *password_get_scheme(const char **password); const char *password_generate(const char *plaintext, const char *user, const char *scheme); +/* INTERNAL: */ +const char *password_generate_md5_crypt(const char *pw, const char *salt); +const char *password_generate_cram_md5(const char *pw); + #endif