bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi#include "lib.h"
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi#include "str.h"
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi#include "net.h"
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi#include "json-parser.h"
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi#include "istream.h"
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi#include "dsasl-client-private.h"
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomistruct oauthbearer_dsasl_client {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi struct dsasl_client client;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const char *host;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const char *status;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi in_port_t port;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi bool output_sent;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi};
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomistatic int
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomimech_oauthbearer_input(struct dsasl_client *_client,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const unsigned char *input, size_t input_len,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const char **error_r)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi{
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi struct oauthbearer_dsasl_client *client =
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi (struct oauthbearer_dsasl_client *)_client;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (!client->output_sent) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (input_len > 0) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *error_r = "Server sent non-empty initial response";
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return -1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi } else {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi client->status = "";
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi /* if response is empty, authentication has *SUCCEEDED* */
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (input_len == 0)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return 0;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi /* authentication has failed, try parse status.
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi we are only interested in extracting status if possible
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi so we don't really need to much error handling. */
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi struct istream *is = i_stream_create_from_data(input, input_len);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const char *status = NULL, *value;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const char *error = NULL;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi enum json_type jtype;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi bool found_status = FALSE;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi struct json_parser *parser = json_parser_init(is);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi while (json_parse_next(parser, &jtype, &value)>0) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (found_status && status == NULL) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (jtype == JSON_TYPE_STRING ||
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi jtype == JSON_TYPE_NUMBER)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi status = t_strdup(value);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi break;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi } else if (jtype == JSON_TYPE_OBJECT_KEY &&
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi strcmp(value, "status") == 0) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi found_status = TRUE;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi } else json_parse_skip_next(parser);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi /* deinitialize json parser */
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi int ret = json_parser_deinit(&parser, &error);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (status != NULL)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi client->status = p_strdup(_client->pool, status);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi else {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi ret = -1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (error == NULL)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi error = "Status value missing";
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (ret < 0)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *error_r = t_strdup_printf("Error parsing JSON reply: %s",
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi error);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi else
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *error_r = t_strdup_printf("Failed to authenticate: %s",
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi client->status);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return -1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return 0;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi}
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomistatic int
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomimech_oauthbearer_output(struct dsasl_client *_client,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const unsigned char **output_r, size_t *output_len_r,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const char **error_r)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi{
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi struct oauthbearer_dsasl_client *client =
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi (struct oauthbearer_dsasl_client *)_client;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi string_t *str;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (_client->set.authid == NULL) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *error_r = "authid not set";
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return -1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (_client->password == NULL) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *error_r = "password not set";
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return -1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi str = str_new(_client->pool, 64);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi str_printfa(str, "n,a=%s,\x01", _client->set.authid);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (client->host != NULL && *client->host != '\0')
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi str_printfa(str, "host=%s\x01", client->host);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (client->port > 0)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi str_printfa(str, "port=%u\x01", client->port);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi str_printfa(str, "auth=Bearer %s\x01", _client->password);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi str_append_c(str, '\x01');
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *output_r = str_data(str);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *output_len_r = str_len(str);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi client->output_sent = TRUE;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return 0;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi}
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomistatic int
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomimech_xoauth2_output(struct dsasl_client *_client,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const unsigned char **output_r, size_t *output_len_r,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const char **error_r)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi{
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi struct oauthbearer_dsasl_client *client =
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi (struct oauthbearer_dsasl_client *)_client;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi string_t *str;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (_client->set.authid == NULL) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *error_r = "authid not set";
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return -1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (_client->password == NULL) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *error_r = "password not set";
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return -1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi str = str_new(_client->pool, 64);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
b1808dcac22fa2c75d5666cc98c9ab59f030a798Aki Tuomi str_printfa(str, "user=%s\x01", _client->set.authid);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi str_printfa(str, "auth=Bearer %s\x01", _client->password);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi str_append_c(str, '\x01');
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *output_r = str_data(str);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *output_len_r = str_len(str);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi client->output_sent = TRUE;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return 0;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi}
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomistatic int
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomimech_oauthbearer_set_parameter(struct dsasl_client *_client, const char *key,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const char *value, const char **error_r)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi{
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi struct oauthbearer_dsasl_client *client =
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi (struct oauthbearer_dsasl_client *)_client;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (strcmp(key, "host") == 0) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (value != NULL)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi client->host = p_strdup(_client->pool, value);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi else
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi client->host = NULL;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return 1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi } else if (strcmp(key, "port") == 0) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (value == NULL) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi client->port = 0;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi } else if (net_str2port(key, &client->port) < 0) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *error_r = "Invalid port value";
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return -1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return 1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return 0;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi}
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomistatic int
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomimech_oauthbearer_get_result(struct dsasl_client *_client, const char *key,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi const char **value_r, const char **error_r ATTR_UNUSED)
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi{
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi struct oauthbearer_dsasl_client *client =
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi (struct oauthbearer_dsasl_client *)_client;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi if (strcmp(key, "status") == 0) {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi /* this is set to value after login attempt */
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi i_assert(client->status != NULL);
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi *value_r = client->status;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return 1;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi }
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi return 0;
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi}
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomiconst struct dsasl_client_mech dsasl_client_mech_oauthbearer = {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .name = "OAUTHBEARER",
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .struct_size = sizeof(struct oauthbearer_dsasl_client),
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .input = mech_oauthbearer_input,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .output = mech_oauthbearer_output,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .set_parameter = mech_oauthbearer_set_parameter,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .get_result = mech_oauthbearer_get_result,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi};
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomiconst struct dsasl_client_mech dsasl_client_mech_xoauth2 = {
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .name = "XOAUTH2",
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .struct_size = sizeof(struct oauthbearer_dsasl_client),
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi
feb5f5eb7c4ce0f965147eef2463c6a95d25344eAki Tuomi .input = mech_oauthbearer_input,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .output = mech_xoauth2_output,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .set_parameter = mech_oauthbearer_set_parameter,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi .get_result = mech_oauthbearer_get_result,
228f1e8d583b32a8ae8d192c0b9ebac95ada3308Aki Tuomi};