diff options
Diffstat (limited to 'src/auth/mech-cram-md5.c')
-rw-r--r-- | src/auth/mech-cram-md5.c | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/src/auth/mech-cram-md5.c b/src/auth/mech-cram-md5.c new file mode 100644 index 0000000..e6ab888 --- /dev/null +++ b/src/auth/mech-cram-md5.c @@ -0,0 +1,193 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +/* CRAM-MD5 SASL authentication, see RFC-2195 + Joshua Goodall <joshua@roughtrade.net> */ + +#include "auth-common.h" +#include "ioloop.h" +#include "buffer.h" +#include "hex-binary.h" +#include "hmac-cram-md5.h" +#include "hmac.h" +#include "md5.h" +#include "randgen.h" +#include "mech.h" +#include "passdb.h" +#include "hostpid.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) +{ + unsigned char buf[17]; + size_t i; + + 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>", (const char *)buf, + dec2str(ioloop_time), my_hostname); +} + +static bool verify_credentials(struct cram_auth_request *request, + const unsigned char *credentials, size_t size) +{ + + unsigned char digest[MD5_RESULTLEN]; + struct hmac_context ctx; + const char *response_hex; + + if (size != CRAM_MD5_CONTEXTLEN) { + e_error(request->auth_request.mech_event, + "invalid credentials length"); + return FALSE; + } + + hmac_init(&ctx, NULL, 0, &hash_method_md5); + hmac_md5_set_cram_context(&ctx, credentials); + hmac_update(&ctx, request->challenge, strlen(request->challenge)); + hmac_final(&ctx, digest); + + response_hex = binary_to_hex(digest, sizeof(digest)); + + if (!mem_equals_timing_safe(response_hex, request->response, sizeof(digest)*2)) { + e_info(request->auth_request.mech_event, + AUTH_LOG_MSG_PASSWORD_MISMATCH); + return FALSE; + } + + return TRUE; +} + +static bool parse_cram_response(struct cram_auth_request *request, + const unsigned char *data, size_t size, + const char **error_r) +{ + size_t i, space; + + *error_r = NULL; + + /* <username> SPACE <response>. Username may contain spaces, so assume + the rightmost space is the response separator. */ + for (i = space = 0; i < size; i++) { + if (data[i] == '\0') { + *error_r = "NULs in response"; + return FALSE; + } + if (data[i] == ' ') + space = i; + } + + if (space == 0) { + *error_r = "missing digest"; + return FALSE; + } + + request->username = p_strndup(request->pool, data, space); + space++; + request->response = + p_strndup(request->pool, data + space, size - space); + return TRUE; +} + +static void credentials_callback(enum passdb_result result, + const unsigned char *credentials, size_t size, + struct auth_request *auth_request) +{ + struct cram_auth_request *request = + (struct cram_auth_request *)auth_request; + + switch (result) { + case PASSDB_RESULT_OK: + if (verify_credentials(request, credentials, size)) + auth_request_success(auth_request, "", 0); + else + auth_request_fail(auth_request); + break; + case PASSDB_RESULT_INTERNAL_FAILURE: + auth_request_internal_failure(auth_request); + break; + default: + auth_request_fail(auth_request); + break; + } +} + +static void +mech_cram_md5_auth_continue(struct auth_request *auth_request, + const unsigned char *data, size_t data_size) +{ + struct cram_auth_request *request = + (struct cram_auth_request *)auth_request; + const char *error; + + if (parse_cram_response(request, data, data_size, &error)) { + if (auth_request_set_username(auth_request, request->username, + &error)) { + auth_request_lookup_credentials(auth_request, + "CRAM-MD5", credentials_callback); + return; + } + } + + if (error == NULL) + error = "authentication failed"; + + e_info(auth_request->mech_event, "%s", error); + auth_request_fail(auth_request); +} + +static void +mech_cram_md5_auth_initial(struct auth_request *auth_request, + const unsigned char *data ATTR_UNUSED, + size_t data_size ATTR_UNUSED) +{ + struct cram_auth_request *request = + (struct cram_auth_request *)auth_request; + + request->challenge = p_strdup(request->pool, get_cram_challenge()); + auth_request_handler_reply_continue(auth_request, request->challenge, + strlen(request->challenge)); +} + +static struct auth_request *mech_cram_md5_auth_new(void) +{ + struct cram_auth_request *request; + pool_t pool; + + pool = pool_alloconly_create(MEMPOOL_GROWING"cram_md5_auth_request", 2048); + request = p_new(pool, struct cram_auth_request, 1); + request->pool = pool; + + request->auth_request.pool = pool; + return &request->auth_request; +} + +const struct mech_module mech_cram_md5 = { + "CRAM-MD5", + + .flags = MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE, + .passdb_need = MECH_PASSDB_NEED_VERIFY_RESPONSE, + + mech_cram_md5_auth_new, + mech_cram_md5_auth_initial, + mech_cram_md5_auth_continue, + mech_generic_auth_free +}; |