Coverage Report

Created: 2026-05-30 06:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dropbear/src/cli-runopts.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
#include "includes.h"
26
#include "runopts.h"
27
#include "signkey.h"
28
#include "buffer.h"
29
#include "dbutil.h"
30
#include "algo.h"
31
#include "forward.h"
32
#include "list.h"
33
34
cli_runopts cli_opts; /* GLOBAL */
35
36
static void printhelp(void);
37
static void parse_hostname(const char* orighostarg);
38
static void parse_multihop_hostname(const char* orighostarg, const char* argv0);
39
static void fill_own_user(void);
40
#if DROPBEAR_CLI_ANYTCPFWD
41
static void addforward(const char* str, m_list *fwdlist);
42
#endif
43
#if DROPBEAR_CLI_NETCAT
44
static void add_netcat(const char *str);
45
#endif
46
static void add_extendedopt(const char *str);
47
48
#if DROPBEAR_USE_SSH_CONFIG
49
static void apply_config_settings(const char* cli_host_arg);
50
#endif
51
52
0
static void printhelp() {
53
54
0
  fprintf(stderr, "Dropbear SSH client v%s https://matt.ucc.asn.au/dropbear/dropbear.html\n"
55
0
#if DROPBEAR_CLI_MULTIHOP
56
0
          "Usage: %s [options] [user@]host[/port][,[user@]host/port],...] [command]\n"
57
#else
58
          "Usage: %s [options] [user@]host[/port] [command]\n"
59
#endif
60
0
          "-p <remoteport>\n"
61
0
          "-l <username>\n"
62
0
          "-t    Allocate a pty\n"
63
0
          "-T    Don't allocate a pty\n"
64
0
          "-N    Don't run a remote command\n"
65
0
          "-f    Run in background after auth\n"
66
0
          "-q    quiet, don't show remote banner\n"
67
0
          "-y    Always accept remote host key if unknown\n"
68
0
          "-y -y Don't perform any remote host key checking (caution)\n"
69
0
          "-s    Request a subsystem (use by external sftp)\n"
70
0
          "-o option     Set option in OpenSSH-like format ('-o help' to list options)\n"
71
0
#if DROPBEAR_CLI_PUBKEY_AUTH
72
0
          "-i <identityfile>   (multiple allowed, default %s)\n"
73
0
#endif
74
0
#if DROPBEAR_CLI_AGENTFWD
75
0
          "-A    Enable agent auth forwarding\n"
76
0
#endif
77
0
#if DROPBEAR_CLI_LOCALTCPFWD
78
0
          "-L <[listenaddress:]listenport:remotehost:remoteport> Local port forwarding\n"
79
0
          "-g    Allow remote hosts to connect to forwarded ports\n"
80
0
#endif
81
0
#if DROPBEAR_CLI_REMOTETCPFWD
82
0
          "-R <[listenaddress:]listenport:remotehost:remoteport> Remote port forwarding\n"
83
0
#endif
84
0
          "-W <receive_window_buffer> (default %d, larger may be faster, max 10MB)\n"
85
0
          "-K <keepalive>  (0 is never, default %d)\n"
86
0
          "-I <idle_timeout>  (0 is never, default %d)\n"
87
0
          "-M <max_duration>  (0 is off, default %d, in seconds)\n"
88
0
          "-z    disable QoS\n"
89
0
#if DROPBEAR_CLI_NETCAT
90
0
          "-B <endhost:endport> Netcat-alike forwarding\n"
91
0
#endif
92
0
#if DROPBEAR_CLI_PROXYCMD
93
0
          "-J <proxy_program> Use program pipe rather than TCP connection\n"
94
0
#endif
95
0
#if DROPBEAR_USER_ALGO_LIST
96
0
          "-c <cipher list> Specify preferred ciphers ('-c help' to list options)\n"
97
0
          "-m <MAC list> Specify preferred MACs for packet verification (or '-m help')\n"
98
0
#endif
99
0
          "-b    [bind_address][:bind_port]\n"
100
0
          "-Q    <algo>   Print supported algorithms, or -Q help\n"
101
0
          "-V    Version\n"
102
#if DEBUG_TRACE
103
          "-v    verbose (repeat for more verbose)\n"
104
#endif
105
0
          ,DROPBEAR_VERSION, cli_opts.progname,
106
0
#if DROPBEAR_CLI_PUBKEY_AUTH
107
0
          DROPBEAR_DEFAULT_CLI_AUTHKEY,
108
0
#endif
109
0
          DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE, DEFAULT_IDLE_TIMEOUT,
110
0
          DEFAULT_MAX_DURATION);
111
112
0
}
113
114
1
void cli_getopts(int argc, char ** argv) {
115
1
  unsigned int i, j;
116
1
  const char ** next = NULL;
117
1
  enum {
118
1
    OPT_EXTENDED_OPTIONS,
119
1
#if DROPBEAR_CLI_PUBKEY_AUTH
120
1
    OPT_AUTHKEY,
121
1
#endif
122
1
#if DROPBEAR_CLI_LOCALTCPFWD
123
1
    OPT_LOCALTCPFWD,
124
1
#endif
125
1
#if DROPBEAR_CLI_REMOTETCPFWD
126
1
    OPT_REMOTETCPFWD,
127
1
#endif
128
1
#if DROPBEAR_CLI_NETCAT
129
1
    OPT_NETCAT,
130
1
#endif
131
    /* a flag (no arg) if 'next' is NULL, a string-valued option otherwise */
132
1
    OPT_OTHER
133
1
  } opt;
134
1
  unsigned int cmdlen;
135
136
1
  const char* recv_window_arg = NULL;
137
1
  const char* idle_timeout_arg = NULL;
138
1
  const char* max_duration_arg = NULL;
139
1
  const char *host_arg = NULL;
140
1
  const char *proxycmd_arg = NULL;
141
1
  const char *remoteport_arg = NULL;
142
1
  const char *username_arg = NULL;
143
1
  const char *algo_print_arg = NULL;
144
1
  char c;
145
146
  /* see printhelp() for options */
147
1
  cli_opts.progname = argv[0];
148
1
  cli_opts.remotehost = NULL;
149
1
  cli_opts.remotehostfixed = 0;
150
1
  cli_opts.remoteport = NULL;
151
1
  cli_opts.username = NULL;
152
1
  cli_opts.cmd = NULL;
153
1
  cli_opts.no_cmd = 0;
154
1
  cli_opts.quiet = 0;
155
1
  cli_opts.backgrounded = 0;
156
1
  cli_opts.wantpty = 9; /* 9 means "it hasn't been touched", gets set later */
157
1
  cli_opts.always_accept_key = 0;
158
1
  cli_opts.ask_hostkey = 1;
159
1
  cli_opts.no_hostkey_check = 0;
160
1
  cli_opts.is_subsystem = 0;
161
1
#if DROPBEAR_CLI_PUBKEY_AUTH
162
1
  cli_opts.privkeys = list_new();
163
1
#endif
164
1
#if DROPBEAR_CLI_ANYTCPFWD
165
1
  cli_opts.exit_on_fwd_failure = 0;
166
1
#endif
167
1
  cli_opts.disable_trivial_auth = 0;
168
1
  cli_opts.password_authentication = 1;
169
1
  cli_opts.batch_mode = 0;
170
1
#if DROPBEAR_CLI_LOCALTCPFWD
171
1
  cli_opts.localfwds = list_new();
172
1
  opts.listen_fwd_all = 0;
173
1
#endif
174
1
#if DROPBEAR_CLI_REMOTETCPFWD
175
1
  cli_opts.remotefwds = list_new();
176
1
#endif
177
1
#if DROPBEAR_CLI_AGENTFWD
178
1
  cli_opts.agent_fwd = 0;
179
1
  cli_opts.agent_fd = -1;
180
1
  cli_opts.agent_keys_loaded = 0;
181
1
#endif
182
1
#if DROPBEAR_CLI_PROXYCMD
183
1
  cli_opts.proxycmd = NULL;
184
1
#endif
185
1
  cli_opts.bind_arg = NULL;
186
1
  cli_opts.bind_address = NULL;
187
1
  cli_opts.bind_port = NULL;
188
1
  cli_opts.keepalive_arg = NULL;
189
#ifndef DISABLE_ZLIB
190
  opts.compression = DROPBEAR_CLI_COMPRESSION;
191
#endif
192
1
#if DROPBEAR_USER_ALGO_LIST
193
1
  opts.cipher_list = NULL;
194
1
  opts.mac_list = NULL;
195
1
#endif
196
1
#ifndef DISABLE_SYSLOG
197
1
  opts.usingsyslog = 0;
198
1
#endif
199
  /* not yet
200
  opts.ipv4 = 1;
201
  opts.ipv6 = 1;
202
  */
203
1
  opts.recv_window = DEFAULT_RECV_WINDOW;
204
1
  opts.keepalive_secs = DEFAULT_KEEPALIVE;
205
1
  opts.idle_timeout_secs = DEFAULT_IDLE_TIMEOUT;
206
1
  opts.max_duration_secs = DEFAULT_MAX_DURATION;
207
208
1
  fill_own_user();
209
210
2
  for (i = 1; i < (unsigned int)argc; i++) {
211
    /* Handle non-flag arguments such as hostname or commands for the remote host */
212
1
    if (argv[i][0] != '-')
213
1
    {
214
1
      if (host_arg == NULL) {
215
1
        host_arg = argv[i];
216
1
        continue;
217
1
      }
218
      /* Commands to pass to the remote host. No more flag handling,
219
      commands are consumed below */
220
0
      break;
221
1
    }
222
223
    /* Begins with '-' */
224
0
    opt = OPT_OTHER;
225
0
    for (j = 1; (c = argv[i][j]) != '\0' && !next && opt == OPT_OTHER; j++) {
226
0
      switch (c) {
227
0
        case 'y':
228
          /* once is always accept the remote hostkey,
229
           * the same as stricthostkeychecking=accept-new */
230
0
          if (cli_opts.always_accept_key) {
231
            /* twice means no checking at all
232
             * (stricthostkeychecking=no) */
233
0
            cli_opts.no_hostkey_check = 1;
234
0
          }
235
0
          cli_opts.always_accept_key = 1;
236
0
          break;
237
0
        case 'q': /* quiet */
238
0
          cli_opts.quiet = 1;
239
0
          break;
240
0
        case 'p': /* remoteport */
241
0
          next = &remoteport_arg;
242
0
          break;
243
0
#if DROPBEAR_CLI_PUBKEY_AUTH
244
0
        case 'i': /* an identityfile */
245
0
          opt = OPT_AUTHKEY;
246
0
          break;
247
0
#endif
248
0
        case 't': /* we want a pty */
249
0
          cli_opts.wantpty = 1;
250
0
          break;
251
0
        case 'T': /* don't want a pty */
252
0
          cli_opts.wantpty = 0;
253
0
          break;
254
0
        case 'N':
255
0
          cli_opts.no_cmd = 1;
256
0
          break;
257
0
        case 'f':
258
0
          cli_opts.backgrounded = 1;
259
0
          break;
260
0
        case 's':
261
0
          cli_opts.is_subsystem = 1;
262
0
          break;
263
0
        case 'o':
264
0
          opt = OPT_EXTENDED_OPTIONS;
265
0
          break;
266
0
#if DROPBEAR_CLI_LOCALTCPFWD
267
0
        case 'L':
268
0
          opt = OPT_LOCALTCPFWD;
269
0
          break;
270
0
        case 'g':
271
0
          opts.listen_fwd_all = 1;
272
0
          break;
273
0
#endif
274
0
#if DROPBEAR_CLI_REMOTETCPFWD
275
0
        case 'R':
276
0
          opt = OPT_REMOTETCPFWD;
277
0
          break;
278
0
#endif
279
0
#if DROPBEAR_CLI_NETCAT
280
0
        case 'B':
281
0
          opt = OPT_NETCAT;
282
0
          break;
283
0
#endif
284
0
#if DROPBEAR_CLI_PROXYCMD
285
0
        case 'J':
286
0
          next = &proxycmd_arg;
287
0
          break;
288
0
#endif
289
0
        case 'l':
290
0
          next = &username_arg;
291
0
          break;
292
0
        case 'Q':
293
0
          next = &algo_print_arg;
294
0
          break;
295
0
        case 'h':
296
0
          printhelp();
297
0
          exit(EXIT_SUCCESS);
298
0
          break;
299
0
        case 'u':
300
          /* backwards compatibility with old urandom option */
301
0
          break;
302
0
        case 'W':
303
0
          next = &recv_window_arg;
304
0
          break;
305
0
        case 'K':
306
0
          next = &cli_opts.keepalive_arg;
307
0
          break;
308
0
        case 'I':
309
0
          next = &idle_timeout_arg;
310
0
          break;
311
0
        case 'M':
312
0
          next = &max_duration_arg;
313
0
          break;
314
0
#if DROPBEAR_CLI_AGENTFWD
315
0
        case 'A':
316
0
          cli_opts.agent_fwd = 1;
317
0
          break;
318
0
#endif
319
0
#if DROPBEAR_USER_ALGO_LIST
320
0
        case 'c':
321
0
          next = &opts.cipher_list;
322
0
          break;
323
0
        case 'm':
324
0
          next = &opts.mac_list;
325
0
          break;
326
0
#endif
327
#if DEBUG_TRACE
328
        case 'v':
329
          debug_trace++;
330
          break;
331
#endif
332
0
        case 'F':
333
0
        case 'e':
334
#if !DROPBEAR_USER_ALGO_LIST
335
        case 'c':
336
        case 'm':
337
#endif
338
0
        case 'D':
339
#if !DROPBEAR_CLI_REMOTETCPFWD
340
        case 'R':
341
#endif
342
#if !DROPBEAR_CLI_LOCALTCPFWD
343
        case 'L':
344
#endif
345
0
        case 'V':
346
0
          print_version();
347
0
          exit(EXIT_SUCCESS);
348
0
          break;
349
0
        case 'b':
350
0
          next = &cli_opts.bind_arg;
351
0
          break;
352
0
        case 'z':
353
0
          opts.disable_ip_tos = 1;
354
0
          break;
355
0
        default:
356
0
          fprintf(stderr,
357
0
            "WARNING: Ignoring unknown option -%c\n", c);
358
0
          break;
359
0
      } /* Switch */
360
0
    }
361
362
0
    if (!next && opt == OPT_OTHER) /* got a flag */
363
0
      continue;
364
365
0
    if (c == '\0') {
366
0
      i++;
367
0
      j = 0;
368
0
      if (!argv[i])
369
0
        dropbear_exit("Missing argument");
370
0
    }
371
372
0
    if (opt == OPT_EXTENDED_OPTIONS) {
373
0
      TRACE(("opt extended"))
374
0
      add_extendedopt(&argv[i][j]);
375
0
    }
376
0
    else
377
0
#if DROPBEAR_CLI_PUBKEY_AUTH
378
0
    if (opt == OPT_AUTHKEY) {
379
0
      TRACE(("opt authkey"))
380
0
      loadidentityfile(&argv[i][j], 1);
381
0
    }
382
0
    else
383
0
#endif
384
0
#if DROPBEAR_CLI_REMOTETCPFWD
385
0
    if (opt == OPT_REMOTETCPFWD) {
386
0
      TRACE(("opt remotetcpfwd"))
387
0
      addforward(&argv[i][j], cli_opts.remotefwds);
388
0
    }
389
0
    else
390
0
#endif
391
0
#if DROPBEAR_CLI_LOCALTCPFWD
392
0
    if (opt == OPT_LOCALTCPFWD) {
393
0
      TRACE(("opt localtcpfwd"))
394
0
      addforward(&argv[i][j], cli_opts.localfwds);
395
0
    }
396
0
    else
397
0
#endif
398
0
#if DROPBEAR_CLI_NETCAT
399
0
    if (opt == OPT_NETCAT) {
400
0
      TRACE(("opt netcat"))
401
0
      add_netcat(&argv[i][j]);
402
0
    }
403
0
    else
404
0
#endif
405
0
    if (next) {
406
      /* The previous flag set a value to assign */
407
0
      *next = &argv[i][j];
408
0
      if (*next == NULL)
409
0
        dropbear_exit("Invalid null argument");
410
0
      next = NULL;
411
0
    }
412
0
  }
413
414
1
#if DROPBEAR_USER_ALGO_LIST
415
  /* -c help doesn't need a hostname */
416
1
  parse_ciphers_macs();
417
1
#endif
418
419
1
  if (algo_print_arg) {
420
0
    print_algos(algo_print_arg);
421
    /* No return */
422
0
  }
423
424
1
  if (host_arg == NULL) { /* missing hostname */
425
0
    printhelp();
426
0
    dropbear_exit("Remote host needs to provided.");
427
0
  }
428
1
  TRACE(("host is: %s", host_arg))
429
430
1
#if DROPBEAR_USE_SSH_CONFIG
431
1
  apply_config_settings(host_arg);
432
1
#endif
433
434
  /* Apply needed defaults if missing from command line or config file. */
435
1
  if (remoteport_arg) {
436
0
    m_free(cli_opts.remoteport);
437
0
    cli_opts.remoteport = m_strdup(remoteport_arg);
438
1
  } else if (!cli_opts.remoteport) {
439
1
    cli_opts.remoteport = m_strdup("22");
440
1
  }
441
442
1
  if (username_arg) {
443
0
    m_free(cli_opts.username);
444
0
    cli_opts.username = m_strdup(username_arg);
445
1
  } else if(!cli_opts.username) {
446
1
    cli_opts.username = m_strdup(cli_opts.own_user);
447
1
  }
448
449
  /* Done with options/flags; now handle the hostname (which may not
450
   * start with a hyphen) and optional command */
451
452
1
  if (i < (unsigned int)argc) {
453
    /* Build the command to send */
454
0
    cmdlen = 0;
455
0
    for (j = i; j < (unsigned int)argc; j++)
456
0
      cmdlen += strlen(argv[j]) + 1; /* +1 for spaces */
457
458
    /* Allocate the space */
459
0
    cli_opts.cmd = (char*)m_malloc(cmdlen);
460
0
    cli_opts.cmd[0] = '\0';
461
462
    /* Append all the bits */
463
0
    for (j = i; j < (unsigned int)argc; j++) {
464
0
      strlcat(cli_opts.cmd, argv[j], cmdlen);
465
0
      strlcat(cli_opts.cmd, " ", cmdlen);
466
0
    }
467
    /* It'll be null-terminated here */
468
0
    TRACE(("cmd is: %s", cli_opts.cmd))
469
0
  }
470
471
  /* And now a few sanity checks and setup */
472
473
1
#if DROPBEAR_CLI_PROXYCMD
474
1
  if (proxycmd_arg) {
475
    /* To match the common path of m_freeing it */
476
0
    cli_opts.proxycmd = m_strdup(proxycmd_arg);
477
0
  }
478
1
#endif
479
480
1
  if (cli_opts.bind_arg) {
481
0
    if (split_address_port(cli_opts.bind_arg,
482
0
      &cli_opts.bind_address, &cli_opts.bind_port)
483
0
        == DROPBEAR_FAILURE) {
484
0
      dropbear_exit("Bad -b argument");
485
0
    }
486
0
  }
487
488
  /* If not explicitly specified with -t or -T, we don't want a pty if
489
   * there's a command, but we do otherwise */
490
1
  if (cli_opts.wantpty == 9) {
491
1
    if (cli_opts.cmd == NULL) {
492
1
      if (isatty(STDIN_FILENO)) {
493
0
        cli_opts.wantpty = 1;
494
1
      } else {
495
1
        TRACE(("Not a TTY"));
496
1
        cli_opts.wantpty = 0;
497
1
      }
498
1
    } else {
499
0
      cli_opts.wantpty = 0;
500
0
    }
501
1
  }
502
503
1
  if (cli_opts.backgrounded && cli_opts.cmd == NULL
504
0
      && cli_opts.no_cmd == 0) {
505
0
    dropbear_exit("Command required for -f");
506
0
  }
507
508
1
  if (recv_window_arg) {
509
0
    parse_recv_window(recv_window_arg);
510
0
  }
511
1
  if (cli_opts.keepalive_arg) {
512
0
    unsigned int val;
513
0
    if (m_str_to_uint(cli_opts.keepalive_arg, &val) == DROPBEAR_FAILURE) {
514
0
      dropbear_exit("Bad keepalive '%s'", cli_opts.keepalive_arg);
515
0
    }
516
0
    opts.keepalive_secs = val;
517
0
  }
518
519
1
  if (idle_timeout_arg) {
520
0
    unsigned int val;
521
0
    if (m_str_to_uint(idle_timeout_arg, &val) == DROPBEAR_FAILURE) {
522
0
      dropbear_exit("Bad idle_timeout '%s'", idle_timeout_arg);
523
0
    }
524
0
    opts.idle_timeout_secs = val;
525
0
  }
526
527
1
  if (max_duration_arg) {
528
0
    unsigned int val;
529
0
    if (m_str_to_uint(max_duration_arg, &val) == DROPBEAR_FAILURE) {
530
0
      dropbear_exit("Bad max_duration '%s'", max_duration_arg);
531
0
    }
532
0
    opts.max_duration_secs = val;
533
0
  }
534
535
1
#if DROPBEAR_CLI_NETCAT
536
1
  if (cli_opts.cmd && cli_opts.netcat_host) {
537
0
    dropbear_log(LOG_INFO, "Ignoring command '%s' in netcat mode", cli_opts.cmd);
538
0
  }
539
1
#endif
540
541
  /* The hostname gets set up last, since
542
   * in multi-hop mode it will require knowledge
543
   * of other flags such as -i */
544
1
#if DROPBEAR_CLI_MULTIHOP
545
1
  parse_multihop_hostname(host_arg, argv[0]);
546
#else
547
  parse_hostname(host_arg);
548
#endif
549
550
  /* We don't want to include default id_dropbear as a
551
     -i argument for multihop, so handle it later. */
552
1
#if (DROPBEAR_CLI_PUBKEY_AUTH)
553
1
  {
554
1
    loadidentityfile(DROPBEAR_DEFAULT_CLI_AUTHKEY, 0);
555
1
  }
556
1
#endif
557
1
}
558
559
#if DROPBEAR_CLI_PUBKEY_AUTH
560
1.55k
void loadidentityfile(const char* filename, int warnfail) {
561
1.55k
  sign_key *key;
562
1.55k
  enum signkey_type keytype;
563
564
1.55k
  char *id_key_path = expand_homedir_path(filename);
565
1.55k
  TRACE(("loadidentityfile %s", id_key_path))
566
567
1.55k
  key = new_sign_key();
568
1.55k
  keytype = DROPBEAR_SIGNKEY_ANY;
569
1.55k
  if ( readhostkey(id_key_path, key, &keytype) != DROPBEAR_SUCCESS ) {
570
1.55k
    if (warnfail) {
571
1.55k
      dropbear_log(LOG_WARNING, "Failed loading keyfile '%s'\n", id_key_path);
572
1.55k
    }
573
1.55k
    sign_key_free(key);
574
1.55k
    m_free(id_key_path);
575
1.55k
  } else {
576
0
    key->type = keytype;
577
0
    key->source = SIGNKEY_SOURCE_RAW_FILE;
578
0
    key->filename = id_key_path;
579
0
    list_append(cli_opts.privkeys, key);
580
0
  }
581
1.55k
}
582
#endif
583
584
#if DROPBEAR_CLI_MULTIHOP
585
586
/* Fill out -i, -y, -W options that make sense for all
587
 * the intermediate processes */
