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 client v%s\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"
62 "-T Don't allocate a pty\n"
63 "-N Don't run a remote command\n"
64 "-f Run in background after auth\n"
65 "-y Always accept remote host key if unknown\n"
66 "-s Request a subsystem (use for 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"
90 "-v verbose (compiled with DEBUG_TRACE)\n"
92 ,DROPBEAR_VERSION
, cli_opts
.progname
,
93 DEFAULT_RECV_WINDOW
, DEFAULT_KEEPALIVE
, DEFAULT_IDLE_TIMEOUT
);
97 void cli_getopts(int argc
, char ** argv
) {
101 #ifdef ENABLE_CLI_PUBKEY_AUTH
102 int nextiskey
= 0; /* A flag if the next argument is a keyfile */
104 #ifdef ENABLE_CLI_LOCALTCPFWD
107 #ifdef ENABLE_CLI_REMOTETCPFWD
108 int nextisremote
= 0;
110 #ifdef ENABLE_CLI_NETCAT
111 int nextisnetcat
= 0;
113 char* dummy
= NULL
; /* Not used for anything real */
115 char* recv_window_arg
= NULL
;
116 char* keepalive_arg
= NULL
;
117 char* idle_timeout_arg
= NULL
;
118 char *host_arg
= NULL
;
120 /* see printhelp() for options */
121 cli_opts
.progname
= argv
[0];
122 cli_opts
.remotehost
= NULL
;
123 cli_opts
.remoteport
= NULL
;
124 cli_opts
.username
= NULL
;
127 cli_opts
.backgrounded
= 0;
128 cli_opts
.wantpty
= 9; /* 9 means "it hasn't been touched", gets set later */
129 cli_opts
.always_accept_key
= 0;
130 cli_opts
.is_subsystem
= 0;
131 #ifdef ENABLE_CLI_PUBKEY_AUTH
132 cli_opts
.privkeys
= list_new();
134 #ifdef ENABLE_CLI_LOCALTCPFWD
135 cli_opts
.localfwds
= list_new();
136 opts
.listen_fwd_all
= 0;
138 #ifdef ENABLE_CLI_REMOTETCPFWD
139 cli_opts
.remotefwds
= list_new();
141 #ifdef ENABLE_CLI_AGENTFWD
142 cli_opts
.agent_fwd
= 0;
143 cli_opts
.agent_keys_loaded
= 0;
145 #ifdef ENABLE_CLI_PROXYCMD
146 cli_opts
.proxycmd
= NULL
;
149 opts
.enable_compress
= 1;
155 opts
.recv_window
= DEFAULT_RECV_WINDOW
;
159 /* Iterate all the arguments */
160 for (i
= 1; i
< (unsigned int)argc
; i
++) {
161 #ifdef ENABLE_CLI_PUBKEY_AUTH
163 /* Load a hostkey since the previous argument was "-i" */
164 loadidentityfile(argv
[i
]);
169 #ifdef ENABLE_CLI_REMOTETCPFWD
171 TRACE(("nextisremote true"))
172 addforward(argv
[i
], cli_opts
.remotefwds
);
177 #ifdef ENABLE_CLI_LOCALTCPFWD
179 TRACE(("nextislocal true"))
180 addforward(argv
[i
], cli_opts
.localfwds
);
185 #ifdef ENABLE_CLI_NETCAT
187 TRACE(("nextisnetcat true"))
194 /* The previous flag set a value to assign */
197 dropbear_exit("Invalid null argument");
203 if (argv
[i
][0] == '-') {
206 switch (argv
[i
][1]) {
207 case 'y': /* always accept the remote hostkey */
208 cli_opts
.always_accept_key
= 1;
210 case 'p': /* remoteport */
211 next
= &cli_opts
.remoteport
;
213 #ifdef ENABLE_CLI_PUBKEY_AUTH
214 case 'i': /* an identityfile */
215 /* Keep scp happy when it changes "-i file" to "-ifile" */
216 if (strlen(argv
[i
]) > 2) {
217 loadidentityfile(&argv
[i
][2]);
223 case 't': /* we want a pty */
224 cli_opts
.wantpty
= 1;
226 case 'T': /* don't want a pty */
227 cli_opts
.wantpty
= 0;
233 cli_opts
.backgrounded
= 1;
236 cli_opts
.is_subsystem
= 1;
238 #ifdef ENABLE_CLI_LOCALTCPFWD
243 opts
.listen_fwd_all
= 1;
246 #ifdef ENABLE_CLI_REMOTETCPFWD
251 #ifdef ENABLE_CLI_NETCAT
256 #ifdef ENABLE_CLI_PROXYCMD
258 next
= &cli_opts
.proxycmd
;
262 next
= &cli_opts
.username
;
269 /* backwards compatibility with old urandom option */
272 next
= &recv_window_arg
;
275 next
= &keepalive_arg
;
278 next
= &idle_timeout_arg
;
280 #ifdef ENABLE_CLI_AGENTFWD
282 cli_opts
.agent_fwd
= 1;
295 #ifndef ENABLE_CLI_REMOTETCPFWD
298 #ifndef ENABLE_CLI_LOCALTCPFWD
306 "WARNING: Ignoring unknown argument '%s'\n", argv
[i
]);
310 /* Now we handle args where they might be "-luser" (no spaces)*/
311 if (next
&& strlen(argv
[i
]) > 2) {
316 continue; /* next argument */
319 TRACE(("non-flag arg: '%s'", argv
[i
]))
321 /* Either the hostname or commands */
323 if (host_arg
== NULL
) {
327 /* this is part of the commands to send - after this we
328 * don't parse any more options, and flags are sent as the
331 for (j
= i
; j
< (unsigned int)argc
; j
++) {
332 cmdlen
+= strlen(argv
[j
]) + 1; /* +1 for spaces */
334 /* Allocate the space */
335 cli_opts
.cmd
= (char*)m_malloc(cmdlen
);
336 cli_opts
.cmd
[0] = '\0';
338 /* Append all the bits */
339 for (j
= i
; j
< (unsigned int)argc
; j
++) {
340 strlcat(cli_opts
.cmd
, argv
[j
], cmdlen
);
341 strlcat(cli_opts
.cmd
, " ", cmdlen
);
343 /* It'll be null-terminated here */
345 /* We've eaten all the options and flags */
351 /* And now a few sanity checks and setup */
353 if (host_arg
== NULL
) {
358 if (cli_opts
.remoteport
== NULL
) {
359 cli_opts
.remoteport
= "22";
362 /* If not explicitly specified with -t or -T, we don't want a pty if
363 * there's a command, but we do otherwise */
364 if (cli_opts
.wantpty
== 9) {
365 if (cli_opts
.cmd
== NULL
) {
366 cli_opts
.wantpty
= 1;
368 cli_opts
.wantpty
= 0;
372 if (cli_opts
.backgrounded
&& cli_opts
.cmd
== NULL
373 && cli_opts
.no_cmd
== 0) {
374 dropbear_exit("Command required for -f");
377 if (recv_window_arg
) {
378 opts
.recv_window
= atol(recv_window_arg
);
379 if (opts
.recv_window
== 0 || opts
.recv_window
> MAX_RECV_WINDOW
) {
380 dropbear_exit("Bad recv window '%s'", recv_window_arg
);
385 if (m_str_to_uint(keepalive_arg
, &val
) == DROPBEAR_FAILURE
) {
386 dropbear_exit("Bad keepalive '%s'", keepalive_arg
);
388 opts
.keepalive_secs
= val
;
391 if (idle_timeout_arg
) {
393 if (m_str_to_uint(idle_timeout_arg
, &val
) == DROPBEAR_FAILURE
) {
394 dropbear_exit("Bad idle_timeout '%s'", idle_timeout_arg
);
396 opts
.idle_timeout_secs
= val
;
399 #ifdef ENABLE_CLI_NETCAT
400 if (cli_opts
.cmd
&& cli_opts
.netcat_host
) {
401 dropbear_log(LOG_INFO
, "Ignoring command '%s' in netcat mode", cli_opts
.cmd
);
405 /* The hostname gets set up last, since
406 * in multi-hop mode it will require knowledge
407 * of other flags such as -i */
408 #ifdef ENABLE_CLI_MULTIHOP
409 parse_multihop_hostname(host_arg
, argv
[0]);
411 parse_hostname(host_arg
);
415 #ifdef ENABLE_CLI_PUBKEY_AUTH
416 static void loadidentityfile(const char* filename
) {
420 key
= new_sign_key();
421 keytype
= DROPBEAR_SIGNKEY_ANY
;
422 if ( readhostkey(filename
, key
, &keytype
) != DROPBEAR_SUCCESS
) {
423 fprintf(stderr
, "Failed loading keyfile '%s'\n", filename
);
427 key
->source
= SIGNKEY_SOURCE_RAW_FILE
;
428 key
->filename
= m_strdup(filename
);
429 list_append(cli_opts
.privkeys
, key
);
434 #ifdef ENABLE_CLI_MULTIHOP
437 multihop_passthrough_args() {
440 unsigned int len
= 0;
442 /* Fill out -i and -W options that make sense for all
443 * the intermediate processes */
444 for (iter
= cli_opts
.privkeys
->first
; iter
; iter
= iter
->next
)
446 sign_key
* key
= (sign_key
*)iter
->item
;
447 len
+= 3 + strlen(key
->filename
);
449 len
+= 20; // space for -W <size>, terminator.
453 if (opts
.recv_window
!= DEFAULT_RECV_WINDOW
)
455 int written
= snprintf(ret
+total
, len
-total
, "-W %d", opts
.recv_window
);
459 for (iter
= cli_opts
.privkeys
->first
; iter
; iter
= iter
->next
)
461 sign_key
* key
= (sign_key
*)iter
->item
;
462 const size_t size
= len
- total
;
463 int written
= snprintf(ret
+total
, size
, "-i %s", key
->filename
);
464 dropbear_assert((unsigned int)written
< size
);
471 /* Sets up 'onion-forwarding' connections. This will spawn
472 * a separate dbclient process for each hop.
473 * As an example, if the cmdline is
474 * dbclient wrt,madako,canyons
475 * then we want to run:
476 * dbclient -J "dbclient -B canyons:22 wrt,madako" canyons
477 * and then the inner dbclient will recursively run:
478 * dbclient -J "dbclient -B madako:22 wrt" madako
479 * etc for as many hosts as we want.
481 * Ports for hosts can be specified as host/port.
483 static void parse_multihop_hostname(const char* orighostarg
, const char* argv0
) {
484 char *userhostarg
= NULL
;
485 char *hostbuf
= NULL
;
486 char *last_hop
= NULL
;
487 char *remainder
= NULL
;
489 /* both scp and rsync parse a user@host argument
490 * and turn it into "-l user host". This breaks
491 * for our multihop syntax, so we suture it back together.
492 * This will break usernames that have both '@' and ',' in them,
493 * though that should be fairly uncommon. */
494 if (cli_opts
.username
495 && strchr(cli_opts
.username
, ',')
496 && strchr(cli_opts
.username
, '@')) {
497 unsigned int len
= strlen(orighostarg
) + strlen(cli_opts
.username
) + 2;
498 hostbuf
= m_malloc(len
);
499 snprintf(hostbuf
, len
, "%s@%s", cli_opts
.username
, orighostarg
);
501 hostbuf
= m_strdup(orighostarg
);
503 userhostarg
= hostbuf
;
505 last_hop
= strrchr(userhostarg
, ',');
507 if (last_hop
== userhostarg
) {
508 dropbear_exit("Bad multi-hop hostnames");
512 remainder
= userhostarg
;
513 userhostarg
= last_hop
;
516 parse_hostname(userhostarg
);
519 /* Set up the proxycmd */
520 unsigned int cmd_len
= 0;
521 char *passthrough_args
= multihop_passthrough_args();
522 if (cli_opts
.proxycmd
) {
523 dropbear_exit("-J can't be used with multihop mode");
525 if (cli_opts
.remoteport
== NULL
) {
526 cli_opts
.remoteport
= "22";
528 cmd_len
= strlen(argv0
) + strlen(remainder
)
529 + strlen(cli_opts
.remotehost
) + strlen(cli_opts
.remoteport
)
530 + strlen(passthrough_args
)
532 cli_opts
.proxycmd
= m_malloc(cmd_len
);
533 snprintf(cli_opts
.proxycmd
, cmd_len
, "%s -B %s:%s %s %s",
534 argv0
, cli_opts
.remotehost
, cli_opts
.remoteport
,
535 passthrough_args
, remainder
);
537 /* The stream will be incompressible since it's encrypted. */
538 opts
.enable_compress
= 0;
540 m_free(passthrough_args
);
544 #endif /* !ENABLE_CLI_MULTIHOP */
546 /* Parses a [user@]hostname[/port] argument. */
547 static void parse_hostname(const char* orighostarg
) {
548 char *userhostarg
= NULL
;
551 userhostarg
= m_strdup(orighostarg
);
553 cli_opts
.remotehost
= strchr(userhostarg
, '@');
554 if (cli_opts
.remotehost
== NULL
) {
555 /* no username portion, the cli-auth.c code can figure the
556 * local user's name */
557 cli_opts
.remotehost
= userhostarg
;
559 cli_opts
.remotehost
[0] = '\0'; /* Split the user/host */
560 cli_opts
.remotehost
++;
561 cli_opts
.username
= userhostarg
;
564 if (cli_opts
.username
== NULL
) {
565 cli_opts
.username
= m_strdup(cli_opts
.own_user
);
568 port
= strchr(cli_opts
.remotehost
, '/');
571 cli_opts
.remoteport
= port
+1;
574 if (cli_opts
.remotehost
[0] == '\0') {
575 dropbear_exit("Bad hostname");
579 #ifdef ENABLE_CLI_NETCAT
580 static void add_netcat(const char* origstr
) {
581 char *portstr
= NULL
;
583 char * str
= m_strdup(origstr
);
585 portstr
= strchr(str
, ':');
586 if (portstr
== NULL
) {
587 TRACE(("No netcat port"))
593 if (strchr(portstr
, ':')) {
594 TRACE(("Multiple netcat colons"))
598 if (m_str_to_uint(portstr
, &cli_opts
.netcat_port
) == DROPBEAR_FAILURE
) {
599 TRACE(("bad netcat port"))
603 if (cli_opts
.netcat_port
> 65535) {
604 TRACE(("too large netcat port"))
608 cli_opts
.netcat_host
= str
;
612 dropbear_exit("Bad netcat endpoint '%s'", origstr
);
616 static void fill_own_user() {
618 struct passwd
*pw
= NULL
;
623 if (pw
== NULL
|| pw
->pw_name
== NULL
) {
624 dropbear_exit("Unknown own user");
627 cli_opts
.own_user
= m_strdup(pw
->pw_name
);
630 #ifdef ENABLE_CLI_ANYTCPFWD
631 /* Turn a "[listenaddr:]listenport:remoteaddr:remoteport" string into into a forwarding
632 * set, and add it to the forwarding list */
633 static void addforward(const char* origstr
, m_list
*fwdlist
) {
635 char *part1
= NULL
, *part2
= NULL
, *part3
= NULL
, *part4
= NULL
;
636 char * listenaddr
= NULL
;
637 char * listenport
= NULL
;
638 char * connectaddr
= NULL
;
639 char * connectport
= NULL
;
640 struct TCPFwdEntry
* newfwd
= NULL
;
643 TRACE(("enter addforward"))
645 /* We need to split the original argument up. This var
647 str
= m_strdup(origstr
);
651 part2
= strchr(str
, ':');
653 TRACE(("part2 == NULL"))
659 part3
= strchr(part2
, ':');
661 TRACE(("part3 == NULL"))
667 part4
= strchr(part3
, ':');
685 newfwd
= m_malloc(sizeof(struct TCPFwdEntry
));
687 /* Now we check the ports - note that the port ints are unsigned,
688 * the check later only checks for >= MAX_PORT */
689 if (m_str_to_uint(listenport
, &newfwd
->listenport
) == DROPBEAR_FAILURE
) {
690 TRACE(("bad listenport strtoul"))
694 if (m_str_to_uint(connectport
, &newfwd
->connectport
) == DROPBEAR_FAILURE
) {
695 TRACE(("bad connectport strtoul"))
699 newfwd
->listenaddr
= listenaddr
;
700 newfwd
->connectaddr
= connectaddr
;
702 if (newfwd
->listenport
> 65535) {
703 TRACE(("listenport > 65535"))
707 if (newfwd
->connectport
> 65535) {
708 TRACE(("connectport > 65535"))
712 newfwd
->have_reply
= 0;
713 list_append(fwdlist
, newfwd
);
715 TRACE(("leave addforward: done"))
719 dropbear_exit("Bad TCP forward '%s'", origstr
);
722 dropbear_exit("Bad TCP port in '%s'", origstr
);