summaryrefslogtreecommitdiffstats
path: root/src/lib-sasl/mech-oauthbearer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-sasl/mech-oauthbearer.c')
-rw-r--r--src/lib-sasl/mech-oauthbearer.c201
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,
+};