/src/dovecot/src/lib-sasl/sasl-server-mech-otp.c
Line | Count | Source |
1 | | /* |
2 | | * One-Time-Password (RFC 2444) authentication mechanism. |
3 | | * |
4 | | * Copyright (c) 2006 Andrey Panin <pazke@donpac.ru> |
5 | | * |
6 | | * This software is released under the MIT license. |
7 | | */ |
8 | | |
9 | | #include "lib.h" |
10 | | #include "safe-memset.h" |
11 | | #include "hash.h" |
12 | | #include "hex-binary.h" |
13 | | #include "otp.h" |
14 | | |
15 | | #include "sasl-server-protected.h" |
16 | | |
17 | | struct otp_auth_request { |
18 | | struct sasl_server_mech_request auth_request; |
19 | | |
20 | | bool lock; |
21 | | |
22 | | struct otp_state state; |
23 | | }; |
24 | | |
25 | | struct otp_auth_mech_data { |
26 | | struct sasl_server_mech_data data; |
27 | | |
28 | | HASH_TABLE(const char *, struct otp_auth_request *) lock_table; |
29 | | }; |
30 | | |
31 | | /* |
32 | | * Locking |
33 | | */ |
34 | | |
35 | | static bool otp_try_lock(struct otp_auth_request *request) |
36 | 1.59k | { |
37 | 1.59k | struct sasl_server_mech_request *auth_request = &request->auth_request; |
38 | 1.59k | struct otp_auth_mech_data *otp_mdata = |
39 | 1.59k | container_of(auth_request->mech->data, |
40 | 1.59k | struct otp_auth_mech_data, data); |
41 | | |
42 | 1.59k | i_assert(auth_request->authid != NULL); |
43 | 1.59k | if (hash_table_lookup(otp_mdata->lock_table, |
44 | 1.59k | auth_request->authid) != NULL) |
45 | 0 | return FALSE; |
46 | | |
47 | 1.59k | hash_table_insert(otp_mdata->lock_table, auth_request->authid, request); |
48 | 1.59k | request->lock = TRUE; |
49 | 1.59k | return TRUE; |
50 | 1.59k | } |
51 | | |
52 | | static void otp_unlock(struct otp_auth_request *request) |
53 | 2.84k | { |
54 | 2.84k | struct sasl_server_mech_request *auth_request = &request->auth_request; |
55 | 2.84k | struct otp_auth_mech_data *otp_mdata = |
56 | 2.84k | container_of(auth_request->mech->data, |
57 | 2.84k | struct otp_auth_mech_data, data); |
58 | | |
59 | 2.84k | if (!request->lock) |
60 | 1.24k | return; |
61 | | |
62 | 2.84k | i_assert(auth_request->authid != NULL); |
63 | 1.59k | hash_table_remove(otp_mdata->lock_table, auth_request->authid); |
64 | 1.59k | request->lock = FALSE; |
65 | 1.59k | } |
66 | | |
67 | | /* |
68 | | * Authentication |
69 | | */ |
70 | | |
71 | | static void |
72 | | otp_send_challenge(struct otp_auth_request *request, |
73 | | const unsigned char *credentials, size_t size) |
74 | 1.59k | { |
75 | 1.59k | struct sasl_server_mech_request *auth_request = &request->auth_request; |
76 | 1.59k | const char *answer; |
77 | | |
78 | 1.59k | if (otp_parse_dbentry(t_strndup(credentials, size), |
79 | 1.59k | &request->state) != 0) { |
80 | 0 | e_error(auth_request->event, "invalid OTP data in passdb"); |
81 | 0 | sasl_server_request_failure(auth_request); |
82 | 0 | return; |
83 | 0 | } |
84 | | |
85 | 1.59k | if (--request->state.seq < 1) { |
86 | 0 | e_error(auth_request->event, "sequence number < 1"); |
87 | 0 | sasl_server_request_failure(auth_request); |
88 | 0 | return; |
89 | 0 | } |
90 | | |
91 | 1.59k | if (!otp_try_lock(request)) { |
92 | 0 | e_error(auth_request->event, "user is locked, race attack?"); |
93 | 0 | sasl_server_request_failure(auth_request); |
94 | 0 | return; |
95 | 0 | } |
96 | | |
97 | 1.59k | answer = p_strdup_printf(auth_request->pool, "otp-%s %u %s ext", |
98 | 1.59k | digest_name(request->state.algo), |
99 | 1.59k | request->state.seq, request->state.seed); |
100 | | |
101 | 1.59k | sasl_server_request_output(auth_request, answer, strlen(answer)); |
102 | 1.59k | } |
103 | | |
104 | | static void |
105 | | otp_credentials_callback(struct sasl_server_mech_request *auth_request, |
106 | | const struct sasl_passdb_result *result) |
107 | 1.68k | { |
108 | 1.68k | struct otp_auth_request *request = |
109 | 1.68k | container_of(auth_request, struct otp_auth_request, |
110 | 1.68k | auth_request); |
111 | | |
112 | 1.68k | switch (result->status) { |
113 | 1.59k | case SASL_PASSDB_RESULT_OK: |
114 | 1.59k | otp_send_challenge(request, result->credentials.data, |
115 | 1.59k | result->credentials.size); |
116 | 1.59k | break; |
117 | 0 | case SASL_PASSDB_RESULT_INTERNAL_FAILURE: |
118 | 0 | sasl_server_request_internal_failure(auth_request); |
119 | 0 | break; |
120 | 86 | default: |
121 | 86 | sasl_server_request_failure(auth_request); |
122 | 86 | break; |
123 | 1.68k | } |
124 | 1.68k | } |
125 | | |
126 | | static void |
127 | | mech_otp_auth_phase1(struct otp_auth_request *request, |
128 | | const unsigned char *data, size_t data_size) |
129 | 1.73k | { |
130 | 1.73k | struct sasl_server_mech_request *auth_request = &request->auth_request; |
131 | 1.73k | const char *authenid; |
132 | 1.73k | size_t i, count; |
133 | | |
134 | | /* authorization ID \0 authentication ID |
135 | | FIXME: we'll ignore authorization ID for now. */ |
136 | 1.73k | authenid = NULL; |
137 | | |
138 | 1.73k | count = 0; |
139 | 1.07M | for (i = 0; i < data_size; i++) { |
140 | 1.07M | if (data[i] == '\0') { |
141 | 123k | if (++count == 1) |
142 | 1.71k | authenid = (const char *) data + i + 1; |
143 | 123k | } |
144 | 1.07M | } |
145 | | |
146 | 1.73k | if (count != 1) { |
147 | 48 | e_info(auth_request->event, "invalid input"); |
148 | 48 | sasl_server_request_failure(auth_request); |
149 | 48 | return; |
150 | 48 | } |
151 | | |
152 | 1.68k | if (!sasl_server_request_set_authid( |
153 | 1.68k | auth_request, SASL_SERVER_AUTHID_TYPE_USERNAME, |
154 | 1.68k | authenid)) { |
155 | 0 | sasl_server_request_failure(auth_request); |
156 | 0 | return; |
157 | 0 | } |
158 | | |
159 | 1.68k | sasl_server_request_lookup_credentials(auth_request, "OTP", |
160 | 1.68k | otp_credentials_callback); |
161 | 1.68k | } |
162 | | |
163 | | static void |
164 | | otp_set_credentials_callback(struct sasl_server_mech_request *auth_request, |
165 | | const struct sasl_passdb_result *result) |
166 | 196 | { |
167 | 196 | struct otp_auth_request *request = |
168 | 196 | container_of(auth_request, struct otp_auth_request, |
169 | 196 | auth_request); |
170 | | |
171 | 196 | if (result->status == SASL_PASSDB_RESULT_OK) |
172 | 196 | sasl_server_request_success(auth_request, "", 0); |
173 | 0 | else |
174 | 0 | sasl_server_request_internal_failure(auth_request); |
175 | | |
176 | 196 | otp_unlock(request); |
177 | 196 | } |
178 | | |
179 | | static void |
180 | | mech_otp_verify(struct otp_auth_request *request, const char *data, bool hex) |
181 | 405 | { |
182 | 405 | struct sasl_server_mech_request *auth_request = &request->auth_request; |
183 | 405 | struct otp_state *state = &request->state; |
184 | 405 | unsigned char hash[OTP_HASH_SIZE], cur_hash[OTP_HASH_SIZE]; |
185 | 405 | int ret; |
186 | | |
187 | 405 | ret = otp_parse_response(data, hash, hex); |
188 | 405 | if (ret < 0) { |
189 | 49 | e_info(auth_request->event, "invalid response"); |
190 | 49 | sasl_server_request_failure(auth_request); |
191 | 49 | otp_unlock(request); |
192 | 49 | return; |
193 | 49 | } |
194 | | |
195 | 356 | otp_next_hash(state->algo, hash, cur_hash); |
196 | | |
197 | 356 | ret = memcmp(cur_hash, state->hash, OTP_HASH_SIZE); |
198 | 356 | if (ret != 0) { |
199 | 160 | sasl_server_request_password_mismatch(auth_request); |
200 | 160 | otp_unlock(request); |
201 | 160 | return; |
202 | 160 | } |
203 | | |
204 | 196 | memcpy(state->hash, hash, sizeof(state->hash)); |
205 | | |
206 | 196 | sasl_server_request_set_credentials(auth_request, "OTP", |
207 | 196 | otp_print_dbentry(state), |
208 | 196 | otp_set_credentials_callback); |
209 | 196 | } |
210 | | |
211 | | static void |
212 | | mech_otp_verify_init(struct otp_auth_request *request, const char *data, |
213 | | bool hex) |
214 | 528 | { |
215 | 528 | struct sasl_server_mech_request *auth_request = &request->auth_request; |
216 | 528 | struct otp_state new_state; |
217 | 528 | unsigned char hash[OTP_HASH_SIZE], cur_hash[OTP_HASH_SIZE]; |
218 | 528 | const char *error; |
219 | 528 | int ret; |
220 | | |
221 | 528 | ret = otp_parse_init_response(data, &new_state, cur_hash, hex, &error); |
222 | 528 | if (ret < 0) { |
223 | 487 | e_info(auth_request->event, "invalid init response, %s", error); |
224 | 487 | sasl_server_request_failure(auth_request); |
225 | 487 | otp_unlock(request); |
226 | 487 | return; |
227 | 487 | } |
228 | | |
229 | 41 | otp_next_hash(request->state.algo, cur_hash, hash); |
230 | | |
231 | 41 | ret = memcmp(hash, request->state.hash, OTP_HASH_SIZE); |
232 | 41 | if (ret != 0) { |
233 | 41 | sasl_server_request_password_mismatch(auth_request); |
234 | 41 | otp_unlock(request); |
235 | 41 | return; |
236 | 41 | } |
237 | | |
238 | 0 | sasl_server_request_set_credentials(auth_request, "OTP", |
239 | 0 | otp_print_dbentry(&new_state), |
240 | 0 | otp_set_credentials_callback); |
241 | 0 | } |
242 | | |
243 | | static void |
244 | | mech_otp_auth_phase2(struct otp_auth_request *request, |
245 | | const unsigned char *data, size_t data_size) |
246 | 1.10k | { |
247 | 1.10k | struct sasl_server_mech_request *auth_request = &request->auth_request; |
248 | 1.10k | const char *value, *str = t_strndup(data, data_size); |
249 | | |
250 | 1.10k | if (str_begins(str, "hex:", &value)) |
251 | 387 | mech_otp_verify(request, value, TRUE); |
252 | 721 | else if (str_begins(str, "word:", &value)) |
253 | 18 | mech_otp_verify(request, value, FALSE); |
254 | 703 | else if (str_begins(str, "init-hex:", &value)) |
255 | 446 | mech_otp_verify_init(request, value, TRUE); |
256 | 257 | else if (str_begins(str, "init-word:", &value)) |
257 | 82 | mech_otp_verify_init(request, value, FALSE); |
258 | 175 | else { |
259 | 175 | e_info(auth_request->event, "unsupported response type"); |
260 | 175 | sasl_server_request_failure(auth_request); |
261 | 175 | otp_unlock(request); |
262 | 175 | } |
263 | 1.10k | } |
264 | | |
265 | | static void |
266 | | mech_otp_auth_continue(struct sasl_server_mech_request *auth_request, |
267 | | const unsigned char *data, size_t data_size) |
268 | 2.84k | { |
269 | 2.84k | struct otp_auth_request *request = |
270 | 2.84k | container_of(auth_request, struct otp_auth_request, |
271 | 2.84k | auth_request); |
272 | | |
273 | 2.84k | if (auth_request->authid == NULL) |
274 | 1.73k | mech_otp_auth_phase1(request, data, data_size); |
275 | 1.10k | else |
276 | 1.10k | mech_otp_auth_phase2(request, data, data_size); |
277 | 2.84k | } |
278 | | |
279 | | static struct sasl_server_mech_request * |
280 | | mech_otp_auth_new(const struct sasl_server_mech *mech ATTR_UNUSED, pool_t pool) |
281 | 1.73k | { |
282 | 1.73k | struct otp_auth_request *request; |
283 | | |
284 | 1.73k | request = p_new(pool, struct otp_auth_request, 1); |
285 | 1.73k | request->lock = FALSE; |
286 | | |
287 | 1.73k | return &request->auth_request; |
288 | 1.73k | } |
289 | | |
290 | | static void mech_otp_auth_free(struct sasl_server_mech_request *auth_request) |
291 | 1.73k | { |
292 | 1.73k | struct otp_auth_request *request = |
293 | 1.73k | container_of(auth_request, struct otp_auth_request, |
294 | 1.73k | auth_request); |
295 | | |
296 | 1.73k | otp_unlock(request); |
297 | 1.73k | } |
298 | | |
299 | | /* |
300 | | * Mechanism |
301 | | */ |
302 | | |
303 | | static struct sasl_server_mech_data *mech_otp_data_new(pool_t pool) |
304 | 7.15k | { |
305 | 7.15k | struct otp_auth_mech_data *otp_mdata; |
306 | | |
307 | 7.15k | otp_mdata = p_new(pool, struct otp_auth_mech_data, 1); |
308 | 7.15k | hash_table_create(&otp_mdata->lock_table, default_pool, 128, |
309 | 7.15k | strcase_hash, strcasecmp); |
310 | | |
311 | 7.15k | return &otp_mdata->data; |
312 | 7.15k | } |
313 | | |
314 | | static void mech_otp_data_free(struct sasl_server_mech_data *mdata) |
315 | 7.15k | { |
316 | 7.15k | struct otp_auth_mech_data *otp_mdata = |
317 | 7.15k | container_of(mdata, struct otp_auth_mech_data, data); |
318 | | |
319 | 7.15k | hash_table_destroy(&otp_mdata->lock_table); |
320 | 7.15k | } |
321 | | |
322 | | static const struct sasl_server_mech_funcs mech_otp_funcs = { |
323 | | .auth_new = mech_otp_auth_new, |
324 | | .auth_initial = sasl_server_mech_generic_auth_initial, |
325 | | .auth_continue = mech_otp_auth_continue, |
326 | | .auth_free = mech_otp_auth_free, |
327 | | |
328 | | .data_new = mech_otp_data_new, |
329 | | .data_free = mech_otp_data_free, |
330 | | }; |
331 | | |
332 | | static const struct sasl_server_mech_def mech_otp = { |
333 | | .name = SASL_MECH_NAME_OTP, |
334 | | |
335 | | .flags = SASL_MECH_SEC_DICTIONARY | SASL_MECH_SEC_ACTIVE | |
336 | | SASL_MECH_SEC_ALLOW_NULS, |
337 | | .passdb_need = SASL_MECH_PASSDB_NEED_SET_CREDENTIALS, |
338 | | |
339 | | .funcs = &mech_otp_funcs, |
340 | | }; |
341 | | |
342 | | void sasl_server_mech_register_otp(struct sasl_server_instance *sinst) |
343 | 7.15k | { |
344 | 7.15k | sasl_server_mech_register(sinst, &mech_otp, NULL); |
345 | 7.15k | } |