Coverage Report

Created: 2026-10-13 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssh/auth2-chall.c
Line
Count
Source
1
/* $OpenBSD: auth2-chall.c,v 1.61 2026/07/06 07:44:48 djm Exp $ */
2
/*
3
 * Copyright (c) 2001 Markus Friedl.  All rights reserved.
4
 * Copyright (c) 2001 Per Allansson.  All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions
8
 * are met:
9
 * 1. Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 * 2. Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
27
#include "includes.h"
28
29
#include <sys/types.h>
30
31
#include <stdlib.h>
32
#include <stdio.h>
33
#include <string.h>
34
#include <stdarg.h>
35
36
#include "xmalloc.h"
37
#include "ssh2.h"
38
#include "sshkey.h"
39
#include "hostfile.h"
40
#include "auth.h"
41
#include "sshbuf.h"
42
#include "packet.h"
43
#include "dispatch.h"
44
#include "ssherr.h"
45
#include "log.h"
46
#include "misc.h"
47
#include "servconf.h"
48
49
/* import */
50
extern ServerOptions options;
51
52
static int auth2_challenge_start(struct ssh *);
53
static int send_userauth_info_request(struct ssh *);
54
static int input_userauth_info_response(int, uint32_t, struct ssh *);
55
56
#ifdef BSD_AUTH
57
extern KbdintDevice mm_bsdauth_device;
58
#else
59
#ifdef USE_PAM
60
extern KbdintDevice mm_sshpam_device;
61
#endif
62
#endif
63
64
KbdintDevice *devices[] = {
65
#ifdef BSD_AUTH
66
  &mm_bsdauth_device,
67
#else
68
#ifdef USE_PAM
69
  &mm_sshpam_device,
70
#endif
71
#endif
72
  NULL
73
};
74
75
typedef struct KbdintAuthctxt KbdintAuthctxt;
76
struct KbdintAuthctxt
77
{
78
  char *devices;
79
  void *ctxt;
80
  KbdintDevice *device;
81
  u_int nreq;
82
  u_int devices_done;
83
};
84
85
#ifdef USE_PAM
86
void
87
remove_kbdint_device(const char *devname)
88
{
89
  int i, j;
90
91
  for (i = 0; devices[i] != NULL; i++)
92
    if (strcmp(devices[i]->name, devname) == 0) {
93
      for (j = i; devices[j] != NULL; j++)
94
        devices[j] = devices[j+1];
95
      i--;
96
    }
97
}
98
#endif
99
100
static KbdintAuthctxt *
101
kbdint_alloc(const char *devs)
102
0
{
103
0
  KbdintAuthctxt *kbdintctxt;
104
0
  struct sshbuf *b;
105
0
  int i, r;
106
107
#ifdef USE_PAM
108
  if (!options.use_pam)
109
    remove_kbdint_device("pam");
110
#endif
111
112
0
  kbdintctxt = xcalloc(1, sizeof(KbdintAuthctxt));
113
0
  if (strcmp(devs, "") == 0) {
114
0
    if ((b = sshbuf_new()) == NULL)
115
0
      fatal_f("sshbuf_new failed");
116
0
    for (i = 0; devices[i]; i++) {
117
0
      if ((r = sshbuf_putf(b, "%s%s",
118
0
          sshbuf_len(b) ? "," : "", devices[i]->name)) != 0)
119
0
        fatal_fr(r, "buffer error");
120
0
    }
121
0
    if ((kbdintctxt->devices = sshbuf_dup_string(b)) == NULL)
122
0
      fatal_f("sshbuf_dup_string failed");
123
0
    sshbuf_free(b);
124
0
  } else {
125
0
    kbdintctxt->devices = xstrdup(devs);
126
0
  }
127
0
  debug("kbdint_alloc: devices '%s'", kbdintctxt->devices);
128
0
  kbdintctxt->ctxt = NULL;
129
0
  kbdintctxt->device = NULL;
130
0
  kbdintctxt->nreq = 0;
131
132
0
  return kbdintctxt;
133
0
}
134
static void
135
kbdint_reset_device(KbdintAuthctxt *kbdintctxt)
136
0
{
137
0
  if (kbdintctxt->ctxt) {
138
0
    kbdintctxt->device->free_ctx(kbdintctxt->ctxt);
139
0
    kbdintctxt->ctxt = NULL;
140
0
  }
141
0
  kbdintctxt->device = NULL;
142
0
}
143
static void
144
kbdint_free(KbdintAuthctxt *kbdintctxt)
145
0
{
146
0
  if (kbdintctxt->device)
147
0
    kbdint_reset_device(kbdintctxt);
148
0
  free(kbdintctxt->devices);
149
0
  freezero(kbdintctxt, sizeof(*kbdintctxt));
150
0
}
151
/* get next device */
152
static int
153
kbdint_next_device(Authctxt *authctxt, KbdintAuthctxt *kbdintctxt)
154
0
{
155
0
  size_t len;
156
0
  char *t;
157
0
  size_t i;
158
159
0
  if (kbdintctxt->device)
160
0
    kbdint_reset_device(kbdintctxt);
161
0
  do {
162
0
    len = kbdintctxt->devices ?
163
0
        strcspn(kbdintctxt->devices, ",") : 0;
164
165
0
    if (len == 0)
166
0
      break;
167
0
    for (i = 0; devices[i]; i++) {
168
0
      if (i >= sizeof(kbdintctxt->devices_done) * 8 ||
169
0
          i >= sizeof(devices) / sizeof(devices[0]))
170
0
        fatal_f("internal error: too many devices");
171
0
      if ((kbdintctxt->devices_done & (1 << i)) != 0 ||
172
0
          !auth2_method_allowed(authctxt,
173
0
          "keyboard-interactive", devices[i]->name))
174
0
        continue;
175
0
      if (strlen(devices[i]->name) == len &&
176
0
          memcmp(kbdintctxt->devices, devices[i]->name,
177
0
          len) == 0) {
178
0
        kbdintctxt->device = devices[i];
179
0
        kbdintctxt->devices_done |= 1 << i;
180
0
      }
181
0
    }
182
0
    t = kbdintctxt->devices;
183
0
    kbdintctxt->devices = t[len] ? xstrdup(t+len+1) : NULL;
184
0
    free(t);
185
0
    debug2("kbdint_next_device: devices %s", kbdintctxt->devices ?
186
0
        kbdintctxt->devices : "<empty>");
187
0
  } while (kbdintctxt->devices && !kbdintctxt->device);
188
189
0
  return kbdintctxt->device ? 1 : 0;
190
0
}
191
192
/*
193
 * try challenge-response, set authctxt->postponed if we have to
194
 * wait for the response.
195
 */
