Coverage Report

Created: 2025-08-29 06:35

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