Coverage Report

Created: 2026-07-15 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dropbear/src/svr-authpubkey.c
Line
Count
Source
1
/*
2
 * Dropbear - a SSH2 server
3
 *
4
 * Copyright (c) 2002,2003 Matt Johnston
5
 * All rights reserved.
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE. */
24
/*
25
 * This file incorporates work covered by the following copyright and
26
 * permission notice:
27
 *
28
 *  Copyright (c) 2000 Markus Friedl.  All rights reserved.
29
 *
30
 *  Redistribution and use in source and binary forms, with or without
31
 *  modification, are permitted provided that the following conditions
32
 *  are met:
33
 *  1. Redistributions of source code must retain the above copyright
34
 *     notice, this list of conditions and the following disclaimer.
35
 *  2. Redistributions in binary form must reproduce the above copyright
36
 *     notice, this list of conditions and the following disclaimer in the
37
 *     documentation and/or other materials provided with the distribution.
38
 *
39
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
40
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
41
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
42
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
43
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
44
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
45
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
46
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
47
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
48
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
49
 *
50
 * This copyright and permission notice applies to the code parsing public keys
51
 * options string which can also be found in OpenSSH auth2-pubkey.c file
52
 * (user_key_allowed2). It has been adapted to work with buffers.
53
 *
54
 */
55
56
/* Process a pubkey auth request */
57
58
#include "includes.h"
59
#include "session.h"
60
#include "dbutil.h"
61
#include "buffer.h"
62
#include "signkey.h"
63
#include "auth.h"
64
#include "ssh.h"
65
#include "packet.h"
66
#include "algo.h"
67
#include "runopts.h"
68
69
#if DROPBEAR_SVR_PUBKEY_AUTH
70
71
/* "ssh-rsa AB" - short but doesn't matter */
72
0
#define MIN_AUTHKEYS_LINE 10
73
/* Max length of a valid key line in authkeys. A 16384 bit RSA key (ridiculous) is
74
 * 2786 bytes.
75
 * An addition limit DROPBEAR_MAX_LINE_LENGTH (10000) will stop file parsing entirely */
76
0
#define MAX_AUTHKEYS_LINE 3000
77
78
static char * authorized_keys_filepath(void);
79
static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
80
    const unsigned char* keyblob, unsigned int keybloblen,
81
    struct PubKeyOptions **ret_options);
82
static int checkpubkeyperms(void);
83
static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen,
84
    const unsigned char* keyblob, unsigned int keybloblen);
85
static int checkfileperm(char * filename);
86
87
/* process a pubkey auth request, sending success or failure message as
88
 * appropriate */
