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 \
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 \
db-pgsql.h \
db-passwd-file.h \
common.h \
- md5crypt.h \
mech.h \
mycrypt.h \
passdb.h \
AUTH_MECH_PLAIN = 0x01,
AUTH_MECH_DIGEST_MD5 = 0x02,
AUTH_MECH_ANONYMOUS = 0x04,
+ AUTH_MECH_CRAM_MD5 = 0x08,
AUTH_MECH_COUNT
};
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 }
};
+++ /dev/null
-#ifndef __MD5CRYPT_H
-#define __MD5CRYPT_H
-
-const char *md5_crypt(const char *pw, const char *salt);
-
-#endif
--- /dev/null
+/* 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
+};
}
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;
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;
}
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;
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) {
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);
}
return "PLAIN";
case PASSDB_CREDENTIALS_CRYPT:
return "CRYPT";
+ case PASSDB_CREDENTIALS_CRAM_MD5:
+ return "CRAM-MD5";
case PASSDB_CREDENTIALS_DIGEST_MD5:
return "DIGEST-MD5";
}
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);
PASSDB_CREDENTIALS_PLAINTEXT,
PASSDB_CREDENTIALS_CRYPT,
+ PASSDB_CREDENTIALS_CRAM_MD5,
PASSDB_CREDENTIALS_DIGEST_MD5
};
--- /dev/null
+/* 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));
+}
#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";
* 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];
#include "lib.h"
#include "hex-binary.h"
#include "md5.h"
-#include "md5crypt.h"
#include "mycrypt.h"
#include "randgen.h"
#include "password-scheme.h"
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;
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, '@');
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