/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 | | }; |