89
106
void svr_auth_pubkey(int valid_user) {
90
91
106
  unsigned char testkey; /* whether we're just checking if a key is usable */
92
106
  char* sigalgo = NULL;
93
106
  unsigned int sigalgolen;
94
106
  const char* keyalgo;
95
106
  unsigned int keyalgolen;
96
106
  unsigned char* keyblob = NULL;
97
106
  unsigned int keybloblen;
98
106
  unsigned int sign_payload_length;
99
106
  buffer * signbuf = NULL;
100
106
  sign_key * key = NULL;
101
106
  char* fp = NULL;
102
106
  enum signature_type sigtype;
103
106
  enum signkey_type keytype;
104
106
  int auth_failure = 1;
105
106
  struct PubKeyOptions *pubkey_options = NULL;
106
107
106
  TRACE(("enter pubkeyauth"))
108
109
  /* 0 indicates user just wants to check if key can be used, 1 is an
110
   * actual attempt*/
111
106
  testkey = (buf_getbool(ses.payload) == 0);
112
113
106
  sigalgo = buf_getstring(ses.payload, &sigalgolen);
114
106
  keybloblen = buf_getint(ses.payload);
115
106
  keyblob = buf_getptr(ses.payload, keybloblen);
116
117
106
  if (!valid_user) {
118
    /* Return failure once we have read the contents of the packet
119
    required to validate a public key.
120
    Avoids blind user enumeration though it isn't possible to prevent
121
    testing for user existence if the public key is known */
122
69
    send_msg_userauth_failure(0, 0);
123
69
    goto out;
124
69
  }
125
126
37
  sigtype = signature_type_from_name(sigalgo, sigalgolen);
127
37
  if (sigtype == DROPBEAR_SIGNATURE_NONE) {
128
0
    send_msg_userauth_failure(0, 0);
129
0
    goto out;
130
0
  }
131
132
37
  keytype = signkey_type_from_signature(sigtype);
133
37
  keyalgo = signkey_name_from_type(keytype, &keyalgolen);
134
135
#if DROPBEAR_PLUGIN
136
    if (svr_ses.plugin_instance != NULL) {
137
        char *options_buf;
138
        if (svr_ses.plugin_instance->checkpubkey(
139
                    svr_ses.plugin_instance,
140
                    &ses.plugin_session,
141
                    keyalgo,
142
                    keyalgolen,
143
                    keyblob,
144
                    keybloblen,
145
                    ses.authstate.username) == DROPBEAR_SUCCESS) {
146
            /* Success */
147
            auth_failure = 0;
148
149
            /* Options provided? */
150
            options_buf = ses.plugin_session->get_options(ses.plugin_session);
151
            if (options_buf) {
152
                struct buf temp_buf = {
153
                    .data = (unsigned char *)options_buf,
154
                    .len = strlen(options_buf),
155
                    .pos = 0,
156
                    .size = 0
157
                };
158
                pubkey_options = svr_parse_pubkey_options(&temp_buf, 0, "plugin");
159
                if (pubkey_options == NULL) {
160
                    /* Fail immediately as the plugin provided wrong options */
161
                    send_msg_userauth_failure(0, 0);
162
                    goto out;
163
                }
164
            }
165
        }
166
    }
167
#endif
168
  /* check if the key is valid */
169
37
  if (auth_failure) {
170
0
    int status = checkpubkey(keyalgo, keyalgolen, keyblob, keybloblen, &pubkey_options);
171
0
    auth_failure = (status != DROPBEAR_SUCCESS);
172
0
  }
173
174
37
  if (auth_failure) {
175
    /* MAX_PUBKEY_QUERIES allows a greater limit of pubkey queries
176
     * than the standard maxauthtries.
177
     * Start counting failures (incrfail) only when it's reaching
178
     * the limit.
179
     */
180
0
    unsigned int free_query_limit =
181
0
      MAX(0, MAX_PUBKEY_QUERIES - (int)svr_opts.maxauthtries);
182
0
    int incrfail = ses.authstate.serv_pubkey_query_count >= free_query_limit;
183
0
    send_msg_userauth_failure(0, incrfail);
184
0
    ses.authstate.serv_pubkey_query_count++;
185
0
    goto out;
186
0
  }
187
188
  /* let them know that the key is ok to use */
189
37
  if (testkey) {
190
0
    send_msg_userauth_pk_ok(sigalgo, sigalgolen, keyblob, keybloblen);
191
0
    goto out;
192
0
  }
193
194
  /* now we can actually verify the signature */
195
196
  /* get the key */
197
37
  key = new_sign_key();
198
37
  if (buf_get_pub_key(ses.payload, key, &keytype) == DROPBEAR_FAILURE) {
199
0
    send_msg_userauth_failure(0, 1);
200
0
    goto out;
201
0
  }
202
203
37
#if DROPBEAR_SK_ECDSA || DROPBEAR_SK_ED25519
204
37
  key->sk_flags_mask = SSH_SK_USER_PRESENCE_REQD;
205
37
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
206
37
  if (pubkey_options->no_touch_required_flag) {
207
0
    key->sk_flags_mask &= ~SSH_SK_USER_PRESENCE_REQD;
208
0
  }
209
37
  if (pubkey_options->verify_required_flag) {
210
0
    key->sk_flags_mask |= SSH_SK_USER_VERIFICATION_REQD;
211
0
  }
212
37
#endif /* DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT */
213
37
#endif
214
215
  /* create the data which has been signed - this a string containing
216
   * session_id, concatenated with the payload packet up to the signature */
217
37
  assert(ses.payload_beginning <= ses.payload->pos);
218
37
  sign_payload_length = ses.payload->pos - ses.payload_beginning;
219
0
  signbuf = buf_new(ses.payload->pos + 4 + ses.session_id->len);
220
0
  buf_putbufstring(signbuf, ses.session_id);
221
222
  /* The entire contents of the payload prior. */
223
0
  buf_setpos(ses.payload, ses.payload_beginning);
224
0
  buf_putbytes(signbuf,
225
0
    buf_getptr(ses.payload, sign_payload_length),
226
0
    sign_payload_length);
227
0
  buf_incrpos(ses.payload, sign_payload_length);
228
229
0
  buf_setpos(signbuf, 0);
230
231
  /* ... and finally verify the signature */
232
0
  fp = sign_key_fingerprint(keyblob, keybloblen);
233
0
  if (buf_verify(ses.payload, key, sigtype, signbuf) == DROPBEAR_SUCCESS) {
234
0
    if (ses.authstate.pubkey_options == NULL) {
235
0
      ses.authstate.pubkey_options = pubkey_options;
236
0
      pubkey_options = NULL;
237
0
    }
238
0
    if (svr_opts.multiauthmethod && (ses.authstate.authtypes & ~AUTH_TYPE_PUBKEY)) {
239
      /* successful pubkey authentication, but extra auth required */
240
0
      dropbear_log(LOG_NOTICE,
241
0
          "Pubkey auth succeeded for '%s' with %s key %s from %s, extra auth required",
242
0
          ses.authstate.pw_name,
243
0
          signkey_name_from_type(keytype, NULL), fp,
244
0
          svr_ses.addrstring);
245
0
      ses.authstate.authtypes &= ~AUTH_TYPE_PUBKEY; /* pubkey auth ok, delete the method flag */
246
0
      send_msg_userauth_failure(1, 0); /* Send partial success */
247
0
    } else {
248
      /* successful authentication */
249
0
      dropbear_log(LOG_NOTICE,
250
0
          "Pubkey auth succeeded for '%s' with %s key %s from %s",
251
0
          ses.authstate.pw_name,
252
0
          signkey_name_from_type(keytype, NULL), fp,
253
0
          svr_ses.addrstring);
254
0
      send_msg_userauth_success();
255
0
    }
256
#if DROPBEAR_PLUGIN
257
    if ((ses.plugin_session != NULL) && (svr_ses.plugin_instance->auth_success != NULL)) {
258
        /* Was authenticated through the external plugin. tell plugin that signature verification was ok */
259
        svr_ses.plugin_instance->auth_success(ses.plugin_session);
260
    }
261
#endif
262
0
  } else {
263
0
    dropbear_log(LOG_WARNING,
264
0
        "Pubkey auth bad signature for '%s' with key %s from %s",
265
0
        ses.authstate.pw_name, fp, svr_ses.addrstring);
266
0
    send_msg_userauth_failure(0, 1);
267
0
  }
268
0
  m_free(fp);
269
270
69
out:
271
  /* cleanup stuff */
272
69
  if (signbuf) {
273
0
    buf_free(signbuf);
274
0
  }
275
69
  if (sigalgo) {
276
69
    m_free(sigalgo);
277
69
  }
278
69
  if (key) {
279
0
    sign_key_free(key);
280
0
    key = NULL;
281
0
  }
282
69
  if (pubkey_options) {
283
0
    svr_pubkey_options_cleanup(pubkey_options);
284
0
    pubkey_options = NULL;
285
0
  }
286
69
  TRACE(("leave pubkeyauth"))
287
69
}
288
289
/* Reply that the key is valid for auth, this is sent when the user sends
290
 * a straight copy of their pubkey to test, to avoid having to perform
291
 * expensive signing operations with a worthless key */
