Mercurial > dovecot > core-2.2
changeset 21578:8b9d500c4917
auth: Add xoauth2 and oauthbearer mechanisms
author | Aki Tuomi <aki.tuomi@dovecot.fi> |
---|---|
date | Sat, 04 Feb 2017 23:56:04 +0200 |
parents | 5c390ae4f640 |
children | 0006d9824c80 |
files | src/auth/Makefile.am src/auth/mech-oauth2.c src/auth/mech.c |
diffstat | 3 files changed, 286 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/src/auth/Makefile.am Mon Feb 06 12:56:27 2017 +0200 +++ b/src/auth/Makefile.am Sat Feb 04 23:56:04 2017 +0200 @@ -106,6 +106,7 @@ mech-apop.c \ mech-winbind.c \ mech-dovecot-token.c \ + mech-oauth2.c \ passdb.c \ passdb-blocking.c \ passdb-bsdauth.c \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/mech-oauth2.c Sat Feb 04 23:56:04 2017 +0200 @@ -0,0 +1,279 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" +#include "safe-memset.h" +#include "str.h" +#include "mech.h" +#include "passdb.h" +#include "oauth2.h" +#include <ctype.h> + +struct oauth2_auth_request { + struct auth_request auth; + bool failed; +}; + +/* RFC5801 based unescaping */ +static bool oauth2_unescape_username(const char *in, const char **username_r) +{ + string_t *out; + out = t_str_new(64); + for (; *in != '\0'; in++) { + if (in[0] == ',') + return FALSE; + if (in[0] == '=') { + if (in[1] == '2' && in[2] == 'C') + str_append_c(out, ','); + else if (in[1] == '3' && in[2] == 'D') + str_append_c(out, '='); + else + return FALSE; + in += 2; + } else { + str_append_c(out, *in); + } + } + *username_r = str_c(out); + return TRUE; +} + +static void oauth2_verify_callback(enum passdb_result result, + const char *error, + struct auth_request *request) +{ + struct oauth2_auth_request *oauth2_req = + (struct oauth2_auth_request*)request; + + i_assert(result == PASSDB_RESULT_OK || error != NULL); + switch (result) { + case PASSDB_RESULT_OK: + auth_request_success(request, "", 0); + break; + case PASSDB_RESULT_INTERNAL_FAILURE: + auth_request_internal_failure(request); + break; + default: + /* we could get new token after this */ + if (request->mech_password) + request->mech_password = NULL; + auth_request_handler_reply_continue(request, error, strlen(error)); + oauth2_req->failed = TRUE; + break; + } +} + +static void +xoauth2_verify_callback(enum passdb_result result, struct auth_request *request) +{ + const char *error = + "{\"status\":\"401\",\"schemes\":\"bearer\",\"scope\":\"mail\"}"; + oauth2_verify_callback(result, error, request); +} + +static void +oauthbearer_verify_callback(enum passdb_result result, struct auth_request *request) +{ + const char *error = + "{\"status\":\"invalid_token\"}"; + oauth2_verify_callback(result, error, request); +} + +/* Input syntax: + user=Username^Aauth=Bearer token^A^A +*/ +static void +mech_xoauth2_auth_continue(struct auth_request *request, + const unsigned char *data, + size_t data_size) +{ + struct oauth2_auth_request *oauth2_req = + (struct oauth2_auth_request*)request; + + /* Specification says that client is sent "invalid token" challenge + which the client is supposed to ack with empty response */ + if (oauth2_req->failed) { + auth_request_fail(request); + return; + } + + /* split the data from ^A */ + bool user_given = FALSE; + const char *error; + const char *token = NULL; + const char *const *ptr; + const char *const *fields = + t_strsplit(t_strndup(data, data_size), "\x01"); + for(ptr = fields; *ptr != NULL; ptr++) { + if (strncmp(*ptr,"user=", 5) == 0) { + /* xoauth2 does not require unescaping because the data + format does not contain anything to escape */ + const char *username = (*ptr)+5; + if (username == NULL) { + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "Invalid username"); + auth_request_fail(request); + return; + } + if (!auth_request_set_username(request, username, &error)) { + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "%s", error); + auth_request_fail(request); + return; + } + user_given = TRUE; + } else if (strncmp(*ptr,"auth=", 5) == 0) { + const char *value = (*ptr)+5; + if (strncasecmp(value, "bearer ", 7) == 0 && + oauth2_valid_token(value+7)) { + token = value+7; + } else { + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "Invalid continued data"); + auth_request_fail(request); + return; + } + } + /* do not fail on unexpected fields */ + } + + if (user_given && token != NULL) + auth_request_verify_plain(request, token, + xoauth2_verify_callback); + else { + auth_request_log_info(request, AUTH_SUBSYS_MECH, "invalid input"); + auth_request_fail(request); + } +} + +/* Input syntax for data: + gs2flag,a=username,^Afield=...^Afield=...^Aauth=Bearer token^A^A +*/ +static void +mech_oauthbearer_auth_continue(struct auth_request *request, + const unsigned char *data, + size_t data_size) +{ + struct oauth2_auth_request *oauth2_req = + (struct oauth2_auth_request*)request; + + if (oauth2_req->failed) { + auth_request_fail(request); + return; + } + + bool user_given = FALSE; + const char *error; + const char *username; + const char *const *ptr; + /* split the data from ^A */ + const char **fields = + t_strsplit(t_strndup(data, data_size), "\x01"); + const char *token = NULL; + /* ensure initial field is OK */ + if (*fields == NULL || *(fields[0]) == '\0') { + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "Invalid continued data"); + auth_request_fail(request); + return; + } + + /* the first field is specified by RFC5801 as gs2-header */ + for(ptr = t_strsplit(fields[0], ","); *ptr != NULL; ptr++) { + switch(*ptr[0]) { + case 'f': + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "Client requested non-standard mechanism"); + auth_request_fail(request); + return; + case 'p': + /* channel binding is not supported */ + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "Client requested and used channel-binding"); + auth_request_fail(request); + return; + case 'n': + case 'y': + /* we don't need to use channel-binding */ + continue; + case 'a': /* authzid */ + if ((*ptr)[1] != '=' || + !oauth2_unescape_username((*ptr)+2, &username)) { + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "Invalid input"); + auth_request_fail(request); + return; + } else if (!auth_request_set_username(request, username, &error)) { + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "%s", error); + + } + default: + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "Invalid gs2-header in request"); + auth_request_fail(request); + return; + } + } + + for(ptr = fields; *ptr != NULL; ptr++) { + if (strncmp(*ptr,"auth=", 5) == 0) { + const char *value = (*ptr)+5; + if (strncasecmp(value, "bearer ", 7) == 0 && + oauth2_valid_token(value+7)) { + token = value+7; + } else { + auth_request_log_info(request, AUTH_SUBSYS_MECH, + "Invalid continued data"); + auth_request_fail(request); + return; + } + } + /* do not fail on unexpected fields */ + } + if (user_given && token != NULL) + auth_request_verify_plain(request, token, + oauthbearer_verify_callback); + else { + auth_request_log_info(request, AUTH_SUBSYS_MECH, "invalid input"); + auth_request_fail(request); + } +} + +static struct auth_request *mech_oauth2_auth_new(void) +{ + struct oauth2_auth_request *request; + pool_t pool; + + pool = pool_alloconly_create(MEMPOOL_GROWING"oauth2_auth_request", 2048); + request = p_new(pool, struct oauth2_auth_request, 1); + request->auth.pool = pool; + return &request->auth; +} + +const struct mech_module mech_oauthbearer = { + "OAUTHBEARER", + + /* while this does not transfer plaintext password, + the token is still considered as password */ + .flags = MECH_SEC_PLAINTEXT, + .passdb_need = 0, + + mech_oauth2_auth_new, + mech_generic_auth_initial, + mech_oauthbearer_auth_continue, + mech_generic_auth_free +}; + +const struct mech_module mech_xoauth2 = { + "XOAUTH2", + + .flags = MECH_SEC_PLAINTEXT, + .passdb_need = 0, + + mech_oauth2_auth_new, + mech_generic_auth_initial, + mech_xoauth2_auth_continue, + mech_generic_auth_free +}; + +
--- a/src/auth/mech.c Mon Feb 06 12:56:27 2017 +0200 +++ b/src/auth/mech.c Sat Feb 04 23:56:04 2017 +0200 @@ -82,6 +82,8 @@ #endif extern const struct mech_module mech_winbind_ntlm; extern const struct mech_module mech_winbind_spnego; +extern const struct mech_module mech_oauthbearer; +extern const struct mech_module mech_xoauth2; static void mech_register_add(struct mechanisms_register *reg, const struct mech_module *mech) @@ -211,6 +213,8 @@ #ifdef BUILTIN_GSSAPI mech_register_module(&mech_gssapi); #endif + mech_register_module(&mech_oauthbearer); + mech_register_module(&mech_xoauth2); } void mech_deinit(const struct auth_settings *set) @@ -238,4 +242,6 @@ #ifdef BUILTIN_GSSAPI mech_unregister_module(&mech_gssapi); #endif + mech_unregister_module(&mech_oauthbearer); + mech_unregister_module(&mech_xoauth2); }