588
0
static char** multihop_args(const char* argv0, const char* prior_hops) {
589
  /* null terminated array */
590
0
  char **args = NULL;
591
0
  size_t max_args = 16, pos = 0, len;
592
0
#if DROPBEAR_CLI_PUBKEY_AUTH
593
0
  m_list_elem *iter;
594
0
#endif
595
596
0
#if DROPBEAR_CLI_PUBKEY_AUTH
597
0
  for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
598
0
  {
599
    /* "-i file" for each */
600
0
    max_args += 2;
601
0
  }
602
0
#endif
603
604
0
  args = m_malloc(sizeof(char*) * max_args);
605
0
  pos = 0;
606
607
0
  args[pos] = m_strdup(argv0);
608
0
  pos++;
609
610
0
  if (cli_opts.quiet) {
611
0
    args[pos] = m_strdup("-q");
612
0
    pos++;
613
0
  }
614
615
0
  if (cli_opts.no_hostkey_check) {
616
0
    args[pos] = m_strdup("-y");
617
0
    pos++;
618
0
    args[pos] = m_strdup("-y");
619
0
    pos++;
620
0
  } else if (cli_opts.always_accept_key) {
621
0
    args[pos] = m_strdup("-y");
622
0
    pos++;
623
0
  }
624
625
0
  if (cli_opts.batch_mode) {
626
0
    args[pos] = m_strdup("-o");
627
0
    pos++;
628
0
    args[pos] = m_strdup("BatchMode=yes");
629
0
    pos++;
630
0
  }
631
632
#ifndef DISABLE_ZLIB
633
  if (opts.compression) {
634
    args[pos] = m_strdup("-o");
635
    pos++;
636
    args[pos] = m_strdup("Compression=yes");
637
    pos++;
638
  }
639
#endif
640
641
0
  if (cli_opts.proxycmd) {
642
0
    args[pos] = m_strdup("-J");
643
0
    pos++;
644
0
    args[pos] = m_strdup(cli_opts.proxycmd);
645
0
    pos++;
646
0
  }
647
648
0
  if (opts.recv_window != DEFAULT_RECV_WINDOW) {
649
0
    args[pos] = m_strdup("-W");
650
0
    pos++;
651
0
    args[pos] = m_malloc(11);
652
0
    m_snprintf(args[pos], 11, "%u", opts.recv_window);
653
0
    pos++;
654
0
  }
655
656
0
#if DROPBEAR_CLI_PUBKEY_AUTH
657
0
  for (iter = cli_opts.privkeys->first; iter; iter = iter->next)
658
0
  {
659
0
    sign_key * key = (sign_key*)iter->item;
660
0
    args[pos] = m_strdup("-i");
661
0
    pos++;
662
0
    args[pos] = m_strdup(key->filename);
663
0
    pos++;
664
0
  }
665
0
#endif /* DROPBEAR_CLI_PUBKEY_AUTH */
666
667
  /* last hop */
668
0
  args[pos] = m_strdup("-B");
669
0
  pos++;
670
0
  len = strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) + 2;