292
static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen,
293
0
    const unsigned char* keyblob, unsigned int keybloblen) {
294
295
0
  TRACE(("enter send_msg_userauth_pk_ok"))
296
0
  CHECKCLEARTOWRITE();
297
298
0
  buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_PK_OK);
299
0
  buf_putstring(ses.writepayload, sigalgo, sigalgolen);
300
0
  buf_putstring(ses.writepayload, (const char*)keyblob, keybloblen);
301
302
0
  encrypt_packet();
303
0
  TRACE(("leave send_msg_userauth_pk_ok"))
304
305
0
}
306
307
/* Key options are optionally returned in ret_options.
308
   Should be passed with *ret_options = NULL, will only be populated
309
   on success return. */
310
static int checkpubkey_line(buffer* line, int line_num, const char* filename,
311
    const char* algo, unsigned int algolen,
312
    const unsigned char* keyblob, unsigned int keybloblen,
313
0
    struct PubKeyOptions ** ret_options) {
314
0
  buffer *options_buf = NULL;
315
0
  char *info_str = NULL;
316
0
  unsigned int pos, len, infopos, infolen;
317
0
  int ret = DROPBEAR_FAILURE;
318
319
0
  if (ret_options) {
320
0
    *ret_options = NULL;
321
0
  }
322
323
0
  if (line->len < MIN_AUTHKEYS_LINE || line->len > MAX_AUTHKEYS_LINE) {
324
0
    TRACE(("checkpubkey_line: bad line length %d", line->len))
325
0
    goto out;
326
0
  }
327
328
0
  if (memchr(line->data, 0x0, line->len) != NULL) {
329
0
    TRACE(("checkpubkey_line: bad line has null char"))
330
0
    goto out;
331
0
  }
332
333
  /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */
334
0
  if (line->pos + algolen+3 > line->len) {
335
0
    goto out;
336
0
  }
337
  /* check the key type */
338
0
  if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) {
339
0
    int is_comment = 0;
340
0
    unsigned char *options_start = NULL;
341
0
    int options_len = 0;
342
0
    int escape, quoted;
343
344
    /* skip over any comments or leading whitespace */
345
0
    while (line->pos < line->len) {
346
0
      const char c = buf_getbyte(line);
347
0
      if (c == ' ' || c == '\t') {
348
0
        continue;
349
0
      } else if (c == '#') {
350
0
        is_comment = 1;
351
0
        break;
352
0
      }
353
0
      buf_decrpos(line, 1);
354
0
      break;
355
0
    }
356
0
    if (is_comment) {
357
      /* next line */
358
0
      goto out;
359
0
    }
360
361
    /* remember start of options */
362
0
    options_start = buf_getptr(line, 1);
363
0
    quoted = 0;
364
0
    escape = 0;
365
0
    options_len = 0;
366
367
    /* figure out where the options are */
368
0
    while (line->pos < line->len) {
369
0
      const char c = buf_getbyte(line);
370
0
      if (!quoted && (c == ' ' || c == '\t')) {
371
0
        break;
372
0
      }
373
0
      escape = (!escape && c == '\\');
374
0
      if (!escape && c == '"') {
375
0
        quoted = !quoted;
376
0
      }
377
0
      options_len++;
378
0
    }
379
0
    options_buf = buf_new(options_len);
380
0
    buf_putbytes(options_buf, options_start, options_len);
381
382
    /* compare the algorithm. +3 so we have enough bytes to read a space and some base64 characters too. */
383
0
    if (line->pos + algolen+3 > line->len) {
384
0
      goto out;
385
0
    }
386
0
    if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) {
387
0
      goto out;
388
0
    }
