Skip to content

Commit

Permalink
auth: Add xoauth2 and oauthbearer mechanisms
Browse files Browse the repository at this point in the history
  • Loading branch information
cmouse authored and GitLab committed Feb 17, 2017
1 parent cdf00f5 commit 5bad922
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/auth/Makefile.am
Expand Up @@ -106,6 +106,7 @@ auth_SOURCES = \
mech-apop.c \
mech-winbind.c \
mech-dovecot-token.c \
mech-oauth2.c \
passdb.c \
passdb-blocking.c \
passdb-bsdauth.c \
Expand Down
279 changes: 279 additions & 0 deletions src/auth/mech-oauth2.c
@@ -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
};


6 changes: 6 additions & 0 deletions src/auth/mech.c
Expand Up @@ -82,6 +82,8 @@ extern const struct mech_module mech_gssapi_spnego;
#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)
Expand Down Expand Up @@ -211,6 +213,8 @@ void mech_init(const struct auth_settings *set)
#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)
Expand Down Expand Up @@ -238,4 +242,6 @@ void mech_deinit(const struct auth_settings *set)
#ifdef BUILTIN_GSSAPI
mech_unregister_module(&mech_gssapi);
#endif
mech_unregister_module(&mech_oauthbearer);
mech_unregister_module(&mech_xoauth2);
}

0 comments on commit 5bad922

Please sign in to comment.