196
int
197
auth2_challenge(struct ssh *ssh, char *devs)
198
0
{
199
0
  Authctxt *authctxt = ssh->authctxt;
200
0
  debug("auth2_challenge: user=%s devs=%s",
201
0
      authctxt->user ? authctxt->user : "<nouser>",
202
0
      devs ? devs : "<no devs>");
203
204
0
  if (authctxt->user == NULL || !devs)
205
0
    return 0;
206
0
  if (authctxt->kbdintctxt == NULL)
207
0
    authctxt->kbdintctxt = kbdint_alloc(devs);
208
0
  return auth2_challenge_start(ssh);
209
0
}
210
211
/* unregister kbd-int callbacks and context */
212
void
213
auth2_challenge_stop(struct ssh *ssh)
214
148
{
215
148
  Authctxt *authctxt = ssh->authctxt;
216
  /* unregister callback */
217
148
  ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL);
218
148
  if (authctxt->kbdintctxt != NULL) {
219
0
    kbdint_free(authctxt->kbdintctxt);
220
0
    authctxt->kbdintctxt = NULL;
221
0
  }
222
148
}
223
224
/* side effect: sets authctxt->postponed if a reply was sent*/
225
static int
226
auth2_challenge_start(struct ssh *ssh)
227
0
{
228
0
  Authctxt *authctxt = ssh->authctxt;
229
0
  KbdintAuthctxt *kbdintctxt = authctxt->kbdintctxt;
230
231
0
  debug2("auth2_challenge_start: devices %s",
232
0
      kbdintctxt->devices ?  kbdintctxt->devices : "<empty>");
233
234
0
  if (kbdint_next_device(authctxt, kbdintctxt) == 0) {
235
0
    auth2_challenge_stop(ssh);
236
0
    return 0;
237
0
  }
238
0
  debug("auth2_challenge_start: trying authentication method '%s'",
239
0
      kbdintctxt->device->name);
240
241
0
  if ((kbdintctxt->ctxt = kbdintctxt->device->init_ctx(authctxt)) == NULL) {
242
0
    auth2_challenge_stop(ssh);
243
0
    return 0;
244
0
  }
245
0
  if (send_userauth_info_request(ssh) == 0) {
246
0
    auth2_challenge_stop(ssh);
247
0
    return 0;
248
0
  }
249
0
  ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_INFO_RESPONSE,
250
0
      &input_userauth_info_response);
251
252
0
  authctxt->postponed = 1;
253
0
  return 0;