389
0
  }
390
0
  buf_incrpos(line, algolen);
391
392
  /* check for space (' ') character */
393
0
  if (buf_getbyte(line) != ' ') {
394
0
    TRACE(("checkpubkey_line: space character expected, isn't there"))
395
0
    goto out;
396
0
  }
397
398
  /* find the length of base64 data */
399
0
  pos = line->pos;
400
0
  for (len = 0; line->pos < line->len; len++) {
401
0
    if (buf_getbyte(line) == ' ') {
402
0
      break;
403
0
    }
404
0
  }
405
406
  /* find out the length of the public key info, stop at the first space */
407
0
  infopos = line->pos;
408
0
  for (infolen = 0; line->pos < line->len; infolen++) {
409
0
    const char c = buf_getbyte(line);
410
0
    if (c == ' ') {
411
0
      break;
412
0
    }
413
    /* We have an allowlist - authorized_keys lines can't be fully trusted,
414
    some shell scripts may do unsafe things with env var values */
415
0
    if (!(isalnum(c) || strchr(".,_-+@", c))) {
416
0
      TRACE(("Not setting SSH_PUBKEYINFO, special characters"))
417
0
      infolen = 0;
418
0
      break;
419
0
    }
420
0
  }
421
0
  if (infolen > 0) {
422
0
    info_str = m_malloc(infolen + 1);
423
0
    buf_setpos(line, infopos);
424
0
    strncpy(info_str, buf_getptr(line, infolen), infolen);
425
0
  }
426
427
  /* truncate to base64 data length */
428
0
  buf_setpos(line, pos);
429
0
  buf_setlen(line, line->pos + len);
430
431
0
  TRACE(("checkpubkey_line: line pos = %d len = %d", line->pos, line->len))
432
433
0
  ret = cmp_base64_key(keyblob, keybloblen, (const unsigned char *) algo, algolen, line, NULL);
434
435
  /* free pubkey_info if it is filled */
