diff options
Diffstat (limited to 'src/lib-sasl/mech-oauthbearer.c')
-rw-r--r-- | src/lib-sasl/mech-oauthbearer.c | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/src/lib-sasl/mech-oauthbearer.c b/src/lib-sasl/mech-oauthbearer.c new file mode 100644 index 0000000..62ec6bc --- /dev/null +++ b/src/lib-sasl/mech-oauthbearer.c @@ -0,0 +1,201 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "net.h" +#include "json-parser.h" +#include "istream.h" +#include "dsasl-client-private.h" + +struct oauthbearer_dsasl_client { + struct dsasl_client client; + const char *host; + const char *status; + in_port_t port; + bool output_sent; +}; + +static int +mech_oauthbearer_input(struct dsasl_client *_client, + const unsigned char *input, size_t input_len, + const char **error_r) +{ + struct oauthbearer_dsasl_client *client = + (struct oauthbearer_dsasl_client *)_client; + + if (!client->output_sent) { + if (input_len > 0) { + *error_r = "Server sent non-empty initial response"; + return -1; + } + } else { + client->status = ""; + /* if response is empty, authentication has *SUCCEEDED* */ + if (input_len == 0) + return 0; + + /* authentication has failed, try parse status. + we are only interested in extracting status if possible + so we don't really need to much error handling. */ + struct istream *is = i_stream_create_from_data(input, input_len); + const char *status = NULL, *value; + const char *error = NULL; + enum json_type jtype; + bool found_status = FALSE; + struct json_parser *parser = json_parser_init(is); + while (json_parse_next(parser, &jtype, &value)>0) { + if (found_status && status == NULL) { + if (jtype == JSON_TYPE_STRING || + jtype == JSON_TYPE_NUMBER) + status = t_strdup(value); + break; + } else if (jtype == JSON_TYPE_OBJECT_KEY && + strcmp(value, "status") == 0) { + found_status = TRUE; + } else json_parse_skip_next(parser); + } + + /* deinitialize json parser */ + int ret = json_parser_deinit(&parser, &error); + i_stream_unref(&is); + + if (status != NULL) + client->status = p_strdup(_client->pool, status); + else { + ret = -1; + if (error == NULL) + error = "Status value missing"; + } + if (ret < 0) + *error_r = t_strdup_printf("Error parsing JSON reply: %s", + error); + else + *error_r = t_strdup_printf("Failed to authenticate: %s", + client->status); + return -1; + } + return 0; +} + +static int +mech_oauthbearer_output(struct dsasl_client *_client, + const unsigned char **output_r, size_t *output_len_r, + const char **error_r) +{ + struct oauthbearer_dsasl_client *client = + (struct oauthbearer_dsasl_client *)_client; + string_t *str; + + if (_client->set.authid == NULL) { + *error_r = "authid not set"; + return -1; + } + if (_client->password == NULL) { + *error_r = "password not set"; + return -1; + } + + str = str_new(_client->pool, 64); + + str_printfa(str, "n,a=%s,\x01", _client->set.authid); + if (client->host != NULL && *client->host != '\0') + str_printfa(str, "host=%s\x01", client->host); + if (client->port > 0) + str_printfa(str, "port=%u\x01", client->port); + str_printfa(str, "auth=Bearer %s\x01", _client->password); + str_append_c(str, '\x01'); + + *output_r = str_data(str); + *output_len_r = str_len(str); + client->output_sent = TRUE; + return 0; +} + +static int +mech_xoauth2_output(struct dsasl_client *_client, + const unsigned char **output_r, size_t *output_len_r, + const char **error_r) +{ + struct oauthbearer_dsasl_client *client = + (struct oauthbearer_dsasl_client *)_client; + string_t *str; + + if (_client->set.authid == NULL) { + *error_r = "authid not set"; + return -1; + } + if (_client->password == NULL) { + *error_r = "password not set"; + return -1; + } + + str = str_new(_client->pool, 64); + + str_printfa(str, "user=%s\x01", _client->set.authid); + str_printfa(str, "auth=Bearer %s\x01", _client->password); + str_append_c(str, '\x01'); + + *output_r = str_data(str); + *output_len_r = str_len(str); + client->output_sent = TRUE; + return 0; +} + +static int +mech_oauthbearer_set_parameter(struct dsasl_client *_client, const char *key, + const char *value, const char **error_r) +{ + struct oauthbearer_dsasl_client *client = + (struct oauthbearer_dsasl_client *)_client; + if (strcmp(key, "host") == 0) { + if (value != NULL) + client->host = p_strdup(_client->pool, value); + else + client->host = NULL; + return 1; + } else if (strcmp(key, "port") == 0) { + if (value == NULL) { + client->port = 0; + } else if (net_str2port(value, &client->port) < 0) { + *error_r = "Invalid port value"; + return -1; + } + return 1; + } + return 0; +} + +static int +mech_oauthbearer_get_result(struct dsasl_client *_client, const char *key, + const char **value_r, const char **error_r ATTR_UNUSED) +{ + struct oauthbearer_dsasl_client *client = + (struct oauthbearer_dsasl_client *)_client; + if (strcmp(key, "status") == 0) { + /* this is set to value after login attempt */ + i_assert(client->status != NULL); + *value_r = client->status; + return 1; + } + return 0; +} + +const struct dsasl_client_mech dsasl_client_mech_oauthbearer = { + .name = "OAUTHBEARER", + .struct_size = sizeof(struct oauthbearer_dsasl_client), + + .input = mech_oauthbearer_input, + .output = mech_oauthbearer_output, + .set_parameter = mech_oauthbearer_set_parameter, + .get_result = mech_oauthbearer_get_result, +}; + +const struct dsasl_client_mech dsasl_client_mech_xoauth2 = { + .name = "XOAUTH2", + .struct_size = sizeof(struct oauthbearer_dsasl_client), + + .input = mech_oauthbearer_input, + .output = mech_xoauth2_output, + .set_parameter = mech_oauthbearer_set_parameter, + .get_result = mech_oauthbearer_get_result, +}; |