Coverage Report

Created: 2025-11-11 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-sasl/dsasl-client-mech-oauthbearer.c
Line
Count
Source
1
/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
#include "net.h"
6
#include "json-istream.h"
7
#include "istream.h"
8
#include "auth-gs2.h"
9
#include "sasl-oauth2.h"
10
#include "dsasl-client-private.h"
11
12
struct oauthbearer_dsasl_client {
13
  struct dsasl_client client;
14
  const char *status;
15
  bool output_sent;
16
};
17
18
static enum dsasl_client_result
19
mech_oauthbearer_input(struct dsasl_client *_client,
20
           const unsigned char *input, size_t input_len,
21
           const char **error_r)
22
0
{
23
0
  struct oauthbearer_dsasl_client *client =
24
0
    container_of(_client, struct oauthbearer_dsasl_client, client);
25
26
0
  if (!client->output_sent) {
27
0
    if (input_len > 0) {
28
0
      *error_r = "Server sent non-empty initial response";
29
0
      return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
30
0
    }
31
0
  } else {
32
0
    client->status = "";
33
    /* if response is empty, authentication has *SUCCEEDED* */
34
0
    if (input_len == 0)
35
0
      return DSASL_CLIENT_RESULT_OK;
36
37
    /* authentication has failed, try parse status.
38
       we are only interested in extracting status if possible
39
       so we don't really need to much error handling. */
40
0
    struct istream *is = i_stream_create_from_data(input, input_len);
41
0
    struct json_node jnode;
42
0
    struct json_istream *jis = json_istream_create_object(
43
0
      is, NULL, JSON_PARSER_FLAG_NUMBERS_AS_STRING);
44
0
    const char *error;
45
0
    int ret;
46
47
0
    i_stream_unref(&is);
48
49
0
    ret = 1;
50
0
    while (ret > 0) {
51
0
      ret = json_istream_read(jis, &jnode);
52
0
      i_assert(ret != 0);
53
0
      if (ret < 0)
54
0
        break;
55
0
      i_assert(jnode.name != NULL);
56
0
      if (strcmp(jnode.name, "status") == 0) {
57
0
        if (!json_node_is_string(&jnode) &&
58
0
            !json_node_is_number(&jnode)) {
59
0
          *error_r = "Status field in response is not a string or a number.";
60
0
          return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
61
0
        }
62
0
        client->status = p_strdup(
63
0
          _client->pool,
64
0
          json_node_get_str(&jnode));
65
0
      }
66
0
      json_istream_skip(jis);
67
0
    }
68
69
0
    ret = json_istream_finish(&jis, &error);
70
0
    i_assert(ret != 0);
71
0
    if (ret < 0) {
72
0
      *error_r = t_strdup_printf(
73
0
        "Error parsing JSON reply: %s", error);
74
0
      return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
75
0
    }
76
77
0
    if (client->status == NULL) {
78
0
      *error_r = "Error parsing JSON reply: "
79
0
        "Status value missing";
80
0
      return DSASL_CLIENT_RESULT_ERR_PROTOCOL;
81
0
    }
82
83
0
    *error_r = t_strdup_printf("Failed to authenticate: %s",
84
0
             client->status);
85
0
    return DSASL_CLIENT_RESULT_AUTH_FAILED;
86
0
  }
87
0
  return DSASL_CLIENT_RESULT_OK;
88
0
}
89
90
static enum dsasl_client_result
91
mech_oauthbearer_output(struct dsasl_client *_client,
92
      const unsigned char **output_r, size_t *output_len_r,
93
      const char **error_r)
