2 * Dropbear - a SSH2 server
4 * Copyright (c) 2002,2003 Matt Johnston
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:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
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
34 cli_runopts cli_opts
; /* GLOBAL */
36 static void printhelp();
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();
40 #ifdef ENABLE_CLI_PUBKEY_AUTH
41 static void loadidentityfile(const char* filename
);
43 #ifdef ENABLE_CLI_ANYTCPFWD
44 static void addforward(const char* str
, m_list
*fwdlist
);
46 #ifdef ENABLE_CLI_NETCAT
47 static void add_netcat(const char *str
);
50 static void printhelp() {
52 fprintf(stderr
, "Dropbear SSH client v%s https://matt.ucc.asn.au/dropbear/dropbear.html\n"
53 #ifdef ENABLE_CLI_MULTIHOP
54 "Usage: %s [options] [user@]host[/port][,[user@]host/port],...] [command]\n"
56 "Usage: %s [options] [user@]host[/port] [command]\n"
61 "-T Don't allocate a pty\n"
62 "-N Don't run a remote command\n"
63 "-f Run in background after auth\n"
64 "-y Always accept remote host key if unknown\n"
65 "-y -y Don't perform any remote host key checking (caution)\n"
66 "-s Request a subsystem (use by external sftp)\n"
67 #ifdef ENABLE_CLI_PUBKEY_AUTH
68 "-i <identityfile> (multiple allowed)\n"
70 #ifdef ENABLE_CLI_AGENTFWD
71 "-A Enable agent auth forwarding\n"
73 #ifdef ENABLE_CLI_LOCALTCPFWD
74 "-L <[listenaddress:]listenport:remotehost:remoteport> Local port forwarding\n"
75 "-g Allow remote hosts to connect to forwarded ports\n"
77 #ifdef ENABLE_CLI_REMOTETCPFWD
78 "-R <[listenaddress:]listenport:remotehost:remoteport> Remote port forwarding\n"
80 "-W <receive_window_buffer> (default %d, larger may be faster, max 1MB)\n"
81 "-K <keepalive> (0 is never, default %d)\n"
82 "-I <idle_timeout> (0 is never, default %d)\n"
83 #ifdef ENABLE_CLI_NETCAT
84 "-B <endhost:endport> Netcat-alike forwarding\n"
86 #ifdef ENABLE_CLI_PROXYCMD
87 "-J <proxy_program> Use program pipe rather than TCP connection\n"
89 #ifdef ENABLE_USER_ALGO_LIST
90 "-c <cipher list> Specify preferred ciphers ('-c help' to list options)\n"
91 "-m <MAC list> Specify preferred MACs for packet verification (or '-m help')\n"
94 "-v verbose (compiled with DEBUG_TRACE)\n"
96 ,DROPBEAR_VERSION
, cli_opts
.progname
,
97 DEFAULT_RECV_WINDOW
, DEFAULT_KEEPALIVE
, DEFAULT_IDLE_TIMEOUT
);
101 void cli_getopts(int argc
, char ** argv
) {
105 #ifdef ENABLE_CLI_PUBKEY_AUTH
106 int nextiskey
= 0; /* A flag if the next argument is a keyfile */
108 #ifdef ENABLE_CLI_LOCALTCPFWD
111 #ifdef ENABLE_CLI_REMOTETCPFWD
112 int nextisremote
= 0;
114 #ifdef ENABLE_CLI_NETCAT
115 int nextisnetcat
= 0;
117 char* dummy
= NULL
; /* Not used for anything real */
119 char* recv_window_arg
= NULL
;
120 char* keepalive_arg
= NULL
;
121 char* idle_timeout_arg
= NULL
;
122 char *host_arg
= NULL
;
124 /* see printhelp() for options */
125 cli_opts
.progname
= argv
[0];
126 cli_opts
.remotehost
= NULL
;
127 cli_opts
.remoteport
= NULL
;
128 cli_opts
.username
= NULL
;
131 cli_opts
.backgrounded
= 0;
132 cli_opts
.wantpty
= 9; /* 9 means "it hasn't been touched", gets set later */
133 cli_opts
.always_accept_key
= 0;
134 cli_opts
.no_hostkey_check
= 0;
135 cli_opts
.is_subsystem
= 0;
136 #ifdef ENABLE_CLI_PUBKEY_AUTH
137 cli_opts
.privkeys
= list_new();
139 #ifdef ENABLE_CLI_LOCALTCPFWD
140 cli_opts
.localfwds
= list_new();
141 opts
.listen_fwd_all
= 0;
143 #ifdef ENABLE_CLI_REMOTETCPFWD
144 cli_opts
.remotefwds
= list_new();
146 #ifdef ENABLE_CLI_AGENTFWD
147 cli_opts
.agent_fwd
= 0;
148 cli_opts
.agent_fd
= -1;
149 cli_opts
.agent_keys_loaded
= 0;
151 #ifdef ENABLE_CLI_PROXYCMD
152 cli_opts
.proxycmd
= NULL
;
155 opts
.enable_compress
= 1;
157 #ifdef ENABLE_USER_ALGO_LIST
158 opts
.cipher_list
= NULL
;
159 opts
.mac_list
= NULL
;
165 opts
.recv_window
= DEFAULT_RECV_WINDOW
;
169 /* Iterate all the arguments */
170 for (i
= 1; i
< (unsigned int)argc
; i
++) {
171 #ifdef ENABLE_CLI_PUBKEY_AUTH
173 /* Load a hostkey since the previous argument was "-i" */
174 loadidentityfile(argv
[i
]);
179 #ifdef ENABLE_CLI_REMOTETCPFWD
181 TRACE(("nextisremote true"))
182 addforward(argv
[i
], cli_opts
.remotefwds
);
187 #ifdef ENABLE_CLI_LOCALTCPFWD
189 TRACE(("nextislocal true"))
190 addforward(argv
[i
], cli_opts
.localfwds
);
195 #ifdef ENABLE_CLI_NETCAT
197 TRACE(("nextisnetcat true"))
204 /* The previous flag set a value to assign */
207 dropbear_exit("Invalid null argument");
213 if (argv
[i
][0] == '-') {
216 switch (argv
[i
][1]) {
217 case 'y': /* always accept the remote hostkey */
218 if (cli_opts
.always_accept_key
) {
219 /* twice means no checking at all */
220 cli_opts
.no_hostkey_check
= 1;
222 cli_opts
.always_accept_key
= 1;
224 case 'p': /* remoteport */
225 next
= &cli_opts
.remoteport
;
227 #ifdef ENABLE_CLI_PUBKEY_AUTH
228 case 'i': /* an identityfile */
229 /* Keep scp happy when it changes "-i file" to "-ifile" */
230 if (strlen(argv
[i
]) > 2) {
231 loadidentityfile(&argv
[i
][2]);
237 case 't': /* we want a pty */
238 cli_opts
.wantpty
= 1;
240 case 'T': /* don't want a pty */
241 cli_opts
.wantpty
= 0;
247 cli_opts
.backgrounded
= 1;
250 cli_opts
.is_subsystem
= 1;
252 #ifdef ENABLE_CLI_LOCALTCPFWD
257 opts
.listen_fwd_all
= 1;
260 #ifdef ENABLE_CLI_REMOTETCPFWD
265 #ifdef ENABLE_CLI_NETCAT
270 #ifdef ENABLE_CLI_PROXYCMD
272 next
= &cli_opts
.proxycmd
;
276 next
= &cli_opts
.username
;
283 /* backwards compatibility with old urandom option */
286 next
= &recv_window_arg
;
289 next
= &keepalive_arg
;
292 next
= &idle_timeout_arg
;
294 #ifdef ENABLE_CLI_AGENTFWD
296 cli_opts
.agent_fwd
= 1;
299 #ifdef ENABLE_USER_ALGO_LIST
301 next
= &opts
.cipher_list
;
304 next
= &opts
.mac_list
;
314 #ifndef ENABLE_USER_ALGO_LIST
319 #ifndef ENABLE_CLI_REMOTETCPFWD
322 #ifndef ENABLE_CLI_LOCALTCPFWD
330 "WARNING: Ignoring unknown argument '%s'\n", argv
[i
]);
334 /* Now we handle args where they might be "-luser" (no spaces)*/
335 if (next
&& strlen(argv
[i
]) > 2) {
340 continue; /* next argument */
343 TRACE(("non-flag arg: '%s'", argv
[i
]))
345 /* Either the hostname or commands */
347 if (host_arg
== NULL
) {
351 /* this is part of the commands to send - after this we
352 * don't parse any more options, and flags are sent as the
355 for (j
= i
; j
< (unsigned int)argc
; j
++) {
356 cmdlen
+= strlen(argv
[j
]) + 1; /* +1 for spaces */
358 /* Allocate the space */
359 cli_opts
.cmd
= (char*)m_malloc(cmdlen
);
360 cli_opts
.cmd
[0] = '\0';
362 /* Append all the bits */
363 for (j
= i
; j
< (unsigned int)argc
; j
++) {
364 strlcat(cli_opts
.cmd
, argv
[j
], cmdlen
);
365 strlcat(cli_opts
.cmd
, " ", cmdlen
);
367 /* It'll be null-terminated here */
369 /* We've eaten all the options and flags */
375 /* And now a few sanity checks and setup */
377 #ifdef ENABLE_USER_ALGO_LIST
378 parse_ciphers_macs();
381 if (host_arg
== NULL
) {
386 #ifdef ENABLE_CLI_PROXYCMD
387 if (cli_opts
.proxycmd
) {
388 /* To match the common path of m_freeing it */
389 cli_opts
.proxycmd
= m_strdup(cli_opts
.proxycmd
);
393 if (cli_opts
.remoteport
== NULL
) {
394 cli_opts
.remoteport
= "22";
397 /* If not explicitly specified with -t or -T, we don't want a pty if
398 * there's a command, but we do otherwise */
399 if (cli_opts
.wantpty
== 9) {
400 if (cli_opts
.cmd
== NULL
) {
401 cli_opts
.wantpty
= 1;
403 cli_opts
.wantpty
= 0;
407 if (cli_opts
.backgrounded
&& cli_opts
.cmd
== NULL
408 && cli_opts
.no_cmd
== 0) {
409 dropbear_exit("Command required for -f");
412 if (recv_window_arg
) {
413 opts
.recv_window
= atol(recv_window_arg
);
414 if (opts
.recv_window
== 0 || opts
.recv_window
> MAX_RECV_WINDOW
) {
415 dropbear_exit("Bad recv window '%s'", recv_window_arg
);
420 if (m_str_to_uint(keepalive_arg
, &val
) == DROPBEAR_FAILURE
) {
421 dropbear_exit("Bad keepalive '%s'", keepalive_arg
);
423 opts
.keepalive_secs
= val
;
426 if (idle_timeout_arg
) {
428 if (m_str_to_uint(idle_timeout_arg
, &val
) == DROPBEAR_FAILURE
) {
429 dropbear_exit("Bad idle_timeout '%s'", idle_timeout_arg
);
431 opts
.idle_timeout_secs
= val
;
434 #ifdef ENABLE_CLI_NETCAT
435 if (cli_opts
.cmd
&& cli_opts
.netcat_host
) {
436 dropbear_log(LOG_INFO
, "Ignoring command '%s' in netcat mode", cli_opts
.cmd
);
440 /* The hostname gets set up last, since
441 * in multi-hop mode it will require knowledge
442 * of other flags such as -i */
443 #ifdef ENABLE_CLI_MULTIHOP
444 parse_multihop_hostname(host_arg
, argv
[0]);
446 parse_hostname(host_arg
);
450 #ifdef ENABLE_CLI_PUBKEY_AUTH
451 static void loadidentityfile(const char* filename
) {
453 enum signkey_type keytype
;
455 key
= new_sign_key();
456 keytype
= DROPBEAR_SIGNKEY_ANY
;
457 if ( readhostkey(filename
, key
, &keytype
) != DROPBEAR_SUCCESS
) {
458 fprintf(stderr
, "Failed loading keyfile '%s'\n", filename
);
462 key
->source
= SIGNKEY_SOURCE_RAW_FILE
;
463 key
->filename
= m_strdup(filename
);
464 list_append(cli_opts
.privkeys
, key
);
469 #ifdef ENABLE_CLI_MULTIHOP
472 multihop_passthrough_args() {
475 unsigned int len
= 0;
477 /* Fill out -i, -y, -W options that make sense for all
478 * the intermediate processes */
479 for (iter
= cli_opts
.privkeys
->first
; iter
; iter
= iter
->next
)
481 sign_key
* key
= (sign_key
*)iter
->item
;
482 len
+= 3 + strlen(key
->filename
);
484 len
+= 30; /* space for -W <size>, terminator. */
488 if (cli_opts
.no_hostkey_check
)
490 int written
= snprintf(ret
+total
, len
-total
, "-y -y ");
493 else if (cli_opts
.always_accept_key
)
495 int written
= snprintf(ret
+total
, len
-total
, "-y ");
499 if (opts
.recv_window
!= DEFAULT_RECV_WINDOW
)
501 int written
= snprintf(ret
+total
, len
-total
, "-W %d ", opts
.recv_window
);
505 for (iter
= cli_opts
.privkeys
->first
; iter
; iter
= iter
->next
)
507 sign_key
* key
= (sign_key
*)iter
->item
;
508 const size_t size
= len
- total
;
509 int written
= snprintf(ret
+total
, size
, "-i %s ", key
->filename
);
510 dropbear_assert((unsigned int)written
< size
);
514 /* if args were passed, total will be not zero, and it will have a space at the end, so remove that */
523 /* Sets up 'onion-forwarding' connections. This will spawn
524 * a separate dbclient process for each hop.
525 * As an example, if the cmdline is
526 * dbclient wrt,madako,canyons
527 * then we want to run:
528 * dbclient -J "dbclient -B canyons:22 wrt,madako" canyons
529 * and then the inner dbclient will recursively run:
530 * dbclient -J "dbclient -B madako:22 wrt" madako
531 * etc for as many hosts as we want.
533 * Ports for hosts can be specified as host/port.
535 static void parse_multihop_hostname(const char* orighostarg
, const char* argv0
) {
536 char *userhostarg
= NULL
;
537 char *hostbuf
= NULL
;
538 char *last_hop
= NULL
;
539 char *remainder
= NULL
;
541 /* both scp and rsync parse a user@host argument
542 * and turn it into "-l user host". This breaks
543 * for our multihop syntax, so we suture it back together.
544 * This will break usernames that have both '@' and ',' in them,
545 * though that should be fairly uncommon. */
546 if (cli_opts
.username
547 && strchr(cli_opts
.username
, ',')
548 && strchr(cli_opts
.username
, '@')) {
549 unsigned int len
= strlen(orighostarg
) + strlen(cli_opts
.username
) + 2;
550 hostbuf
= m_malloc(len
);
551 snprintf(hostbuf
, len
, "%s@%s", cli_opts
.username
, orighostarg
);
553 hostbuf
= m_strdup(orighostarg
);
555 userhostarg
= hostbuf
;
557 last_hop
= strrchr(userhostarg
, ',');
559 if (last_hop
== userhostarg
) {
560 dropbear_exit("Bad multi-hop hostnames");
564 remainder
= userhostarg
;
565 userhostarg
= last_hop
;
568 parse_hostname(userhostarg
);
571 /* Set up the proxycmd */
572 unsigned int cmd_len
= 0;
573 char *passthrough_args
= multihop_passthrough_args();
574 if (cli_opts
.proxycmd
) {
575 dropbear_exit("-J can't be used with multihop mode");
577 if (cli_opts
.remoteport
== NULL
) {
578 cli_opts
.remoteport
= "22";
580 cmd_len
= strlen(argv0
) + strlen(remainder
)
581 + strlen(cli_opts
.remotehost
) + strlen(cli_opts
.remoteport
)
582 + strlen(passthrough_args
)
584 cli_opts
.proxycmd
= m_malloc(cmd_len
);
585 snprintf(cli_opts
.proxycmd
, cmd_len
, "%s -B %s:%s %s %s",
586 argv0
, cli_opts
.remotehost
, cli_opts
.remoteport
,
587 passthrough_args
, remainder
);
589 /* The stream will be incompressible since it's encrypted. */
590 opts
.enable_compress
= 0;
592 m_free(passthrough_args
);
596 #endif /* !ENABLE_CLI_MULTIHOP */
598 /* Parses a [user@]hostname[/port] argument. */
599 static void parse_hostname(const char* orighostarg
) {
600 char *userhostarg
= NULL
;
603 userhostarg
= m_strdup(orighostarg
);
605 cli_opts
.remotehost
= strchr(userhostarg
, '@');
606 if (cli_opts
.remotehost
== NULL
) {
607 /* no username portion, the cli-auth.c code can figure the
608 * local user's name */
609 cli_opts
.remotehost
= userhostarg
;
611 cli_opts
.remotehost
[0] = '\0'; /* Split the user/host */
612 cli_opts
.remotehost
++;
613 cli_opts
.username
= userhostarg
;
616 if (cli_opts
.username
== NULL
) {
617 cli_opts
.username
= m_strdup(cli_opts
.own_user
);
620 port
= strchr(cli_opts
.remotehost
, '%');
622 /* legacy separator */
623 port
= strchr(cli_opts
.remotehost
, '/');
627 cli_opts
.remoteport
= port
+1;
630 if (cli_opts
.remotehost
[0] == '\0') {
631 dropbear_exit("Bad hostname");
635 #ifdef ENABLE_CLI_NETCAT
636 static void add_netcat(const char* origstr
) {
637 char *portstr
= NULL
;
639 char * str
= m_strdup(origstr
);
641 portstr
= strchr(str
, ':');
642 if (portstr
== NULL
) {
643 TRACE(("No netcat port"))
649 if (strchr(portstr
, ':')) {
650 TRACE(("Multiple netcat colons"))
654 if (m_str_to_uint(portstr
, &cli_opts
.netcat_port
) == DROPBEAR_FAILURE
) {
655 TRACE(("bad netcat port"))
659 if (cli_opts
.netcat_port
> 65535) {
660 TRACE(("too large netcat port"))
664 cli_opts
.netcat_host
= str
;
668 dropbear_exit("Bad netcat endpoint '%s'", origstr
);
672 static void fill_own_user() {
674 struct passwd
*pw
= NULL
;
679 if (pw
== NULL
|| pw
->pw_name
== NULL
) {
680 dropbear_exit("Unknown own user");
683 cli_opts
.own_user
= m_strdup(pw
->pw_name
);
686 #ifdef ENABLE_CLI_ANYTCPFWD
687 /* Turn a "[listenaddr:]listenport:remoteaddr:remoteport" string into into a forwarding
688 * set, and add it to the forwarding list */
689 static void addforward(const char* origstr
, m_list
*fwdlist
) {
691 char *part1
= NULL
, *part2
= NULL
, *part3
= NULL
, *part4
= NULL
;
692 char * listenaddr
= NULL
;
693 char * listenport
= NULL
;
694 char * connectaddr
= NULL
;
695 char * connectport
= NULL
;
696 struct TCPFwdEntry
* newfwd
= NULL
;
699 TRACE(("enter addforward"))
701 /* We need to split the original argument up. This var
703 str
= m_strdup(origstr
);
707 part2
= strchr(str
, ':');
709 TRACE(("part2 == NULL"))
715 part3
= strchr(part2
, ':');
717 TRACE(("part3 == NULL"))
723 part4
= strchr(part3
, ':');
741 newfwd
= m_malloc(sizeof(struct TCPFwdEntry
));
743 /* Now we check the ports - note that the port ints are unsigned,
744 * the check later only checks for >= MAX_PORT */
745 if (m_str_to_uint(listenport
, &newfwd
->listenport
) == DROPBEAR_FAILURE
) {
746 TRACE(("bad listenport strtoul"))
750 if (m_str_to_uint(connectport
, &newfwd
->connectport
) == DROPBEAR_FAILURE
) {
751 TRACE(("bad connectport strtoul"))
755 newfwd
->listenaddr
= listenaddr
;
756 newfwd
->connectaddr
= connectaddr
;
758 if (newfwd
->listenport
> 65535) {
759 TRACE(("listenport > 65535"))
763 if (newfwd
->connectport
> 65535) {
764 TRACE(("connectport > 65535"))
768 newfwd
->have_reply
= 0;
769 list_append(fwdlist
, newfwd
);
771 TRACE(("leave addforward: done"))
775 dropbear_exit("Bad TCP forward '%s'", origstr
);
778 dropbear_exit("Bad TCP port in '%s'", origstr
);