436
0
  if (ret == DROPBEAR_SUCCESS) {
437
0
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
438
0
    if (ret_options) {
439
0
      *ret_options = svr_parse_pubkey_options(options_buf, line_num, filename);
440
0
      if (*ret_options == NULL) {
441
0
        ret = DROPBEAR_FAILURE;
442
0
        goto out;
443
0
      }
444
      /* take the (optional) public key information */
445
0
      (*ret_options)->info_env = info_str;
446
0
      info_str = NULL;
447
0
    }
448
0
#endif
449
0
  }
450
451
0
out:
452
0
  if (options_buf) {
453
0
    buf_free(options_buf);
454
0
  }
455
0
  if (info_str) {
456
0
    m_free(info_str);
457
0
  }
458
0
  return ret;
459
0
}
460
461
/* Returns the full path to the user's authorized_keys file in an
462
 * allocated string which caller must free. */
463
0
static char *authorized_keys_filepath() {
464
0
  size_t len = 0;
465
0
  char *pathname = NULL, *dir = NULL;
466
0
  const char *filename = "authorized_keys";
467
468
0
  dir = expand_homedir_path_home(svr_opts.authorized_keys_dir,
469
0
               ses.authstate.pw_dir);
470
471
  /* allocate max required pathname storage,
472
   * = dir + "/" + "authorized_keys" + '\0' */;
473
0
  len = strlen(dir) + strlen(filename) + 2;
474
0
  pathname = m_malloc(len);
475
0
  snprintf(pathname, len, "%s/%s", dir, filename);
476
0
  m_free(dir);
477
0
  return pathname;
478
0
}
479
480
/* Checks whether a specified publickey (and associated algorithm) is an
481
 * acceptable key for authentication */
482
/* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */
483
static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
484
    const unsigned char* keyblob, unsigned int keybloblen,
485
0
    struct PubKeyOptions **ret_options) {
486
487
0
  FILE * authfile = NULL;
488
0
  char * filename = NULL;
489
0
  int ret = DROPBEAR_FAILURE;
490
0
  buffer * line = NULL;
491
0
  int line_num;
492
0
  uid_t origuid;
493
0
  gid_t origgid;
494
495
0
  TRACE(("enter checkpubkey"))
496
497
0
#if DROPBEAR_SVR_MULTIUSER
498
  /* access the file as the authenticating user. */
499
0
  origuid = getuid();
500
0
  origgid = getgid();
501
0
  if ((setegid(ses.authstate.pw_gid)) < 0 ||
502
0
    (seteuid(ses.authstate.pw_uid)) < 0) {
503
0
    dropbear_exit("Failed to set euid");
504
0
  }
505
0
#endif
506
  /* check file permissions, also whether file exists */
507
0
  if (checkpubkeyperms() == DROPBEAR_FAILURE) {
508
0
    TRACE(("bad authorized_keys permissions, or file doesn't exist"))
509
0
  } else {
510
0
    int fd;
511
    /* we don't need to check pw and pw_dir for validity, since
512
     * its been done in checkpubkeyperms. */
513
0
    filename = authorized_keys_filepath();
514
0
    fd = open(filename, O_RDONLY | O_NONBLOCK);
515
0
    if (fd >= 0) {
516
0
      authfile = fdopen(fd, "r");
517
0
      if (!authfile) {
518
        /* fdopen could fail with ENOMEM */
519
0
        m_close(fd);
520
0
      }
521
0
    }
522
0
    if (!authfile) {
523
0
      TRACE(("checkpubkey: failed opening %s: %s", filename, strerror(errno)))
524
0
    }
525
0
  }
526
0
#if DROPBEAR_SVR_MULTIUSER
527
0
  if ((seteuid(origuid)) < 0 ||
528
0
    (setegid(origgid)) < 0) {
529
0
    dropbear_exit("Failed to revert euid");
530
0
  }
531
0
#endif
532
533
0
  if (authfile == NULL) {
534
0
    goto out;
535
0
  }
536
0
  TRACE(("checkpubkey: opened authorized_keys OK"))
537
538
0
  line = buf_new(MAX_AUTHKEYS_LINE);
539
540
  /* iterate through the lines */
541
0
  for (line_num = 1; line_num <= MAX_AUTHKEYS_LINE_COUNT; line_num++) {
542
0
    if (buf_getline(line, authfile) == DROPBEAR_FAILURE) {
543
      /* EOF reached */
544
0
      TRACE(("checkpubkey: authorized_keys EOF reached"))
545
0
      break;
546
0
    }
547
548
0
    ret = checkpubkey_line(line, line_num, filename, keyalgo, keyalgolen,
549
0
      keyblob, keybloblen, ret_options);
550
0
    if (ret == DROPBEAR_SUCCESS) {
551
0
      break;
552
0
    }
553
0
  }
554
555
0
  if (line_num > MAX_AUTHKEYS_LINE_COUNT) {
556
0
    TRACE(("authorized_keys line limit"))
557
0
  }
558
559
0
out:
560
0
  if (authfile) {
561
0
    fclose(authfile);
562
0
  }
563
0
  if (line) {
564
0
    buf_free(line);
565
0
  }
566
0
  m_free(filename);
567
0
  TRACE(("leave checkpubkey: ret=%d", ret))
568
0
  return ret;
569
0
}
570
571
572
/* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok,
573
 * DROPBEAR_FAILURE otherwise.
574
 * Checks that the authorized_keys path permissions are all owned by either
575
 * root or the user, and are g-w, o-w.
576
 * When this path is inside the user's home dir it checks up to and including
577
 * the home dir, otherwise it checks every path component. */
