remove unused variable
[dropbear.git] / cli-runopts.c
blobe874dc2f9deae16ebb5f27b6a8c6b66d1b8945d4
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:
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
23 * SOFTWARE. */
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"
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);
42 #endif
43 #ifdef ENABLE_CLI_ANYTCPFWD
44 static void addforward(const char* str, m_list *fwdlist);
45 #endif
46 #ifdef ENABLE_CLI_NETCAT
47 static void add_netcat(const char *str);
48 #endif
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"
55 #else
56 "Usage: %s [options] [user@]host[/port] [command]\n"
57 #endif
58 "Options are:\n"
59 "-p <remoteport>\n"
60 "-l <username>\n"
61 "-t Allocate a pty\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"
69 #endif
70 #ifdef ENABLE_CLI_AGENTFWD
71 "-A Enable agent auth forwarding\n"
72 #endif
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"
76 #endif
77 #ifdef ENABLE_CLI_REMOTETCPFWD
78 "-R <[listenaddress:]listenport:remotehost:remoteport> Remote port forwarding\n"
79 #endif
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"
85 #endif
86 #ifdef ENABLE_CLI_PROXYCMD
87 "-J <proxy_program> Use program pipe rather than TCP connection\n"
88 #endif
89 #ifdef DEBUG_TRACE
90 "-v verbose (compiled with DEBUG_TRACE)\n"
91 #endif
92 ,DROPBEAR_VERSION, cli_opts.progname,
93 DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE, DEFAULT_IDLE_TIMEOUT);
97 void cli_getopts(int argc, char ** argv) {
98 unsigned int i, j;
99 char ** next = 0;
100 unsigned int cmdlen;
101 #ifdef ENABLE_CLI_PUBKEY_AUTH
102 int nextiskey = 0; /* A flag if the next argument is a keyfile */
103 #endif
104 #ifdef ENABLE_CLI_LOCALTCPFWD
105 int nextislocal = 0;
106 #endif
107 #ifdef ENABLE_CLI_REMOTETCPFWD
108 int nextisremote = 0;
109 #endif
110 #ifdef ENABLE_CLI_NETCAT
111 int nextisnetcat = 0;
112 #endif
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;
125 cli_opts.cmd = NULL;
126 cli_opts.no_cmd = 0;
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();
133 #endif
134 #ifdef ENABLE_CLI_LOCALTCPFWD
135 cli_opts.localfwds = list_new();
136 opts.listen_fwd_all = 0;
137 #endif
138 #ifdef ENABLE_CLI_REMOTETCPFWD
139 cli_opts.remotefwds = list_new();
140 #endif
141 #ifdef ENABLE_CLI_AGENTFWD
142 cli_opts.agent_fwd = 0;
143 cli_opts.agent_keys_loaded = 0;
144 #endif
145 #ifdef ENABLE_CLI_PROXYCMD
146 cli_opts.proxycmd = NULL;
147 #endif
148 #ifndef DISABLE_ZLIB
149 opts.enable_compress = 1;
150 #endif
151 /* not yet
152 opts.ipv4 = 1;
153 opts.ipv6 = 1;
155 opts.recv_window = DEFAULT_RECV_WINDOW;
157 fill_own_user();
159 /* Iterate all the arguments */
160 for (i = 1; i < (unsigned int)argc; i++) {
161 #ifdef ENABLE_CLI_PUBKEY_AUTH
162 if (nextiskey) {
163 /* Load a hostkey since the previous argument was "-i" */
164 loadidentityfile(argv[i]);
165 nextiskey = 0;
166 continue;
168 #endif
169 #ifdef ENABLE_CLI_REMOTETCPFWD
170 if (nextisremote) {
171 TRACE(("nextisremote true"))
172 addforward(argv[i], cli_opts.remotefwds);
173 nextisremote = 0;
174 continue;
176 #endif
177 #ifdef ENABLE_CLI_LOCALTCPFWD
178 if (nextislocal) {
179 TRACE(("nextislocal true"))
180 addforward(argv[i], cli_opts.localfwds);
181 nextislocal = 0;
182 continue;
184 #endif
185 #ifdef ENABLE_CLI_NETCAT
186 if (nextisnetcat) {
187 TRACE(("nextisnetcat true"))
188 add_netcat(argv[i]);
189 nextisnetcat = 0;
190 continue;
192 #endif
193 if (next) {
194 /* The previous flag set a value to assign */
195 *next = argv[i];
196 if (*next == NULL) {
197 dropbear_exit("Invalid null argument");
199 next = NULL;
200 continue;
203 if (argv[i][0] == '-') {
204 /* A flag *waves* */
206 switch (argv[i][1]) {
207 case 'y': /* always accept the remote hostkey */
208 cli_opts.always_accept_key = 1;
209 break;
210 case 'p': /* remoteport */
211 next = &cli_opts.remoteport;
212 break;
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]);
218 } else {
219 nextiskey = 1;
221 break;
222 #endif
223 case 't': /* we want a pty */
224 cli_opts.wantpty = 1;
225 break;
226 case 'T': /* don't want a pty */
227 cli_opts.wantpty = 0;
228 break;
229 case 'N':
230 cli_opts.no_cmd = 1;
231 break;
232 case 'f':
233 cli_opts.backgrounded = 1;
234 break;
235 case 's':
236 cli_opts.is_subsystem = 1;
237 break;
238 #ifdef ENABLE_CLI_LOCALTCPFWD
239 case 'L':
240 nextislocal = 1;
241 break;
242 case 'g':
243 opts.listen_fwd_all = 1;
244 break;
245 #endif
246 #ifdef ENABLE_CLI_REMOTETCPFWD
247 case 'R':
248 nextisremote = 1;
249 break;
250 #endif
251 #ifdef ENABLE_CLI_NETCAT
252 case 'B':
253 nextisnetcat = 1;
254 break;
255 #endif
256 #ifdef ENABLE_CLI_PROXYCMD
257 case 'J':
258 next = &cli_opts.proxycmd;
259 break;
260 #endif
261 case 'l':
262 next = &cli_opts.username;
263 break;
264 case 'h':
265 printhelp();
266 exit(EXIT_SUCCESS);
267 break;
268 case 'u':
269 /* backwards compatibility with old urandom option */
270 break;
271 case 'W':
272 next = &recv_window_arg;
273 break;
274 case 'K':
275 next = &keepalive_arg;
276 break;
277 case 'I':
278 next = &idle_timeout_arg;
279 break;
280 #ifdef ENABLE_CLI_AGENTFWD
281 case 'A':
282 cli_opts.agent_fwd = 1;
283 break;
284 #endif
285 #ifdef DEBUG_TRACE
286 case 'v':
287 debug_trace = 1;
288 break;
289 #endif
290 case 'F':
291 case 'e':
292 case 'c':
293 case 'm':
294 case 'D':
295 #ifndef ENABLE_CLI_REMOTETCPFWD
296 case 'R':
297 #endif
298 #ifndef ENABLE_CLI_LOCALTCPFWD
299 case 'L':
300 #endif
301 case 'o':
302 case 'b':
303 next = &dummy;
304 default:
305 fprintf(stderr,
306 "WARNING: Ignoring unknown argument '%s'\n", argv[i]);
307 break;
308 } /* Switch */
310 /* Now we handle args where they might be "-luser" (no spaces)*/
311 if (next && strlen(argv[i]) > 2) {
312 *next = &argv[i][2];
313 next = NULL;
316 continue; /* next argument */
318 } else {
319 TRACE(("non-flag arg: '%s'", argv[i]))
321 /* Either the hostname or commands */
323 if (host_arg == NULL) {
324 host_arg = argv[i];
325 } else {
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
329 * command */
330 cmdlen = 0;
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 */
346 break;
351 /* And now a few sanity checks and setup */
353 if (host_arg == NULL) {
354 printhelp();
355 exit(EXIT_FAILURE);
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;
367 } else {
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);
383 if (keepalive_arg) {
384 unsigned int val;
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) {
392 unsigned int val;
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);
403 #endif
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]);
410 #else
411 parse_hostname(host_arg);
412 #endif
415 #ifdef ENABLE_CLI_PUBKEY_AUTH
416 static void loadidentityfile(const char* filename) {
417 sign_key *key;
418 int keytype;
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);
424 sign_key_free(key);
425 } else {
426 key->type = keytype;
427 key->source = SIGNKEY_SOURCE_RAW_FILE;
428 key->filename = m_strdup(filename);
429 list_append(cli_opts.privkeys, key);
432 #endif
434 #ifdef ENABLE_CLI_MULTIHOP
436 static char*
437 multihop_passthrough_args() {
438 char *ret;
439 int total;
440 unsigned int len = 0;
441 m_list_elem *iter;
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.
450 ret = m_malloc(len);
451 total = 0;
453 if (opts.recv_window != DEFAULT_RECV_WINDOW)
455 int written = snprintf(ret+total, len-total, "-W %d", opts.recv_window);
456 total += written;
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);
465 total += written;
468 return ret;
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);
500 } else {
501 hostbuf = m_strdup(orighostarg);
503 userhostarg = hostbuf;
505 last_hop = strrchr(userhostarg, ',');
506 if (last_hop) {
507 if (last_hop == userhostarg) {
508 dropbear_exit("Bad multi-hop hostnames");
510 *last_hop = '\0';
511 last_hop++;
512 remainder = userhostarg;
513 userhostarg = last_hop;
516 parse_hostname(userhostarg);
518 if (last_hop) {
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)
531 + 30;
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);
536 #ifndef DISABLE_ZLIB
537 /* The stream will be incompressible since it's encrypted. */
538 opts.enable_compress = 0;
539 #endif
540 m_free(passthrough_args);
542 m_free(hostbuf);
544 #endif /* !ENABLE_CLI_MULTIHOP */
546 /* Parses a [user@]hostname[/port] argument. */
547 static void parse_hostname(const char* orighostarg) {
548 char *userhostarg = NULL;
549 char *port = 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;
558 } else {
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, '/');
569 if (port) {
570 *port = '\0';
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"))
588 goto fail;
590 *portstr = '\0';
591 portstr++;
593 if (strchr(portstr, ':')) {
594 TRACE(("Multiple netcat colons"))
595 goto fail;
598 if (m_str_to_uint(portstr, &cli_opts.netcat_port) == DROPBEAR_FAILURE) {
599 TRACE(("bad netcat port"))
600 goto fail;
603 if (cli_opts.netcat_port > 65535) {
604 TRACE(("too large netcat port"))
605 goto fail;
608 cli_opts.netcat_host = str;
609 return;
611 fail:
612 dropbear_exit("Bad netcat endpoint '%s'", origstr);
614 #endif
616 static void fill_own_user() {
617 uid_t uid;
618 struct passwd *pw = NULL;
620 uid = getuid();
622 pw = getpwuid(uid);
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;
641 char * str = NULL;
643 TRACE(("enter addforward"))
645 /* We need to split the original argument up. This var
646 is never free()d. */
647 str = m_strdup(origstr);
649 part1 = str;
651 part2 = strchr(str, ':');
652 if (part2 == NULL) {
653 TRACE(("part2 == NULL"))
654 goto fail;
656 *part2 = '\0';
657 part2++;
659 part3 = strchr(part2, ':');
660 if (part3 == NULL) {
661 TRACE(("part3 == NULL"))
662 goto fail;
664 *part3 = '\0';
665 part3++;
667 part4 = strchr(part3, ':');
668 if (part4) {
669 *part4 = '\0';
670 part4++;
673 if (part4) {
674 listenaddr = part1;
675 listenport = part2;
676 connectaddr = part3;
677 connectport = part4;
678 } else {
679 listenaddr = NULL;
680 listenport = part1;
681 connectaddr = part2;
682 connectport = 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"))
691 goto fail;
694 if (m_str_to_uint(connectport, &newfwd->connectport) == DROPBEAR_FAILURE) {
695 TRACE(("bad connectport strtoul"))
696 goto fail;
699 newfwd->listenaddr = listenaddr;
700 newfwd->connectaddr = connectaddr;
702 if (newfwd->listenport > 65535) {
703 TRACE(("listenport > 65535"))
704 goto badport;
707 if (newfwd->connectport > 65535) {
708 TRACE(("connectport > 65535"))
709 goto badport;
712 newfwd->have_reply = 0;
713 list_append(fwdlist, newfwd);
715 TRACE(("leave addforward: done"))
716 return;
718 fail:
719 dropbear_exit("Bad TCP forward '%s'", origstr);
721 badport:
722 dropbear_exit("Bad TCP port in '%s'", origstr);
724 #endif