671
0
  args[pos] = m_malloc(len);
672
0
  snprintf(args[pos], len, "%s:%s", cli_opts.remotehost, cli_opts.remoteport);
673
0
  pos++;
674
675
  /* hostnames of prior hops */
676
0
  args[pos] = m_strdup(prior_hops);
677
0
  pos++;
678
679
0
  return args;
680
0
}
681
682
/* Sets up 'onion-forwarding' connections. This will spawn
683
 * a separate dbclient process for each hop.
684
 * As an example, if the cmdline is
685
 *   dbclient wrt,madako,canyons
686
 * then we want to run:
687
 *   dbclient -J "dbclient -B canyons:22 wrt,madako" canyons
688
 * and then the inner dbclient will recursively run:
689
 *   dbclient -J "dbclient -B madako:22 wrt" madako
690
 * etc for as many hosts as we want.
691
 *
692
 * Note that "-J" arguments aren't actually used, instead
693
 * below sets cli_opts.proxyexec directly.
694
 *
695
 * Ports for hosts can be specified as host/port.
696
 */
697
1
static void parse_multihop_hostname(const char* orighostarg, const char* argv0) {
698
1
  char *userhostarg = NULL;
699
1
  char *hostbuf = NULL;
700
1
  char *last_hop = NULL;
701
1
  char *prior_hops = NULL;
702
703
  /* both scp and rsync parse a user@host argument
704
   * and turn it into "-l user host". This breaks
705
   * for our multihop syntax, so we suture it back together.
706
   * This will break usernames that have both '@' and ',' in them,
707
   * though that should be fairly uncommon. */
708
1
  if (cli_opts.username
709
1
      && strchr(cli_opts.username, ',')
710
0
      && strchr(cli_opts.username, '@')) {
711
0
    unsigned int len = strlen(orighostarg) + strlen(cli_opts.username) + 2;
712
0
    hostbuf = m_malloc(len);
713
0
    m_snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg);
714
1
  } else {
715
1
    hostbuf = m_strdup(orighostarg);
716
1
  }