254
0
}
255
256
static int
257
send_userauth_info_request(struct ssh *ssh)
258
0
{
259
0
  Authctxt *authctxt = ssh->authctxt;
260
0
  KbdintAuthctxt *kbdintctxt;
261
0
  char *name, *instr, **prompts;
262
0
  u_int r, i, *echo_on;
263
264
0
  kbdintctxt = authctxt->kbdintctxt;
265
0
  if (kbdintctxt->device->query(kbdintctxt->ctxt,
266
0
      &name, &instr, &kbdintctxt->nreq, &prompts, &echo_on))
267
0
    return 0;
268
269
0
  if ((r = sshpkt_start(ssh, SSH2_MSG_USERAUTH_INFO_REQUEST)) != 0 ||
270
0
      (r = sshpkt_put_cstring(ssh, name)) != 0 ||
271
0
      (r = sshpkt_put_cstring(ssh, instr)) != 0 ||
272
0
      (r = sshpkt_put_cstring(ssh, "")) != 0 || /* language not used */
273
0
      (r = sshpkt_put_u32(ssh, kbdintctxt->nreq)) != 0)
274
0
    fatal_fr(r, "start packet");
275
0
  for (i = 0; i < kbdintctxt->nreq; i++) {
276
0
    if ((r = sshpkt_put_cstring(ssh, prompts[i])) != 0 ||
277
0
        (r = sshpkt_put_u8(ssh, echo_on[i])) != 0)
278
0
      fatal_fr(r, "assemble packet");
279
0
  }
280
0
  if ((r = sshpkt_send(ssh)) != 0 ||
281
0
      (r = ssh_packet_write_wait(ssh)) != 0)
282
0
    fatal_fr(r, "send packet");
283
284
0
  for (i = 0; i < kbdintctxt->nreq; i++)
285
0
    free(prompts[i]);
286
0
  free(prompts);
287
0
  free(echo_on);
288
0
  free(name);
289
0
  free(instr);
290
0
  return 1;
291
0
}
292
293
static int
294
input_userauth_info_response(int type, uint32_t seq, struct ssh *ssh)
295
0
{
296
0
  Authctxt *authctxt = ssh->authctxt;
297
0
  KbdintAuthctxt *kbdintctxt;
298
0
  int authenticated = 0, res;
299
0
  int r;
300
0
  u_int i, nresp;
301
0
  const char *devicename = NULL;
302
0
  char **response = NULL;
303
0
  double tstart = monotime_double();
304
305
0
  if (authctxt == NULL)
306
0
    fatal_f("no authctxt");
307
0
  kbdintctxt = authctxt->kbdintctxt;
308
0
  if (kbdintctxt == NULL || kbdintctxt->ctxt == NULL)
309
0
    fatal_f("no kbdintctxt");
310
0
  if (kbdintctxt->device == NULL)
311
0
    fatal_f("no device");
312
313
0
  authctxt->postponed = 0;  /* reset */
314
0
  if ((r = sshpkt_get_u32(ssh, &nresp)) != 0)
315
0
    fatal_fr(r, "parse packet");
316
0
  if (nresp != kbdintctxt->nreq)
317
0
    fatal_f("wrong number of replies");
318
0
  if (nresp > 100)
319
0
    fatal_f("too many replies");
320
0
  if (nresp > 0) {
321
0
    response = xcalloc(nresp, sizeof(char *));
322
0
    for (i = 0; i < nresp; i++) {
323
0
      if ((r = sshpkt_get_cstring(ssh, &response[i], NULL)) != 0)
324
0
        fatal_fr(r, "parse response");
325
0
    }
326
0
  }
327
0
  if ((r = sshpkt_get_end(ssh)) != 0)
328
0
    fatal_fr(r, "parse packet");
329
330
0
  res = kbdintctxt->device->respond(kbdintctxt->ctxt, nresp, response);
331
332
0
  for (i = 0; i < nresp; i++) {
333
0
    explicit_bzero(response[i], strlen(response[i]));
334
0
    free(response[i]);
335
0
  }
336
0
  free(response);
337
338
0
  switch (res) {
339
0
  case 0:
340
    /* Success! */
341
0
    authenticated = authctxt->valid ? 1 : 0;
342
0
    break;
343
0
  case 1:
344
    /* Authentication needs further interaction */
345
0
    if (send_userauth_info_request(ssh) == 1)
346
0
      authctxt->postponed = 1;
347
0
    break;
348
0
  default:
349
    /* Failure! */
350
0
    break;
351
0
  }
352
0
  devicename = kbdintctxt->device->name;
353
0
  if (!authctxt->postponed) {
354
0
    if (authenticated) {
355
0
      auth2_challenge_stop(ssh);
356
0
    } else {
357
      /* start next device */
358
      /* may set authctxt->postponed */
359
0
      auth2_challenge_start(ssh);
360
0
    }
361
0
  }
362
363
0
  if (!authenticated)
364
0
    auth_failure_delay(authctxt, tstart);
365
0
  userauth_finish(ssh, authenticated, "keyboard-interactive",
366
0
      devicename);
367
0
  return 0;
368
0
}