94
558
{
95
558
  struct oauthbearer_dsasl_client *client =
96
558
    container_of(_client, struct oauthbearer_dsasl_client, client);
97
558
  string_t *str;
98
99
558
  if (_client->set.authid == NULL) {
100
0
    *error_r = "authid not set";
101
0
    return DSASL_CLIENT_RESULT_ERR_INTERNAL;
102
0
  }
103
558
  if (_client->password == NULL) {
104
0
    *error_r = "password not set";
105
0
    return DSASL_CLIENT_RESULT_ERR_INTERNAL;
106
0
  }
107
558
  if (!sasl_oauth2_kvpair_check_value(_client->password)) {
108
6
    *error_r = "password contains unsupported characters";
109
6
    return DSASL_CLIENT_RESULT_ERR_INTERNAL;
110
6
  }
111
552
  if (_client->set.host != NULL &&
112
552
      !sasl_oauth2_kvpair_check_value(_client->set.host)) {
113
0
    *error_r = "host contains unsupported characters";
114
0
    return DSASL_CLIENT_RESULT_ERR_INTERNAL;
115
0
  }
116
117
552
  struct auth_gs2_header gs2_header = {
118
552
    .authzid = _client->set.authid,
119
552
  };
120
121
552
  str = str_new(_client->pool, 64);
122
552
  auth_gs2_header_encode(&gs2_header, str);
123
552
  str_append_c(str, '\x01');
124
552
  if (_client->set.host != NULL && *_client->set.host != '\0')
125
552
    str_printfa(str, "host=%s\x01", _client->set.host);
126
552
  if (_client->set.port > 0)
127
0
    str_printfa(str, "port=%u\x01", _client->set.port);
128
552
  str_printfa(str, "auth=Bearer %s\x01", _client->password);
129
552
  str_append_c(str, '\x01');
130
131
552
  *output_r = str_data(str);
132
552
  *output_len_r = str_len(str);
133
552
  client->output_sent = TRUE;
134
552
  return DSASL_CLIENT_RESULT_OK;
135
552
}
136
137
static enum dsasl_client_result
138
mech_xoauth2_output(struct dsasl_client *_client,
139
        const unsigned char **output_r, size_t *output_len_r,
140
        const char **error_r)
141
297
{
142
297
  struct oauthbearer_dsasl_client *client =
143
297
    (struct oauthbearer_dsasl_client *)_client;
144
297
  string_t *str;
145
146
297
  if (_client->set.authid == NULL) {
147
0
    *error_r = "authid not set";
148
0
    return DSASL_CLIENT_RESULT_ERR_INTERNAL;
149
0
  }
150
297
  if (_client->password == NULL) {
151
0
    *error_r = "password not set";
152
0
    return DSASL_CLIENT_RESULT_ERR_INTERNAL;
153
0
  }
154
297
  if (strchr(_client->password, 0x01) != NULL) {
155
2
    *error_r = "password contains unsupported characters";
156
2
    return DSASL_CLIENT_RESULT_ERR_INTERNAL;
157
2
  }
158
159
295
  str = str_new(_client->pool, 64);
160
161
295
  str_printfa(str, "user=%s\x01", _client->set.authid);
162
295
  str_printfa(str, "auth=Bearer %s\x01", _client->password);
163
295
  str_append_c(str, '\x01');
164
165
295
  *output_r = str_data(str);
166
295
  *output_len_r = str_len(str);
167
295
  client->output_sent = TRUE;
168
295
  return DSASL_CLIENT_RESULT_OK;
169
297
}
170
171
static int
172
mech_oauthbearer_get_result(struct dsasl_client *_client, const char *key,
173
          const char **value_r,
174
          const char **error_r ATTR_UNUSED)
175
0
{
176
0
  struct oauthbearer_dsasl_client *client =
177
0
    container_of(_client, struct oauthbearer_dsasl_client, client);
178
179
0
  if (strcmp(key, "status") == 0) {
180
    /* this is set to value after login attempt */
181
0
    i_assert(client->status != NULL);
182
0
    *value_r = client->status;
183
0
    return 1;
184
0
  }
185
0
  return 0;
186
0
}
187
188
const struct dsasl_client_mech dsasl_client_mech_oauthbearer = {
189
  .name = SASL_MECH_NAME_OAUTHBEARER,
190
  .struct_size = sizeof(struct oauthbearer_dsasl_client),
191
192
  .input = mech_oauthbearer_input,
193
  .output = mech_oauthbearer_output,
194
  .get_result = mech_oauthbearer_get_result,
195
};
196
197
const struct dsasl_client_mech dsasl_client_mech_xoauth2 = {
198
  .name = SASL_MECH_NAME_XOAUTH2,
199
  .struct_size = sizeof(struct oauthbearer_dsasl_client),
200
201
  .input = mech_oauthbearer_input,
202
  .output = mech_xoauth2_output,
203
  .get_result = mech_oauthbearer_get_result,
204
};