Coverage Report

Created: 2024-06-12 06:17

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