578
0
static int checkpubkeyperms() {
579
0
  char *path = authorized_keys_filepath(), *sep = NULL;
580
0
  int ret = DROPBEAR_SUCCESS;
581
582
0
  TRACE(("enter checkpubkeyperms"))
583
584
  /* Walk back up path checking permissions, stopping at either homedir,
585
   * or root if the path is outside of the homedir. */
586
0
  while ((sep = strrchr(path, '/')) != NULL) {
587
0
    if (sep == path) { /* root directory */
588
0
      sep++;
589
0
    }
590
0
    *sep = '\0';
591
0
    if (checkfileperm(path) != DROPBEAR_SUCCESS) {
592
0
      TRACE(("checkpubkeyperms: bad perm on %s", path))
593
0
      ret = DROPBEAR_FAILURE;
594
0
    }
595
0
    if (strcmp(path, ses.authstate.pw_dir) == 0 || strcmp(path, "/") == 0) {
596
0
      break;
597
0
    }
598
0
  }
599
600
  /* all looks ok, return success */
601
0
  m_free(path);
602
603
0
  TRACE(("leave checkpubkeyperms"))
604
0
  return ret;
605
0
}
606
607
/* Checks that a file is owned by the user or root, and isn't writable by
608
 * group or other */
609
/* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
610
0
static int checkfileperm(char * filename) {
611
0
  struct stat filestat;
612
0
  int badperm = 0;
613
614
0
  TRACE(("enter checkfileperm(%s)", filename))
615
616
0
  if (stat(filename, &filestat) != 0) {
617
0
    TRACE(("leave checkfileperm: stat() != 0"))
618
0
    return DROPBEAR_FAILURE;
619
0
  }
620
  /* check ownership - user or root only*/
621
0
  if (filestat.st_uid != ses.authstate.pw_uid
622
0
      && filestat.st_uid != 0) {
623
0
    badperm = 1;
624
0
    TRACE(("wrong ownership"))
625
0
  }
626
  /* check permissions - don't want group or others +w */
627
0
  if (filestat.st_mode & (S_IWGRP | S_IWOTH)) {
628
0
    badperm = 1;
629
0
    TRACE(("wrong perms"))
630
0
  }
631
0
  if (badperm) {
632
0
    if (!ses.authstate.perm_warn) {
633
0
      ses.authstate.perm_warn = 1;
634
0
      dropbear_log(LOG_INFO, "%s must be owned by user or root, and not writable by group or others", filename);
635
0
    }
636
0
    TRACE(("leave checkfileperm: failure perms/owner"))
637
0
    return DROPBEAR_FAILURE;
638
0
  }
639
640
0
  TRACE(("leave checkfileperm: success"))
641
0
  return DROPBEAR_SUCCESS;
642
0
}
643
644
#if DROPBEAR_FUZZ
645
void fuzz_checkpubkey_line(buffer* line, int line_num, char* filename,
646
    const char* algo, unsigned int algolen,
647
0
    const unsigned char* keyblob, unsigned int keybloblen) {
648
  struct PubKeyOptions *options = NULL;
649
0
  checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen, &options);
650
0
  svr_pubkey_options_cleanup(options);
651
0
}
652
#endif
653
654
#endif