717
1
  userhostarg = hostbuf;
718
719
  /* Split off any last hostname and use that as remotehost/remoteport.
720
   * That is used for authorized_keys checking etc */
721
1
  last_hop = strrchr(userhostarg, ',');
722
1
  if (last_hop) {
723
0
    if (last_hop == userhostarg) {
724
0
      dropbear_exit("Bad multi-hop hostnames");
725
0
    }
726
0
    *last_hop = '\0';
727
0
    last_hop++;
728
0
    prior_hops = userhostarg;
729
0
    userhostarg = last_hop;
730
0
  }
731
732
  /* Update cli_opts.remotehost and cli_opts.remoteport */
733
1
  parse_hostname(userhostarg);
734
735
  /* Construct any multihop proxy command. Use proxyexec to
736
   * avoid worrying about shell escaping. */
737
1
  if (prior_hops) {
738
0
    cli_opts.proxyexec = multihop_args(argv0, prior_hops);
739
    /* Any -J argument has been copied to proxyexec */
740
0
    if (cli_opts.proxycmd) {
741
0
      m_free(cli_opts.proxycmd);
742
0
    }
743
744
#ifndef DISABLE_ZLIB
745
    /* This outer stream will be incompressible since it's encrypted. */
746
    opts.compression = 0;
747
#endif
748
0
  }
