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
, int warnfail
);
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, default %s)\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"
95 "-v verbose (compiled with DEBUG_TRACE)\n"
97 ,DROPBEAR_VERSION
, cli_opts
.progname
,
98 #ifdef ENABLE_CLI_PUBKEY_AUTH
99 DROPBEAR_DEFAULT_CLI_AUTHKEY
,
101 DEFAULT_RECV_WINDOW
, DEFAULT_KEEPALIVE
, DEFAULT_IDLE_TIMEOUT
);
105 void cli_getopts(int argc
, char ** argv
) {
109 #ifdef ENABLE_CLI_PUBKEY_AUTH
110 int nextiskey
= 0; /* A flag if the next argument is a keyfile */
112 #ifdef ENABLE_CLI_LOCALTCPFWD
115 #ifdef ENABLE_CLI_REMOTETCPFWD
116 int nextisremote
= 0;
118 #ifdef ENABLE_CLI_NETCAT
119 int nextisnetcat
= 0;
121 char* dummy
= NULL
; /* Not used for anything real */
123 char* recv_window_arg
= NULL
;
124 char* keepalive_arg
= NULL
;
125 char* idle_timeout_arg
= NULL
;
126 char *host_arg
= NULL
;
128 /* see printhelp() for options */
129 cli_opts
.progname
= argv
[0];
130 cli_opts
.remotehost
= NULL
;
131 cli_opts
.remoteport
= NULL
;
132 cli_opts
.username
= NULL
;
135 cli_opts
.backgrounded
= 0;
136 cli_opts
.wantpty
= 9; /* 9 means "it hasn't been touched", gets set later */
137 cli_opts
.always_accept_key
= 0;
138 cli_opts
.no_hostkey_check
= 0;
139 cli_opts
.is_subsystem
= 0;
140 #ifdef ENABLE_CLI_PUBKEY_AUTH
141 cli_opts
.privkeys
= list_new();
143 #ifdef ENABLE_CLI_LOCALTCPFWD
144 cli_opts
.localfwds
= list_new();
145 opts
.listen_fwd_all
= 0;
147 #ifdef ENABLE_CLI_REMOTETCPFWD
148 cli_opts
.remotefwds
= list_new();
150 #ifdef ENABLE_CLI_AGENTFWD
151 cli_opts
.agent_fwd
= 0;
152 cli_opts
.agent_fd
= -1;
153 cli_opts
.agent_keys_loaded
= 0;
155 #ifdef ENABLE_CLI_PROXYCMD
156 cli_opts
.proxycmd
= NULL
;
159 opts
.compress_mode
= DROPBEAR_COMPRESS_ON
;
161 #ifdef ENABLE_USER_ALGO_LIST
162 opts
.cipher_list
= NULL
;
163 opts
.mac_list
= NULL
;
169 opts
.recv_window
= DEFAULT_RECV_WINDOW
;
170 opts
.keepalive_secs
= DEFAULT_KEEPALIVE
;
171 opts
.idle_timeout_secs
= DEFAULT_IDLE_TIMEOUT
;
175 /* Iterate all the arguments */
176 for (i
= 1; i
< (unsigned int)argc
; i
++) {
177 #ifdef ENABLE_CLI_PUBKEY_AUTH
179 /* Load a hostkey since the previous argument was "-i" */
180 loadidentityfile(argv
[i
], 1);
185 #ifdef ENABLE_CLI_REMOTETCPFWD
187 TRACE(("nextisremote true"))
188 addforward(argv
[i
], cli_opts
.remotefwds
);
193 #ifdef ENABLE_CLI_LOCALTCPFWD
195 TRACE(("nextislocal true"))
196 addforward(argv
[i
], cli_opts
.localfwds
);
201 #ifdef ENABLE_CLI_NETCAT
203 TRACE(("nextisnetcat true"))
210 /* The previous flag set a value to assign */
213 dropbear_exit("Invalid null argument");
219 if (argv
[i
][0] == '-') {
222 switch (argv
[i
][1]) {
223 case 'y': /* always accept the remote hostkey */
224 if (cli_opts
.always_accept_key
) {
225 /* twice means no checking at all */
226 cli_opts
.no_hostkey_check
= 1;
228 cli_opts
.always_accept_key
= 1;
230 case 'p': /* remoteport */
231 next
= &cli_opts
.remoteport
;
233 #ifdef ENABLE_CLI_PUBKEY_AUTH
234 case 'i': /* an identityfile */
235 /* Keep scp happy when it changes "-i file" to "-ifile" */
236 if (strlen(argv
[i
]) > 2) {
237 loadidentityfile(&argv
[i
][2], 1);
243 case 't': /* we want a pty */
244 cli_opts
.wantpty
= 1;
246 case 'T': /* don't want a pty */
247 cli_opts
.wantpty
= 0;
253 cli_opts
.backgrounded
= 1;
256 cli_opts
.is_subsystem
= 1;
258 #ifdef ENABLE_CLI_LOCALTCPFWD
263 opts
.listen_fwd_all
= 1;
266 #ifdef ENABLE_CLI_REMOTETCPFWD
271 #ifdef ENABLE_CLI_NETCAT
276 #ifdef ENABLE_CLI_PROXYCMD
278 next
= &cli_opts
.proxycmd
;
282 next
= &cli_opts
.username
;
289 /* backwards compatibility with old urandom option */
292 next
= &recv_window_arg
;
295 next
= &keepalive_arg
;
298 next
= &idle_timeout_arg
;
300 #ifdef ENABLE_CLI_AGENTFWD
302 cli_opts
.agent_fwd
= 1;
305 #ifdef ENABLE_USER_ALGO_LIST
307 next
= &opts
.cipher_list
;
310 next
= &opts
.mac_list
;
320 #ifndef ENABLE_USER_ALGO_LIST
325 #ifndef ENABLE_CLI_REMOTETCPFWD
328 #ifndef ENABLE_CLI_LOCALTCPFWD
340 "WARNING: Ignoring unknown argument '%s'\n", argv
[i
]);
344 /* Now we handle args where they might be "-luser" (no spaces)*/
345 if (next
&& strlen(argv
[i
]) > 2) {
350 continue; /* next argument */
353 TRACE(("non-flag arg: '%s'", argv
[i
]))
355 /* Either the hostname or commands */
357 if (host_arg
== NULL
) {
361 /* this is part of the commands to send - after this we
362 * don't parse any more options, and flags are sent as the
365 for (j
= i
; j
< (unsigned int)argc
; j
++) {
366 cmdlen
+= strlen(argv
[j
]) + 1; /* +1 for spaces */
368 /* Allocate the space */
369 cli_opts
.cmd
= (char*)m_malloc(cmdlen
);
370 cli_opts
.cmd
[0] = '\0';
372 /* Append all the bits */
373 for (j
= i
; j
< (unsigned int)argc
; j
++) {
374 strlcat(cli_opts
.cmd
, argv
[j
], cmdlen
);
375 strlcat(cli_opts
.cmd
, " ", cmdlen
);
377 /* It'll be null-terminated here */
379 /* We've eaten all the options and flags */
385 /* And now a few sanity checks and setup */
387 #ifdef ENABLE_USER_ALGO_LIST
388 parse_ciphers_macs();
391 if (host_arg
== NULL
) {
396 #ifdef ENABLE_CLI_PROXYCMD
397 if (cli_opts
.proxycmd
) {
398 /* To match the common path of m_freeing it */
399 cli_opts
.proxycmd
= m_strdup(cli_opts
.proxycmd
);
403 if (cli_opts
.remoteport
== NULL
) {
404 cli_opts
.remoteport
= "22";
407 /* If not explicitly specified with -t or -T, we don't want a pty if
408 * there's a command, but we do otherwise */
409 if (cli_opts
.wantpty
== 9) {
410 if (cli_opts
.cmd
== NULL
) {
411 cli_opts
.wantpty
= 1;
413 cli_opts
.wantpty
= 0;
417 if (cli_opts
.backgrounded
&& cli_opts
.cmd
== NULL
418 && cli_opts
.no_cmd
== 0) {
419 dropbear_exit("Command required for -f");
422 if (recv_window_arg
) {
423 opts
.recv_window
= atol(recv_window_arg
);
424 if (opts
.recv_window
== 0 || opts
.recv_window
> MAX_RECV_WINDOW
) {
425 dropbear_exit("Bad recv window '%s'", recv_window_arg
);
430 if (m_str_to_uint(keepalive_arg
, &val
) == DROPBEAR_FAILURE
) {
431 dropbear_exit("Bad keepalive '%s'", keepalive_arg
);
433 opts
.keepalive_secs
= val
;
436 if (idle_timeout_arg
) {
438 if (m_str_to_uint(idle_timeout_arg
, &val
) == DROPBEAR_FAILURE
) {
439 dropbear_exit("Bad idle_timeout '%s'", idle_timeout_arg
);
441 opts
.idle_timeout_secs
= val
;
444 #ifdef ENABLE_CLI_NETCAT
445 if (cli_opts
.cmd
&& cli_opts
.netcat_host
) {
446 dropbear_log(LOG_INFO
, "Ignoring command '%s' in netcat mode", cli_opts
.cmd
);
450 #ifdef DROPBEAR_DEFAULT_CLI_AUTHKEY
452 char *expand_path
= expand_tilde(DROPBEAR_DEFAULT_CLI_AUTHKEY
);
453 loadidentityfile(expand_path
, 0);
458 /* The hostname gets set up last, since
459 * in multi-hop mode it will require knowledge
460 * of other flags such as -i */
461 #ifdef ENABLE_CLI_MULTIHOP
462 parse_multihop_hostname(host_arg
, argv
[0]);
464 parse_hostname(host_arg
);
468 #ifdef ENABLE_CLI_PUBKEY_AUTH
469 static void loadidentityfile(const char* filename
, int warnfail
) {
471 enum signkey_type keytype
;
473 TRACE(("loadidentityfile %s", filename
))
475 key
= new_sign_key();
476 keytype
= DROPBEAR_SIGNKEY_ANY
;
477 if ( readhostkey(filename
, key
, &keytype
) != DROPBEAR_SUCCESS
) {
479 fprintf(stderr
, "Failed loading keyfile '%s'\n", filename
);
484 key
->source
= SIGNKEY_SOURCE_RAW_FILE
;
485 key
->filename
= m_strdup(filename
);
486 list_append(cli_opts
.privkeys
, key
);
491 #ifdef ENABLE_CLI_MULTIHOP
494 multihop_passthrough_args() {
497 unsigned int len
= 0;
499 /* Fill out -i, -y, -W options that make sense for all
500 * the intermediate processes */
501 for (iter
= cli_opts
.privkeys
->first
; iter
; iter
= iter
->next
)
503 sign_key
* key
= (sign_key
*)iter
->item
;
504 len
+= 3 + strlen(key
->filename
);
506 len
+= 30; /* space for -W <size>, terminator. */
510 if (cli_opts
.no_hostkey_check
)
512 int written
= snprintf(ret
+total
, len
-total
, "-y -y ");
515 else if (cli_opts
.always_accept_key
)
517 int written
= snprintf(ret
+total
, len
-total
, "-y ");
521 if (opts
.recv_window
!= DEFAULT_RECV_WINDOW
)
523 int written
= snprintf(ret
+total
, len
-total
, "-W %d ", opts
.recv_window
);
527 for (iter
= cli_opts
.privkeys
->first
; iter
; iter
= iter
->next
)
529 sign_key
* key
= (sign_key
*)iter
->item
;
530 const size_t size
= len
- total
;
531 int written
= snprintf(ret
+total
, size
, "-i %s ", key
->filename
);
532 dropbear_assert((unsigned int)written
< size
);
536 /* if args were passed, total will be not zero, and it will have a space at the end, so remove that */
545 /* Sets up 'onion-forwarding' connections. This will spawn
546 * a separate dbclient process for each hop.
547 * As an example, if the cmdline is
548 * dbclient wrt,madako,canyons
549 * then we want to run:
550 * dbclient -J "dbclient -B canyons:22 wrt,madako" canyons
551 * and then the inner dbclient will recursively run:
552 * dbclient -J "dbclient -B madako:22 wrt" madako
553 * etc for as many hosts as we want.
555 * Ports for hosts can be specified as host/port.
557 static void parse_multihop_hostname(const char* orighostarg
, const char* argv0
) {
558 char *userhostarg
= NULL
;
559 char *hostbuf
= NULL
;
560 char *last_hop
= NULL
;
561 char *remainder
= NULL
;
563 /* both scp and rsync parse a user@host argument
564 * and turn it into "-l user host". This breaks
565 * for our multihop syntax, so we suture it back together.
566 * This will break usernames that have both '@' and ',' in them,
567 * though that should be fairly uncommon. */
568 if (cli_opts
.username
569 && strchr(cli_opts
.username
, ',')
570 && strchr(cli_opts
.username
, '@')) {
571 unsigned int len
= strlen(orighostarg
) + strlen(cli_opts
.username
) + 2;
572 hostbuf
= m_malloc(len
);
573 snprintf(hostbuf
, len
, "%s@%s", cli_opts
.username
, orighostarg
);
575 hostbuf
= m_strdup(orighostarg
);
577 userhostarg
= hostbuf
;
579 last_hop
= strrchr(userhostarg
, ',');
581 if (last_hop
== userhostarg
) {
582 dropbear_exit("Bad multi-hop hostnames");
586 remainder
= userhostarg
;
587 userhostarg
= last_hop
;
590 parse_hostname(userhostarg
);
593 /* Set up the proxycmd */
594 unsigned int cmd_len
= 0;
595 char *passthrough_args
= multihop_passthrough_args();
596 if (cli_opts
.proxycmd
) {
597 dropbear_exit("-J can't be used with multihop mode");
599 if (cli_opts
.remoteport
== NULL
) {
600 cli_opts
.remoteport
= "22";
602 cmd_len
= strlen(argv0
) + strlen(remainder
)
603 + strlen(cli_opts
.remotehost
) + strlen(cli_opts
.remoteport
)
604 + strlen(passthrough_args
)
606 cli_opts
.proxycmd
= m_malloc(cmd_len
);
607 snprintf(cli_opts
.proxycmd
, cmd_len
, "%s -B %s:%s %s %s",
608 argv0
, cli_opts
.remotehost
, cli_opts
.remoteport
,
609 passthrough_args
, remainder
);
611 /* The stream will be incompressible since it's encrypted. */
612 opts
.compress_mode
= DROPBEAR_COMPRESS_OFF
;
614 m_free(passthrough_args
);
618 #endif /* !ENABLE_CLI_MULTIHOP */
620 /* Parses a [user@]hostname[/port] argument. */
621 static void parse_hostname(const char* orighostarg
) {
622 char *userhostarg
= NULL
;
625 userhostarg
= m_strdup(orighostarg
);
627 cli_opts
.remotehost
= strchr(userhostarg
, '@');
628 if (cli_opts
.remotehost
== NULL
) {
629 /* no username portion, the cli-auth.c code can figure the
630 * local user's name */
631 cli_opts
.remotehost
= userhostarg
;
633 cli_opts
.remotehost
[0] = '\0'; /* Split the user/host */
634 cli_opts
.remotehost
++;
635 cli_opts
.username
= userhostarg
;
638 if (cli_opts
.username
== NULL
) {
639 cli_opts
.username
= m_strdup(cli_opts
.own_user
);
642 port
= strchr(cli_opts
.remotehost
, '^');
644 /* legacy separator */
645 port
= strchr(cli_opts
.remotehost
, '/');
649 cli_opts
.remoteport
= port
+1;
652 if (cli_opts
.remotehost
[0] == '\0') {
653 dropbear_exit("Bad hostname");
657 #ifdef ENABLE_CLI_NETCAT
658 static void add_netcat(const char* origstr
) {
659 char *portstr
= NULL
;
661 char * str
= m_strdup(origstr
);
663 portstr
= strchr(str
, ':');
664 if (portstr
== NULL
) {
665 TRACE(("No netcat port"))
671 if (strchr(portstr
, ':')) {
672 TRACE(("Multiple netcat colons"))
676 if (m_str_to_uint(portstr
, &cli_opts
.netcat_port
) == DROPBEAR_FAILURE
) {
677 TRACE(("bad netcat port"))
681 if (cli_opts
.netcat_port
> 65535) {
682 TRACE(("too large netcat port"))
686 cli_opts
.netcat_host
= str
;
690 dropbear_exit("Bad netcat endpoint '%s'", origstr
);
694 static void fill_own_user() {
696 struct passwd
*pw
= NULL
;
701 if (pw
&& pw
->pw_name
!= NULL
) {
702 cli_opts
.own_user
= m_strdup(pw
->pw_name
);
704 dropbear_log(LOG_INFO
, "Warning: failed to identify current user. Trying anyway.");
705 cli_opts
.own_user
= m_strdup("unknown");
710 #ifdef ENABLE_CLI_ANYTCPFWD
711 /* Turn a "[listenaddr:]listenport:remoteaddr:remoteport" string into into a forwarding
712 * set, and add it to the forwarding list */
713 static void addforward(const char* origstr
, m_list
*fwdlist
) {
715 char *part1
= NULL
, *part2
= NULL
, *part3
= NULL
, *part4
= NULL
;
716 char * listenaddr
= NULL
;
717 char * listenport
= NULL
;
718 char * connectaddr
= NULL
;
719 char * connectport
= NULL
;
720 struct TCPFwdEntry
* newfwd
= NULL
;
723 TRACE(("enter addforward"))
725 /* We need to split the original argument up. This var
727 str
= m_strdup(origstr
);
731 part2
= strchr(str
, ':');
733 TRACE(("part2 == NULL"))
739 part3
= strchr(part2
, ':');
741 TRACE(("part3 == NULL"))
747 part4
= strchr(part3
, ':');
765 newfwd
= m_malloc(sizeof(struct TCPFwdEntry
));
767 /* Now we check the ports - note that the port ints are unsigned,
768 * the check later only checks for >= MAX_PORT */
769 if (m_str_to_uint(listenport
, &newfwd
->listenport
) == DROPBEAR_FAILURE
) {
770 TRACE(("bad listenport strtoul"))
774 if (m_str_to_uint(connectport
, &newfwd
->connectport
) == DROPBEAR_FAILURE
) {
775 TRACE(("bad connectport strtoul"))
779 newfwd
->listenaddr
= listenaddr
;
780 newfwd
->connectaddr
= connectaddr
;
782 if (newfwd
->listenport
> 65535) {
783 TRACE(("listenport > 65535"))
787 if (newfwd
->connectport
> 65535) {
788 TRACE(("connectport > 65535"))
792 newfwd
->have_reply
= 0;
793 list_append(fwdlist
, newfwd
);
795 TRACE(("leave addforward: done"))
799 dropbear_exit("Bad TCP forward '%s'", origstr
);
802 dropbear_exit("Bad TCP port in '%s'", origstr
);