Coverage Report

Created: 2026-03-08 06:35

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