749
750
1
  m_free(hostbuf);
751
1
}
752
#endif /* DROPBEAR_CLI_MULTIHOP */
753
754
/* Parses a [user@]hostname[/port] argument. */
755
1
static void parse_hostname(const char* orighostarg) {
756
1
  char *userhostarg = NULL;
757
1
  char *port = NULL;
758
1
  char* remotehost = NULL;
759
760
1
  userhostarg = m_strdup(orighostarg);
761
762
1
  remotehost = strchr(userhostarg, '@');
763
1
  if (remotehost == NULL) {
764
    /* no username portion, the cli-auth.c code can figure the
765
     * local user's name */
766
1
    remotehost = userhostarg;
767
1
  } else {
768
0
    remotehost[0] = '\0'; /* Split the user/host */
769
0
    remotehost++;
770
0
    cli_opts.username = m_strdup(userhostarg);
771
0
  }
772
773
1
  port = strchr(remotehost, '^');
774
1
  if (!port)  {
775
    /* legacy separator */
776
1
    port = strchr(remotehost, '/');
777
1
  }
778
1
  if (port) {
779
0
    *port = '\0';
780
0
    cli_opts.remoteport = m_strdup(port+1);
781
0
  }
782
783
1
  if (remotehost[0] == '\0') {
784
0
    dropbear_exit("Bad hostname.");
785
0
  }
786
787
1
  if (!cli_opts.remotehostfixed) {
788
1
    cli_opts.remotehost = m_strdup(remotehost);
789
1
  }
790
1
  m_free(userhostarg);
791
1
}
792
793
#if DROPBEAR_CLI_NETCAT
794
0
static void add_netcat(const char* origstr) {
795
0
  char *portstr = NULL;
796
797
0
  char * str = m_strdup(origstr);
798
799
0
  portstr = strchr(str, ':');
800
0
  if (portstr == NULL) {
801
0
    TRACE(("No netcat port"))
802
0
    goto fail;
803
0
  }
804
0
  *portstr = '\0';
805
0
  portstr++;
806
807
0
  if (strchr(portstr, ':')) {
808
0
    TRACE(("Multiple netcat colons"))
809
0
    goto fail;
810
0
  }
811
812
0
  if (m_str_to_uint(portstr, &cli_opts.netcat_port) == DROPBEAR_FAILURE) {
813
0
    TRACE(("bad netcat port"))
814
0
    goto fail;
815
0
  }
816
817
0
  if (cli_opts.netcat_port > 65535) {
818
0
    TRACE(("too large netcat port"))
819
0
    goto fail;
820
0
  }
821
822
0
  cli_opts.netcat_host = str;
823
0
  return;
824
825
0
fail:
826
0
  dropbear_exit("Bad netcat endpoint '%s'", origstr);
827
0
}
828
#endif
829
830
1
static void fill_own_user() {
831
1
  uid_t uid;
832
1
  struct passwd *pw = NULL;
833
834
1
  uid = getuid();
835
836
1
  pw = getpwuid(uid);
837
1
  if (pw && pw->pw_name != NULL) {
838
1
    cli_opts.own_user = m_strdup(pw->pw_name);
839
1
  } else {
840
0
    dropbear_log(LOG_INFO, "Warning: failed to identify current user. Trying anyway.");
841
0
    cli_opts.own_user = m_strdup("unknown");
842
0
  }
843
844
1
}
845
846
#if DROPBEAR_CLI_ANYTCPFWD
847
/* Turn a "[listenaddr:]listenport:remoteaddr:remoteport" string into into a forwarding
848
 * set, and add it to the forwarding list */
