]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
CRAM-MD5 mechanism by Joshua Goodall, plus some cleanups.
authorTimo Sirainen <tss@iki.fi>
Mon, 10 Nov 2003 20:36:02 +0000 (22:36 +0200)
committerTimo Sirainen <tss@iki.fi>
Mon, 10 Nov 2003 20:36:02 +0000 (22:36 +0200)
--HG--
branch : HEAD

13 files changed:
src/auth/Makefile.am
src/auth/auth-client-interface.h
src/auth/auth-mech-desc.h
src/auth/md5crypt.h [deleted file]
src/auth/mech-cram-md5.c [new file with mode: 0644]
src/auth/mech-digest-md5.c
src/auth/mech.c
src/auth/passdb.c
src/auth/passdb.h
src/auth/password-scheme-cram-md5.c [new file with mode: 0644]
src/auth/password-scheme-md5crypt.c [moved from src/auth/md5crypt.c with 97% similarity]
src/auth/password-scheme.c
src/auth/password-scheme.h

index b4814d081c0fb7b9d2f069d577217d81e04e87c1..d47d1e1ccfaa6a63533e06b86c2b1a351af5152c 100644 (file)
@@ -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 \
index 3ccdcefcabfbc8982754b9977f3220cd16f47829..1309a759cec3f24206607c9bdc85871b18a29302 100644 (file)
@@ -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
 };
index e4a1122e1e519a2925eb48469bb720abcad3cb03..fc5e74de2b45c208c620e057924c3751d4e8367b 100644 (file)
@@ -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 (file)
index b277c9c..0000000
+++ /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 (file)
index 0000000..37ee6a4
--- /dev/null
@@ -0,0 +1,230 @@
+/* Copyright (C) 2002,2003 Timo Sirainen / Joshua Goodall */
+
+/* CRAM-MD5 SASL authentication, see RFC-2195
+   Joshua Goodall <joshua@roughtrade.net> */
+
+#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 <stdlib.h>
+#include <time.h>
+
+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
+};
index 849d7ac1929c029cd0f823764e93aad80a93ccac..cc22b0e64d1da18e17c5aa997e7fd7aab9ee2647 100644 (file)
@@ -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;
index 401eabcb05c6180748f3b09462071fda0a9483c8..621b5169ed3968608b537cc0073ee638545690a6 100644 (file)
@@ -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);
 }
index 6e6fe281d0bc60b967878cf7b08e1fb5ec311883..5ffae69b0521681ea08a39541c08905425b2c161 100644 (file)
@@ -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);
index 2650f52d4acf11cb832508e668ee8698a956ed82..097b5369bffbcd71dfbad0d220a2dd65da570f7f 100644 (file)
@@ -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 (file)
index 0000000..7f00bea
--- /dev/null
@@ -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));
+}
similarity index 97%
rename from src/auth/md5crypt.c
rename to src/auth/password-scheme-md5crypt.c
index ae7c1d0c36ac37320b514d4aa52993f9fc4c8938..e90ccb35f670c70cddd11423022791b0ed7d5b6b 100644 (file)
@@ -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];
index cd189fdeac2acbc0f82658e8ef37ba897643a566..9d746d559613880e3435dfddc049cdd030c35647 100644 (file)
@@ -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, '@');
index 959af7e9b8e948ec440a1ece8b2541144d93a57a..0cd7750bb21ac1430ceeb0e3890e42c86d4b387c 100644 (file)
@@ -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