Coverage Report

Created: 2025-10-28 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}