849
0
static void addforward(const char* origstr, m_list *fwdlist) {
850
851
0
  char *part1 = NULL, *part2 = NULL, *part3 = NULL, *part4 = NULL;
852
0
  char * listenaddr = NULL;
853
0
  char * listenport = NULL;
854
0
  char * connectaddr = NULL;
855
0
  char * connectport = NULL;
856
0
  struct TCPFwdEntry* newfwd = NULL;
857
0
  char * str = NULL;
858
859
0
  TRACE(("enter addforward"))
860
861
  /* We need to split the original argument up. This var
862
     is never free()d. */
863
0
  str = m_strdup(origstr);
864
865
0
  part1 = str;
866
867
0
  part2 = strchr(str, ':');
868
0
  if (part2 == NULL) {
869
0
    TRACE(("part2 == NULL"))
870
0
    goto fail;
871
0
  }
872
0
  *part2 = '\0';
873
0
  part2++;
874
875
0
  part3 = strchr(part2, ':');
876
0
  if (part3 == NULL) {
877
0
    TRACE(("part3 == NULL"))
878
0
    goto fail;
879
0
  }
880
0
  *part3 = '\0';
881
0
  part3++;
882
883
0
  part4 = strchr(part3, ':');
884
0
  if (part4) {
885
0
    *part4 = '\0';
886
0
    part4++;
887
0
  }
888
889
0
  if (part4) {
890
0
    listenaddr = part1;
891
0
    listenport = part2;
892
0
    connectaddr = part3;
893
0
    connectport = part4;
894
0
  } else {
895
0
    listenaddr = NULL;
896
0
    listenport = part1;
897
0
    connectaddr = part2;
898
0
    connectport = part3;
899
0
  }
900
901
0
  newfwd = m_malloc(sizeof(struct TCPFwdEntry));
902
903
  /* Now we check the ports - note that the port ints are unsigned,
904
   * the check later only checks for >= MAX_PORT */
905
0
  if (m_str_to_uint(listenport, &newfwd->listenport) == DROPBEAR_FAILURE) {
906
0
    TRACE(("bad listenport strtoul"))
907
0
    goto fail;
908
0
  }
909
910
0
  if (m_str_to_uint(connectport, &newfwd->connectport) == DROPBEAR_FAILURE) {
911
0
    TRACE(("bad connectport strtoul"))
912
0
    goto fail;
913
0
  }
914
915
0
  newfwd->listenaddr = listenaddr;
916
0
  newfwd->connectaddr = connectaddr;
917
918
0
  if (newfwd->listenport > 65535) {
919
0
    TRACE(("listenport > 65535"))
920
0
    goto badport;
921
0
  }
922
923
0
  if (newfwd->connectport > 65535) {
924
0
    TRACE(("connectport > 65535"))
925
0
    goto badport;
926
0
  }
927
928
0
  newfwd->have_reply = 0;
929
0
  list_append(fwdlist, newfwd);
930
931
0
  TRACE(("leave addforward: done"))
932
0
  return;
933
934
0
fail:
935
0
  dropbear_exit("Bad TCP forward '%s'", origstr);
936
937
0
badport:
938
0
  dropbear_exit("Bad TCP port in '%s'", origstr);
939
0
}
940
#endif
941
942
0
static int match_extendedopt(const char** strptr, const char *optname) {
943
0
  int seen_eq = 0;
944
0
  int optlen = strlen(optname);
945
0
  const char *str = *strptr;
946
947
0
  while (isspace(*str)) {
948
0
    ++str;
949
0
  }
950
951
0
  if (strncasecmp(str, optname, optlen) != 0) {
952
0
    return DROPBEAR_FAILURE;
953
0
  }
954
955
0
  str += optlen;
956
957
0
  while (isspace(*str) || (!seen_eq && *str == '=')) {
958
0
    if (*str == '=') {
959
0
      seen_eq = 1;
960
0
    }
961
0
    ++str;
962
0
  }
963
964
0
  if (str-*strptr == optlen) {
965
    /* matched just a prefix of optname */
966
0
    return DROPBEAR_FAILURE;
967
0
  }
968
969
0
  *strptr = str;
970
0
  return DROPBEAR_SUCCESS;
971
0
}
972
973
0
static int parse_flag_value(const char *value) {
974
0
  if (strcmp(value, "yes") == 0
975
0
    || strcmp(value, "y") == 0
976
0
    || strcmp(value, "true") == 0
977
0
    ) {
978
0
    return 1;
979
0
  } else if (strcmp(value, "no") == 0
980
0
    || strcmp(value, "n") == 0
981
0
    || strcmp(value, "false") == 0) {
982
0
    return 0;
983
0
  }
984
985
0
  dropbear_exit("Bad yes/no argument '%s'", value);
986
0
}
987
988
0
static void add_extendedopt(const char* origstr) {
989
0
  const char *optstr = origstr;
990
991
0
  if (strcmp(origstr, "help") == 0) {
992
0
    dropbear_log(LOG_INFO, "Available options:\n"
993
0
      "\tBatchMode\n"
994
0
      "\tBindAddress\n"
995
#ifndef DISABLE_ZLIB
996
      "\tCompression\n"
997
#endif
998
0
      "\tDisableTrivialAuth\n"
999
0
#if DROPBEAR_CLI_ANYTCPFWD
1000
0
      "\tExitOnForwardFailure\n"
1001
0
#endif
1002
0
#if DROPBEAR_CLI_AGENTFWD
1003
0
      "\tForwardAgent\n"
1004
0
#endif
1005
0
#if DROPBEAR_CLI_LOCALTCPFWD
1006
0
      "\tGatewayPorts\n"
1007
0
#endif
1008
0
#if DROPBEAR_CLI_PUBKEY_AUTH
1009
0
      "\tIdentityFile\n"
1010
0
#endif
1011
0
      "\tPasswordAuthentication\n"
1012
0
      "\tPort\n"
1013
0
#if DROPBEAR_CLI_PROXYCMD
1014
0
      "\tProxyCommand\n"
1015
0
#endif
1016
0
      "\tServerAliveInterval\n"
1017
0
      "\tStrictHostKeyChecking\n"
1018
0
#ifndef DISABLE_SYSLOG
1019
0
      "\tUseSyslog\n"
1020
0
#endif
1021
0
    );
1022
0
    exit(EXIT_SUCCESS);
1023
0
  }
1024
1025
0
  if (match_extendedopt(&optstr, "BatchMode") == DROPBEAR_SUCCESS) {
1026
0
    cli_opts.batch_mode = parse_flag_value(optstr);
1027
0
    return;
1028
0
  }
1029
1030
0
  if (match_extendedopt(&optstr, "BindAddress") == DROPBEAR_SUCCESS) {
1031
0
    cli_opts.bind_arg = optstr;
1032
0
    return;
1033
0
  }
1034
1035
0
  if (match_extendedopt(&optstr, "Compression") == DROPBEAR_SUCCESS) {
1036
0
    int flag = parse_flag_value(optstr);
1037
#ifndef DISABLE_ZLIB
1038
    /* Compression compiled in */
1039
    opts.compression = flag;
1040
#else
1041
0
    if (flag) {
1042
0
      dropbear_log(LOG_WARNING, "compression is not supported");
1043
0
    }
1044
0
#endif
1045
0
    return;
1046
0
  }
1047
1048
0
  if (match_extendedopt(&optstr, "DisableTrivialAuth") == DROPBEAR_SUCCESS) {
1049
0
    cli_opts.disable_trivial_auth = parse_flag_value(optstr);
1050
0
    return;
1051
0
  }
1052
1053
0
#if DROPBEAR_CLI_ANYTCPFWD
1054
0
  if (match_extendedopt(&optstr, "ExitOnForwardFailure") == DROPBEAR_SUCCESS) {
1055
0
    cli_opts.exit_on_fwd_failure = parse_flag_value(optstr);
1056
0
    return;
1057
0
  }
1058
0
#endif
1059
1060
0
#if DROPBEAR_CLI_AGENTFWD
1061
0
  if (match_extendedopt(&optstr, "ForwardAgent") == DROPBEAR_SUCCESS) {
1062
0
    cli_opts.agent_fwd = parse_flag_value(optstr);
1063
0
    return;
1064
0
  }
1065
0
#endif
1066
1067
0
#if DROPBEAR_CLI_LOCALTCPFWD
1068
0
  if (match_extendedopt(&optstr, "GatewayPorts") == DROPBEAR_SUCCESS) {
1069
0
    opts.listen_fwd_all = 1;
1070
0
    return;
1071
0
  }
1072
0
#endif
1073
1074
0
#if DROPBEAR_CLI_PUBKEY_AUTH
1075
0
  if (match_extendedopt(&optstr, "IdentityFile") == DROPBEAR_SUCCESS) {
1076
0
    loadidentityfile(optstr, 1);
1077
0
    return;
1078
0
  }
1079
0
#endif
1080
1081
0
#if DROPBEAR_CLI_PASSWORD_AUTH
1082
0
  if (match_extendedopt(&optstr, "PasswordAuthentication") == DROPBEAR_SUCCESS) {
1083
0
    cli_opts.password_authentication = parse_flag_value(optstr);
1084
0
    return;
1085
0
  }
1086
0
#endif
1087
1088
0
  if (match_extendedopt(&optstr, "BatchMode") == DROPBEAR_SUCCESS) {
1089
0
    cli_opts.batch_mode = parse_flag_value(optstr);
1090
0
    return;
1091
0
  }
1092
1093
0
  if (match_extendedopt(&optstr, "Port") == DROPBEAR_SUCCESS) {
1094
0
    cli_opts.remoteport = m_strdup(optstr);
1095
0
    return;
1096
0
  }
1097
1098
0
#if DROPBEAR_CLI_PROXYCMD
1099
0
  if (match_extendedopt(&optstr, "ProxyCommand") == DROPBEAR_SUCCESS) {
1100
0
    cli_opts.proxycmd = m_strdup(optstr);
1101
0
    return;
1102
0
  }
1103
0
#endif
1104
1105
0
  if (match_extendedopt(&optstr, "ServerAliveInterval") == DROPBEAR_SUCCESS) {
1106
0
    cli_opts.keepalive_arg = optstr;
1107
0
    return;
1108
0
  }
1109
1110
0
  if (match_extendedopt(&optstr, "StrictHostKeyChecking") == DROPBEAR_SUCCESS) {
1111
0
    if (strcmp(optstr, "accept-new") == 0) {
1112
0
      cli_opts.always_accept_key = 1;
1113
0
    } else if (strcmp(optstr, "ask") == 0) {
1114
      /* the default */
1115
0
    } else {
1116
0
      int opt = parse_flag_value(optstr);
1117
0
      if (opt) {
1118
        /* "yes" means entry must already exist in
1119
         * known_hosts for success. */
1120
0
        cli_opts.ask_hostkey = 0;
1121
0
      } else {
1122
        /* "no" means no check at all */
1123
0
        cli_opts.no_hostkey_check = 1;
1124
0
      }
1125
0
    }
1126
0
    return;
1127
0
  }
1128
1129
0
#ifndef DISABLE_SYSLOG
1130
0
  if (match_extendedopt(&optstr, "UseSyslog") == DROPBEAR_SUCCESS) {
1131
0
    opts.usingsyslog = parse_flag_value(optstr);
1132
0
    return;
1133
0
  }
1134
0
#endif
1135
1136
0
  dropbear_log(LOG_WARNING, "Ignoring unknown configuration option '%s'", origstr);
1137
0
}
1138
1139
#if DROPBEAR_USE_SSH_CONFIG
1140
1
static void apply_config_settings(const char* cli_host_arg) {
1141
1
  char* is_multi_hop_host_target = strchr(cli_host_arg, ',');
1142
1
  if (!is_multi_hop_host_target) {
1143
1
    char* config_path = expand_homedir_path(DROPBEAR_DEFAULT_SSH_CONFIG);
1144
1
    FILE* f;
1145
1
    if ((f = fopen(config_path, "r")) == NULL) {
1146
1
      DEBUG1(("Configuration file '%.200s' not found.", config_path));
1147
1
    }
1148
0
    else {
1149
0
      parse_hostname(cli_host_arg); /* Needed as key into the config. */
1150
0
      read_config_file(config_path, f, &cli_opts);
1151
0
      fclose(f);
1152
0
    }
1153
    m_free(config_path);
1154
1
  }
1155
1
}
1156
#endif