/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_PUBKEY_AUTH |
41 | | static void loadidentityfile(const char* filename, int warnfail); |
42 | | #endif |
43 | | #if DROPBEAR_CLI_ANYTCPFWD |
44 | | static void addforward(const char* str, m_list *fwdlist); |
45 | | #endif |
46 | | #if DROPBEAR_CLI_NETCAT |
47 | | static void add_netcat(const char *str); |
48 | | #endif |
49 | | static void add_extendedopt(const char *str); |
50 | | |
51 | 0 | static void printhelp() { |
52 | |
|
53 | 0 | fprintf(stderr, "Dropbear SSH client v%s https://matt.ucc.asn.au/dropbear/dropbear.html\n" |
54 | 0 | #if DROPBEAR_CLI_MULTIHOP |
55 | 0 | "Usage: %s [options] [user@]host[/port][,[user@]host/port],...] [command]\n" |
56 | | #else |
57 | | "Usage: %s [options] [user@]host[/port] [command]\n" |
58 | | #endif |
59 | 0 | "-p <remoteport>\n" |
60 | 0 | "-l <username>\n" |
61 | 0 | "-t Allocate a pty\n" |
62 | 0 | "-T Don't allocate a pty\n" |
63 | 0 | "-N Don't run a remote command\n" |
64 | 0 | "-f Run in background after auth\n" |
65 | 0 | "-q quiet, don't show remote banner\n" |
66 | 0 | "-y Always accept remote host key if unknown\n" |
67 | 0 | "-y -y Don't perform any remote host key checking (caution)\n" |
68 | 0 | "-s Request a subsystem (use by external sftp)\n" |
69 | 0 | "-o option Set option in OpenSSH-like format ('-o help' to list options)\n" |
70 | 0 | #if DROPBEAR_CLI_PUBKEY_AUTH |
71 | 0 | "-i <identityfile> (multiple allowed, default %s)\n" |
72 | 0 | #endif |
73 | 0 | #if DROPBEAR_CLI_AGENTFWD |
74 | 0 | "-A Enable agent auth forwarding\n" |
75 | 0 | #endif |
76 | 0 | #if DROPBEAR_CLI_LOCALTCPFWD |
77 | 0 | "-L <[listenaddress:]listenport:remotehost:remoteport> Local port forwarding\n" |
78 | 0 | "-g Allow remote hosts to connect to forwarded ports\n" |
79 | 0 | #endif |
80 | 0 | #if DROPBEAR_CLI_REMOTETCPFWD |
81 | 0 | "-R <[listenaddress:]listenport:remotehost:remoteport> Remote port forwarding\n" |
82 | 0 | #endif |
83 | 0 | "-W <receive_window_buffer> (default %d, larger may be faster, max 10MB)\n" |
84 | 0 | "-K <keepalive> (0 is never, default %d)\n" |
85 | 0 | "-I <idle_timeout> (0 is never, default %d)\n" |
86 | 0 | "-z disable QoS\n" |
87 | 0 | #if DROPBEAR_CLI_NETCAT |
88 | 0 | "-B <endhost:endport> Netcat-alike forwarding\n" |
89 | 0 | #endif |
90 | 0 | #if DROPBEAR_CLI_PROXYCMD |
91 | 0 | "-J <proxy_program> Use program pipe rather than TCP connection\n" |
92 | 0 | #endif |
93 | 0 | #if DROPBEAR_USER_ALGO_LIST |
94 | 0 | "-c <cipher list> Specify preferred ciphers ('-c help' to list options)\n" |
95 | 0 | "-m <MAC list> Specify preferred MACs for packet verification (or '-m help')\n" |
96 | 0 | #endif |
97 | 0 | "-b [bind_address][:bind_port]\n" |
98 | 0 | "-V Version\n" |
99 | | #if DEBUG_TRACE |
100 | | "-v verbose (repeat for more verbose)\n" |
101 | | #endif |
102 | 0 | ,DROPBEAR_VERSION, cli_opts.progname, |
103 | 0 | #if DROPBEAR_CLI_PUBKEY_AUTH |
104 | 0 | DROPBEAR_DEFAULT_CLI_AUTHKEY, |
105 | 0 | #endif |
106 | 0 | DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE, DEFAULT_IDLE_TIMEOUT); |
107 | | |
108 | 0 | } |
109 | | |
110 | 2 | void cli_getopts(int argc, char ** argv) { |
111 | 2 | unsigned int i, j; |
112 | 2 | char ** next = NULL; |
113 | 2 | enum { |
114 | 2 | OPT_EXTENDED_OPTIONS, |
115 | 2 | #if DROPBEAR_CLI_PUBKEY_AUTH |
116 | 2 | OPT_AUTHKEY, |
117 | 2 | #endif |
118 | 2 | #if DROPBEAR_CLI_LOCALTCPFWD |
119 | 2 | OPT_LOCALTCPFWD, |
120 | 2 | #endif |
121 | 2 | #if DROPBEAR_CLI_REMOTETCPFWD |
122 | 2 | OPT_REMOTETCPFWD, |
123 | 2 | #endif |
124 | 2 | #if DROPBEAR_CLI_NETCAT |
125 | 2 | OPT_NETCAT, |
126 | 2 | #endif |
127 | | /* a flag (no arg) if 'next' is NULL, a string-valued option otherwise */ |
128 | 2 | OPT_OTHER |
129 | 2 | } opt; |
130 | 2 | unsigned int cmdlen; |
131 | | |
132 | 2 | char* recv_window_arg = NULL; |
133 | 2 | char* keepalive_arg = NULL; |
134 | 2 | char* idle_timeout_arg = NULL; |
135 | 2 | char *host_arg = NULL; |
136 | 2 | char *bind_arg = NULL; |
137 | 2 | char c; |
138 | | |
139 | | /* see printhelp() for options */ |
140 | 2 | cli_opts.progname = argv[0]; |
141 | 2 | cli_opts.remotehost = NULL; |
142 | 2 | cli_opts.remoteport = NULL; |
143 | 2 | cli_opts.username = NULL; |
144 | 2 | cli_opts.cmd = NULL; |
145 | 2 | cli_opts.no_cmd = 0; |
146 | 2 | cli_opts.quiet = 0; |
147 | 2 | cli_opts.backgrounded = 0; |
148 | 2 | cli_opts.wantpty = 9; /* 9 means "it hasn't been touched", gets set later */ |
149 | 2 | cli_opts.always_accept_key = 0; |
150 | 2 | cli_opts.no_hostkey_check = 0; |
151 | 2 | cli_opts.is_subsystem = 0; |
152 | 2 | #if DROPBEAR_CLI_PUBKEY_AUTH |
153 | 2 | cli_opts.privkeys = list_new(); |
154 | 2 | #endif |
155 | 2 | #if DROPBEAR_CLI_ANYTCPFWD |
156 | 2 | cli_opts.exit_on_fwd_failure = 0; |
157 | 2 | #endif |
158 | 2 | cli_opts.disable_trivial_auth = 0; |
159 | 2 | #if DROPBEAR_CLI_LOCALTCPFWD |
160 | 2 | cli_opts.localfwds = list_new(); |
161 | 2 | opts.listen_fwd_all = 0; |
162 | 2 | #endif |
163 | 2 | #if DROPBEAR_CLI_REMOTETCPFWD |
164 | 2 | cli_opts.remotefwds = list_new(); |
165 | 2 | #endif |
166 | 2 | #if DROPBEAR_CLI_AGENTFWD |
167 | 2 | cli_opts.agent_fwd = 0; |
168 | 2 | cli_opts.agent_fd = -1; |
169 | 2 | cli_opts.agent_keys_loaded = 0; |
170 | 2 | #endif |
171 | 2 | #if DROPBEAR_CLI_PROXYCMD |
172 | 2 | cli_opts.proxycmd = NULL; |
173 | 2 | #endif |
174 | 2 | cli_opts.bind_address = NULL; |
175 | 2 | cli_opts.bind_port = NULL; |
176 | | #ifndef DISABLE_ZLIB |
177 | | opts.compress_mode = DROPBEAR_COMPRESS_ON; |
178 | | #endif |
179 | 2 | #if DROPBEAR_USER_ALGO_LIST |
180 | 2 | opts.cipher_list = NULL; |
181 | 2 | opts.mac_list = NULL; |
182 | 2 | #endif |
183 | 2 | #ifndef DISABLE_SYSLOG |
184 | 2 | opts.usingsyslog = 0; |
185 | 2 | #endif |
186 | | /* not yet |
187 | | opts.ipv4 = 1; |
188 | | opts.ipv6 = 1; |
189 | | */ |
190 | 2 | opts.recv_window = DEFAULT_RECV_WINDOW; |
191 | 2 | opts.keepalive_secs = DEFAULT_KEEPALIVE; |
192 | 2 | opts.idle_timeout_secs = DEFAULT_IDLE_TIMEOUT; |
193 | | |
194 | 2 | fill_own_user(); |
195 | | |
196 | 6 | for (i = 1; i < (unsigned int)argc; i++) { |
197 | | /* Handle non-flag arguments such as hostname or commands for the remote host */ |
198 | 6 | if (argv[i][0] != '-') |
199 | 4 | { |
200 | 4 | if (host_arg == NULL) { |
201 | 2 | host_arg = argv[i]; |
202 | 2 | continue; |
203 | 2 | } |
204 | | /* Commands to pass to the remote host. No more flag handling, |
205 | | commands are consumed below */ |
206 | 2 | break; |
207 | 4 | } |
208 | | |
209 | | /* Begins with '-' */ |
210 | 2 | opt = OPT_OTHER; |
211 | 4 | for (j = 1; (c = argv[i][j]) != '\0' && !next && opt == OPT_OTHER; j++) { |
212 | 2 | switch (c) { |
213 | 2 | case 'y': /* always accept the remote hostkey */ |
214 | 2 | if (cli_opts.always_accept_key) { |
215 | | /* twice means no checking at all */ |
216 | 0 | cli_opts.no_hostkey_check = 1; |
217 | 0 | } |
218 | 2 | cli_opts.always_accept_key = 1; |
219 | 2 | break; |
220 | 0 | case 'q': /* quiet */ |
221 | 0 | cli_opts.quiet = 1; |
222 | 0 | break; |
223 | 0 | case 'p': /* remoteport */ |
224 | 0 | next = (char**)&cli_opts.remoteport; |
225 | 0 | break; |
226 | 0 | #if DROPBEAR_CLI_PUBKEY_AUTH |
227 | 0 | case 'i': /* an identityfile */ |
228 | 0 | opt = OPT_AUTHKEY; |
229 | 0 | break; |
230 | 0 | #endif |
231 | 0 | case 't': /* we want a pty */ |
232 | 0 | cli_opts.wantpty = 1; |
233 | 0 | break; |
234 | 0 | case 'T': /* don't want a pty */ |
235 | 0 | cli_opts.wantpty = 0; |
236 | 0 | break; |
237 | 0 | case 'N': |
238 | 0 | cli_opts.no_cmd = 1; |
239 | 0 | break; |
240 | 0 | case 'f': |
241 | 0 | cli_opts.backgrounded = 1; |
242 | 0 | break; |
243 | 0 | case 's': |
244 | 0 | cli_opts.is_subsystem = 1; |
245 | 0 | break; |
246 | 0 | case 'o': |
247 | 0 | opt = OPT_EXTENDED_OPTIONS; |
248 | 0 | break; |
249 | 0 | #if DROPBEAR_CLI_LOCALTCPFWD |
250 | 0 | case 'L': |
251 | 0 | opt = OPT_LOCALTCPFWD; |
252 | 0 | break; |
253 | 0 | case 'g': |
254 | 0 | opts.listen_fwd_all = 1; |
255 | 0 | break; |
256 | 0 | #endif |
257 | 0 | #if DROPBEAR_CLI_REMOTETCPFWD |
258 | 0 | case 'R': |
259 | 0 | opt = OPT_REMOTETCPFWD; |
260 | 0 | break; |
261 | 0 | #endif |
262 | 0 | #if DROPBEAR_CLI_NETCAT |
263 | 0 | case 'B': |
264 | 0 | opt = OPT_NETCAT; |
265 | 0 | break; |
266 | 0 | #endif |
267 | 0 | #if DROPBEAR_CLI_PROXYCMD |
268 | 0 | case 'J': |
269 | 0 | next = &cli_opts.proxycmd; |
270 | 0 | break; |
271 | 0 | #endif |
272 | 0 | case 'l': |
273 | 0 | next = &cli_opts.username; |
274 | 0 | break; |
275 | 0 | case 'h': |
276 | 0 | printhelp(); |
277 | 0 | exit(EXIT_SUCCESS); |
278 | 0 | break; |
279 | 0 | case 'u': |
280 | | /* backwards compatibility with old urandom option */ |
281 | 0 | break; |
282 | 0 | case 'W': |
283 | 0 | next = &recv_window_arg; |
284 | 0 | break; |
285 | 0 | case 'K': |
286 | 0 | next = &keepalive_arg; |
287 | 0 | break; |
288 | 0 | case 'I': |
289 | 0 | next = &idle_timeout_arg; |
290 | 0 | break; |
291 | 0 | #if DROPBEAR_CLI_AGENTFWD |
292 | 0 | case 'A': |
293 | 0 | cli_opts.agent_fwd = 1; |
294 | 0 | break; |
295 | 0 | #endif |
296 | 0 | #if DROPBEAR_USER_ALGO_LIST |
297 | 0 | case 'c': |
298 | 0 | next = &opts.cipher_list; |
299 | 0 | break; |
300 | 0 | case 'm': |
301 | 0 | next = &opts.mac_list; |
302 | 0 | break; |
303 | 0 | #endif |
304 | | #if DEBUG_TRACE |
305 | | case 'v': |
306 | | debug_trace++; |
307 | | break; |
308 | | #endif |
309 | 0 | case 'F': |
310 | 0 | case 'e': |
311 | | #if !DROPBEAR_USER_ALGO_LIST |
312 | | case 'c': |
313 | | case 'm': |
314 | | #endif |
315 | 0 | case 'D': |
316 | | #if !DROPBEAR_CLI_REMOTETCPFWD |
317 | | case 'R': |
318 | | #endif |
319 | | #if !DROPBEAR_CLI_LOCALTCPFWD |
320 | | case 'L': |
321 | | #endif |
322 | 0 | case 'V': |
323 | 0 | print_version(); |
324 | 0 | exit(EXIT_SUCCESS); |
325 | 0 | break; |
326 | 0 | case 'b': |
327 | 0 | next = &bind_arg; |
328 | 0 | break; |
329 | 0 | case 'z': |
330 | 0 | opts.disable_ip_tos = 1; |
331 | 0 | break; |
332 | 0 | default: |
333 | 0 | fprintf(stderr, |
334 | 0 | "WARNING: Ignoring unknown option -%c\n", c); |
335 | 0 | break; |
336 | 2 | } /* Switch */ |
337 | 2 | } |
338 | | |
339 | 2 | if (!next && opt == OPT_OTHER) /* got a flag */ |
340 | 2 | continue; |
341 | | |
342 | 0 | if (c == '\0') { |
343 | 0 | i++; |
344 | 0 | j = 0; |
345 | 0 | if (!argv[i]) |
346 | 0 | dropbear_exit("Missing argument"); |
347 | 0 | } |
348 | | |
349 | 0 | if (opt == OPT_EXTENDED_OPTIONS) { |
350 | 0 | TRACE(("opt extended")) |
351 | 0 | add_extendedopt(&argv[i][j]); |
352 | 0 | } |
353 | 0 | else |
354 | 0 | #if DROPBEAR_CLI_PUBKEY_AUTH |
355 | 0 | if (opt == OPT_AUTHKEY) { |
356 | 0 | TRACE(("opt authkey")) |
357 | 0 | loadidentityfile(&argv[i][j], 1); |
358 | 0 | } |
359 | 0 | else |
360 | 0 | #endif |
361 | 0 | #if DROPBEAR_CLI_REMOTETCPFWD |
362 | 0 | if (opt == OPT_REMOTETCPFWD) { |
363 | 0 | TRACE(("opt remotetcpfwd")) |
364 | 0 | addforward(&argv[i][j], cli_opts.remotefwds); |
365 | 0 | } |
366 | 0 | else |
367 | 0 | #endif |
368 | 0 | #if DROPBEAR_CLI_LOCALTCPFWD |
369 | 0 | if (opt == OPT_LOCALTCPFWD) { |
370 | 0 | TRACE(("opt localtcpfwd")) |
371 | 0 | addforward(&argv[i][j], cli_opts.localfwds); |
372 | 0 | } |
373 | 0 | else |
374 | 0 | #endif |
375 | 0 | #if DROPBEAR_CLI_NETCAT |
376 | 0 | if (opt == OPT_NETCAT) { |
377 | 0 | TRACE(("opt netcat")) |
378 | 0 | add_netcat(&argv[i][j]); |
379 | 0 | } |
380 | 0 | else |
381 | 0 | #endif |
382 | 0 | if (next) { |
383 | | /* The previous flag set a value to assign */ |
384 | 0 | *next = &argv[i][j]; |
385 | 0 | if (*next == NULL) |
386 | 0 | dropbear_exit("Invalid null argument"); |
387 | 0 | next = NULL; |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | 2 | #if DROPBEAR_USER_ALGO_LIST |
392 | | /* -c help doesn't need a hostname */ |
393 | 2 | parse_ciphers_macs(); |
394 | 2 | #endif |
395 | | |
396 | | /* Done with options/flags; now handle the hostname (which may not |
397 | | * start with a hyphen) and optional command */ |
398 | | |
399 | 2 | if (host_arg == NULL) { /* missing hostname */ |
400 | 0 | printhelp(); |
401 | 0 | exit(EXIT_FAILURE); |
402 | 0 | } |
403 | 2 | TRACE(("host is: %s", host_arg)) |
404 | | |
405 | 2 | if (i < (unsigned int)argc) { |
406 | | /* Build the command to send */ |
407 | 2 | cmdlen = 0; |
408 | 4 | for (j = i; j < (unsigned int)argc; j++) |
409 | 2 | cmdlen += strlen(argv[j]) + 1; /* +1 for spaces */ |
410 | | |
411 | | /* Allocate the space */ |
412 | 2 | cli_opts.cmd = (char*)m_malloc(cmdlen); |
413 | 2 | cli_opts.cmd[0] = '\0'; |
414 | | |
415 | | /* Append all the bits */ |
416 | 4 | for (j = i; j < (unsigned int)argc; j++) { |
417 | 2 | strlcat(cli_opts.cmd, argv[j], cmdlen); |
418 | 2 | strlcat(cli_opts.cmd, " ", cmdlen); |
419 | 2 | } |
420 | | /* It'll be null-terminated here */ |
421 | 2 | TRACE(("cmd is: %s", cli_opts.cmd)) |
422 | 2 | } |
423 | | |
424 | | /* And now a few sanity checks and setup */ |
425 | | |
426 | 2 | #if DROPBEAR_CLI_PROXYCMD |
427 | 2 | if (cli_opts.proxycmd) { |
428 | | /* To match the common path of m_freeing it */ |
429 | 0 | cli_opts.proxycmd = m_strdup(cli_opts.proxycmd); |
430 | 0 | } |
431 | 2 | #endif |
432 | | |
433 | 2 | if (cli_opts.remoteport == NULL) { |
434 | 2 | cli_opts.remoteport = "22"; |
435 | 2 | } |
436 | | |
437 | 2 | if (bind_arg) { |
438 | 0 | if (split_address_port(bind_arg, |
439 | 0 | &cli_opts.bind_address, &cli_opts.bind_port) |
440 | 0 | == DROPBEAR_FAILURE) { |
441 | 0 | dropbear_exit("Bad -b argument"); |
442 | 0 | } |
443 | 0 | } |
444 | | |
445 | | /* If not explicitly specified with -t or -T, we don't want a pty if |
446 | | * there's a command, but we do otherwise */ |
447 | 2 | if (cli_opts.wantpty == 9) { |
448 | 2 | if (cli_opts.cmd == NULL) { |
449 | 0 | cli_opts.wantpty = 1; |
450 | 2 | } else { |
451 | 2 | cli_opts.wantpty = 0; |
452 | 2 | } |
453 | 2 | } |
454 | | |
455 | 2 | if (cli_opts.backgrounded && cli_opts.cmd == NULL |
456 | 2 | && cli_opts.no_cmd == 0) { |
457 | 0 | dropbear_exit("Command required for -f"); |
458 | 0 | } |
459 | | |
460 | 2 | if (recv_window_arg) { |
461 | 0 | parse_recv_window(recv_window_arg); |
462 | 0 | } |
463 | 2 | if (keepalive_arg) { |
464 | 0 | unsigned int val; |
465 | 0 | if (m_str_to_uint(keepalive_arg, &val) == DROPBEAR_FAILURE) { |
466 | 0 | dropbear_exit("Bad keepalive '%s'", keepalive_arg); |
467 | 0 | } |
468 | 0 | opts.keepalive_secs = val; |
469 | 0 | } |
470 | | |
471 | 2 | if (idle_timeout_arg) { |
472 | 0 | unsigned int val; |
473 | 0 | if (m_str_to_uint(idle_timeout_arg, &val) == DROPBEAR_FAILURE) { |
474 | 0 | dropbear_exit("Bad idle_timeout '%s'", idle_timeout_arg); |
475 | 0 | } |
476 | 0 | opts.idle_timeout_secs = val; |
477 | 0 | } |
478 | | |
479 | 2 | #if DROPBEAR_CLI_NETCAT |
480 | 2 | if (cli_opts.cmd && cli_opts.netcat_host) { |
481 | 0 | dropbear_log(LOG_INFO, "Ignoring command '%s' in netcat mode", cli_opts.cmd); |
482 | 0 | } |
483 | 2 | #endif |
484 | | |
485 | | /* The hostname gets set up last, since |
486 | | * in multi-hop mode it will require knowledge |
487 | | * of other flags such as -i */ |
488 | 2 | #if DROPBEAR_CLI_MULTIHOP |
489 | 2 | parse_multihop_hostname(host_arg, argv[0]); |
490 | | #else |
491 | | parse_hostname(host_arg); |
492 | | #endif |
493 | | |
494 | | /* We don't want to include default id_dropbear as a |
495 | | -i argument for multihop, so handle it later. */ |
496 | 2 | #if (DROPBEAR_CLI_PUBKEY_AUTH) |
497 | 2 | { |
498 | 2 | char *expand_path = expand_homedir_path(DROPBEAR_DEFAULT_CLI_AUTHKEY); |
499 | 2 | loadidentityfile(expand_path, 0); |
500 | 2 | m_free(expand_path); |
501 | 2 | } |
502 | 2 | #endif |
503 | | |
504 | 2 | } |
505 | | |
506 | | #if DROPBEAR_CLI_PUBKEY_AUTH |
507 | 2 | static void loadidentityfile(const char* filename, int warnfail) { |
508 | 2 | sign_key *key; |
509 | 2 | enum signkey_type keytype; |
510 | | |
511 | 2 | TRACE(("loadidentityfile %s", filename)) |
512 | | |
513 | 2 | key = new_sign_key(); |
514 | 2 | keytype = DROPBEAR_SIGNKEY_ANY; |
515 | 2 | if ( readhostkey(filename, key, &keytype) != DROPBEAR_SUCCESS ) { |
516 | 2 | if (warnfail) { |
517 | 0 | dropbear_log(LOG_WARNING, "Failed loading keyfile '%s'\n", filename); |
518 | 0 | } |
519 | 2 | sign_key_free(key); |
520 | 2 | } else { |
521 | 0 | key->type = keytype; |
522 | 0 | key->source = SIGNKEY_SOURCE_RAW_FILE; |
523 | 0 | key->filename = m_strdup(filename); |
524 | 0 | list_append(cli_opts.privkeys, key); |
525 | 0 | } |
526 | 2 | } |
527 | | #endif |
528 | | |
529 | | #if DROPBEAR_CLI_MULTIHOP |
530 | | |
531 | | static char* |
532 | 0 | multihop_passthrough_args() { |
533 | 0 | char *ret; |
534 | 0 | unsigned int len, total; |
535 | 0 | m_list_elem *iter; |
536 | | /* Fill out -i, -y, -W options that make sense for all |
537 | | * the intermediate processes */ |
538 | 0 | len = 30; /* space for "-q -y -y -W <size>\0" */ |
539 | 0 | #if DROPBEAR_CLI_PUBKEY_AUTH |
540 | 0 | for (iter = cli_opts.privkeys->first; iter; iter = iter->next) |
541 | 0 | { |
542 | 0 | sign_key * key = (sign_key*)iter->item; |
543 | 0 | len += 3 + strlen(key->filename); |
544 | 0 | } |
545 | 0 | #endif /* DROPBEAR_CLI_PUBKEY_AUTH */ |
546 | 0 | if (cli_opts.proxycmd) { |
547 | | /* "-J 'cmd'" */ |
548 | 0 | len += 6 + strlen(cli_opts.proxycmd); |
549 | 0 | } |
550 | |
|
551 | 0 | ret = m_malloc(len); |
552 | 0 | total = 0; |
553 | |
|
554 | 0 | if (cli_opts.quiet) { |
555 | 0 | total += m_snprintf(ret+total, len-total, "-q "); |
556 | 0 | } |
557 | |
|
558 | 0 | if (cli_opts.no_hostkey_check) { |
559 | 0 | total += m_snprintf(ret+total, len-total, "-y -y "); |
560 | 0 | } else if (cli_opts.always_accept_key) { |
561 | 0 | total += m_snprintf(ret+total, len-total, "-y "); |
562 | 0 | } |
563 | |
|
564 | 0 | if (cli_opts.proxycmd) { |
565 | 0 | total += m_snprintf(ret+total, len-total, "-J '%s' ", cli_opts.proxycmd); |
566 | 0 | } |
567 | |
|
568 | 0 | if (opts.recv_window != DEFAULT_RECV_WINDOW) { |
569 | 0 | total += m_snprintf(ret+total, len-total, "-W %u ", opts.recv_window); |
570 | 0 | } |
571 | |
|
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 | total += m_snprintf(ret+total, len-total, "-i %s ", key->filename); |
577 | 0 | } |
578 | 0 | #endif /* DROPBEAR_CLI_PUBKEY_AUTH */ |
579 | |
|
580 | 0 | return ret; |
581 | 0 | } |
582 | | |
583 | | /* Sets up 'onion-forwarding' connections. This will spawn |
584 | | * a separate dbclient process for each hop. |
585 | | * As an example, if the cmdline is |
586 | | * dbclient wrt,madako,canyons |
587 | | * then we want to run: |
588 | | * dbclient -J "dbclient -B canyons:22 wrt,madako" canyons |
589 | | * and then the inner dbclient will recursively run: |
590 | | * dbclient -J "dbclient -B madako:22 wrt" madako |
591 | | * etc for as many hosts as we want. |
592 | | * |
593 | | * Note that "-J" arguments aren't actually used, instead |
594 | | * below sets cli_opts.proxycmd directly. |
595 | | * |
596 | | * Ports for hosts can be specified as host/port. |
597 | | */ |
598 | 2 | static void parse_multihop_hostname(const char* orighostarg, const char* argv0) { |
599 | 2 | char *userhostarg = NULL; |
600 | 2 | char *hostbuf = NULL; |
601 | 2 | char *last_hop = NULL; |
602 | 2 | char *remainder = NULL; |
603 | | |
604 | | /* both scp and rsync parse a user@host argument |
605 | | * and turn it into "-l user host". This breaks |
606 | | * for our multihop syntax, so we suture it back together. |
607 | | * This will break usernames that have both '@' and ',' in them, |
608 | | * though that should be fairly uncommon. */ |
609 | 2 | if (cli_opts.username |
610 | 2 | && strchr(cli_opts.username, ',') |
611 | 2 | && strchr(cli_opts.username, '@')) { |
612 | 0 | unsigned int len = strlen(orighostarg) + strlen(cli_opts.username) + 2; |
613 | 0 | hostbuf = m_malloc(len); |
614 | 0 | m_snprintf(hostbuf, len, "%s@%s", cli_opts.username, orighostarg); |
615 | 2 | } else { |
616 | 2 | hostbuf = m_strdup(orighostarg); |
617 | 2 | } |
618 | 2 | userhostarg = hostbuf; |
619 | | |
620 | 2 | last_hop = strrchr(userhostarg, ','); |
621 | 2 | if (last_hop) { |
622 | 0 | if (last_hop == userhostarg) { |
623 | 0 | dropbear_exit("Bad multi-hop hostnames"); |
624 | 0 | } |
625 | 0 | *last_hop = '\0'; |
626 | 0 | last_hop++; |
627 | 0 | remainder = userhostarg; |
628 | 0 | userhostarg = last_hop; |
629 | 0 | } |
630 | | |
631 | 2 | parse_hostname(userhostarg); |
632 | | |
633 | 2 | if (last_hop) { |
634 | | /* Set up the proxycmd */ |
635 | 0 | unsigned int cmd_len = 0; |
636 | 0 | char *passthrough_args = multihop_passthrough_args(); |
637 | 0 | if (cli_opts.remoteport == NULL) { |
638 | 0 | cli_opts.remoteport = "22"; |
639 | 0 | } |
640 | 0 | cmd_len = strlen(argv0) + strlen(remainder) |
641 | 0 | + strlen(cli_opts.remotehost) + strlen(cli_opts.remoteport) |
642 | 0 | + strlen(passthrough_args) |
643 | 0 | + 30; |
644 | | /* replace proxycmd. old -J arguments have been copied |
645 | | to passthrough_args */ |
646 | 0 | cli_opts.proxycmd = m_realloc(cli_opts.proxycmd, cmd_len); |
647 | 0 | m_snprintf(cli_opts.proxycmd, cmd_len, "%s -B %s:%s %s %s", |
648 | 0 | argv0, cli_opts.remotehost, cli_opts.remoteport, |
649 | 0 | passthrough_args, remainder); |
650 | | #ifndef DISABLE_ZLIB |
651 | | /* The stream will be incompressible since it's encrypted. */ |
652 | | opts.compress_mode = DROPBEAR_COMPRESS_OFF; |
653 | | #endif |
654 | 0 | m_free(passthrough_args); |
655 | 0 | } |
656 | 2 | m_free(hostbuf); |
657 | 2 | } |
658 | | #endif /* !DROPBEAR_CLI_MULTIHOP */ |
659 | | |
660 | | /* Parses a [user@]hostname[/port] argument. */ |
661 | 2 | static void parse_hostname(const char* orighostarg) { |
662 | 2 | char *userhostarg = NULL; |
663 | 2 | char *port = NULL; |
664 | | |
665 | 2 | userhostarg = m_strdup(orighostarg); |
666 | | |
667 | 2 | cli_opts.remotehost = strchr(userhostarg, '@'); |
668 | 2 | if (cli_opts.remotehost == NULL) { |
669 | | /* no username portion, the cli-auth.c code can figure the |
670 | | * local user's name */ |
671 | 2 | cli_opts.remotehost = userhostarg; |
672 | 2 | } else { |
673 | 0 | cli_opts.remotehost[0] = '\0'; /* Split the user/host */ |
674 | 0 | cli_opts.remotehost++; |
675 | 0 | cli_opts.username = userhostarg; |
676 | 0 | } |
677 | | |
678 | 2 | if (cli_opts.username == NULL) { |
679 | 2 | cli_opts.username = m_strdup(cli_opts.own_user); |
680 | 2 | } |
681 | | |
682 | 2 | port = strchr(cli_opts.remotehost, '^'); |
683 | 2 | if (!port) { |
684 | | /* legacy separator */ |
685 | 2 | port = strchr(cli_opts.remotehost, '/'); |
686 | 2 | } |
687 | 2 | if (port) { |
688 | 0 | *port = '\0'; |
689 | 0 | cli_opts.remoteport = port+1; |
690 | 0 | } |
691 | | |
692 | 2 | if (cli_opts.remotehost[0] == '\0') { |
693 | 0 | dropbear_exit("Bad hostname"); |
694 | 0 | } |
695 | 2 | } |
696 | | |
697 | | #if DROPBEAR_CLI_NETCAT |
698 | 0 | static void add_netcat(const char* origstr) { |
699 | 0 | char *portstr = NULL; |
700 | | |
701 | 0 | char * str = m_strdup(origstr); |
702 | | |
703 | 0 | portstr = strchr(str, ':'); |
704 | 0 | if (portstr == NULL) { |
705 | 0 | TRACE(("No netcat port")) |
706 | 0 | goto fail; |
707 | 0 | } |
708 | 0 | *portstr = '\0'; |
709 | 0 | portstr++; |
710 | | |
711 | 0 | if (strchr(portstr, ':')) { |
712 | 0 | TRACE(("Multiple netcat colons")) |
713 | 0 | goto fail; |
714 | 0 | } |
715 | | |
716 | 0 | if (m_str_to_uint(portstr, &cli_opts.netcat_port) == DROPBEAR_FAILURE) { |
717 | 0 | TRACE(("bad netcat port")) |
718 | 0 | goto fail; |
719 | 0 | } |
720 | | |
721 | 0 | if (cli_opts.netcat_port > 65535) { |
722 | 0 | TRACE(("too large netcat port")) |
723 | 0 | goto fail; |
724 | 0 | } |
725 | | |
726 | 0 | cli_opts.netcat_host = str; |
727 | 0 | return; |
728 | | |
729 | 0 | fail: |
730 | 0 | dropbear_exit("Bad netcat endpoint '%s'", origstr); |
731 | 0 | } |
732 | | #endif |
733 | | |
734 | 2 | static void fill_own_user() { |
735 | 2 | uid_t uid; |
736 | 2 | struct passwd *pw = NULL; |
737 | | |
738 | 2 | uid = getuid(); |
739 | | |
740 | 2 | pw = getpwuid(uid); |
741 | 2 | if (pw && pw->pw_name != NULL) { |
742 | 2 | cli_opts.own_user = m_strdup(pw->pw_name); |
743 | 2 | } else { |
744 | 0 | dropbear_log(LOG_INFO, "Warning: failed to identify current user. Trying anyway."); |
745 | 0 | cli_opts.own_user = m_strdup("unknown"); |
746 | 0 | } |
747 | | |
748 | 2 | } |
749 | | |
750 | | #if DROPBEAR_CLI_ANYTCPFWD |
751 | | /* Turn a "[listenaddr:]listenport:remoteaddr:remoteport" string into into a forwarding |
752 | | * set, and add it to the forwarding list */ |
753 | 0 | static void addforward(const char* origstr, m_list *fwdlist) { |
754 | |
|
755 | 0 | char *part1 = NULL, *part2 = NULL, *part3 = NULL, *part4 = NULL; |
756 | 0 | char * listenaddr = NULL; |
757 | 0 | char * listenport = NULL; |
758 | 0 | char * connectaddr = NULL; |
759 | 0 | char * connectport = NULL; |
760 | 0 | struct TCPFwdEntry* newfwd = NULL; |
761 | 0 | char * str = NULL; |
762 | |
|
763 | 0 | TRACE(("enter addforward")) |
764 | | |
765 | | /* We need to split the original argument up. This var |
766 | | is never free()d. */ |
767 | 0 | str = m_strdup(origstr); |
768 | |
|
769 | 0 | part1 = str; |
770 | |
|
771 | 0 | part2 = strchr(str, ':'); |
772 | 0 | if (part2 == NULL) { |
773 | 0 | TRACE(("part2 == NULL")) |
774 | 0 | goto fail; |
775 | 0 | } |
776 | 0 | *part2 = '\0'; |
777 | 0 | part2++; |
778 | |
|
779 | 0 | part3 = strchr(part2, ':'); |
780 | 0 | if (part3 == NULL) { |
781 | 0 | TRACE(("part3 == NULL")) |
782 | 0 | goto fail; |
783 | 0 | } |
784 | 0 | *part3 = '\0'; |
785 | 0 | part3++; |
786 | |
|
787 | 0 | part4 = strchr(part3, ':'); |
788 | 0 | if (part4) { |
789 | 0 | *part4 = '\0'; |
790 | 0 | part4++; |
791 | 0 | } |
792 | |
|
793 | 0 | if (part4) { |
794 | 0 | listenaddr = part1; |
795 | 0 | listenport = part2; |
796 | 0 | connectaddr = part3; |
797 | 0 | connectport = part4; |
798 | 0 | } else { |
799 | 0 | listenaddr = NULL; |
800 | 0 | listenport = part1; |
801 | 0 | connectaddr = part2; |
802 | 0 | connectport = part3; |
803 | 0 | } |
804 | |
|
805 | 0 | newfwd = m_malloc(sizeof(struct TCPFwdEntry)); |
806 | | |
807 | | /* Now we check the ports - note that the port ints are unsigned, |
808 | | * the check later only checks for >= MAX_PORT */ |
809 | 0 | if (m_str_to_uint(listenport, &newfwd->listenport) == DROPBEAR_FAILURE) { |
810 | 0 | TRACE(("bad listenport strtoul")) |
811 | 0 | goto fail; |
812 | 0 | } |
813 | | |
814 | 0 | if (m_str_to_uint(connectport, &newfwd->connectport) == DROPBEAR_FAILURE) { |
815 | 0 | TRACE(("bad connectport strtoul")) |
816 | 0 | goto fail; |
817 | 0 | } |
818 | | |
819 | 0 | newfwd->listenaddr = listenaddr; |
820 | 0 | newfwd->connectaddr = connectaddr; |
821 | |
|
822 | 0 | if (newfwd->listenport > 65535) { |
823 | 0 | TRACE(("listenport > 65535")) |
824 | 0 | goto badport; |
825 | 0 | } |
826 | | |
827 | 0 | if (newfwd->connectport > 65535) { |
828 | 0 | TRACE(("connectport > 65535")) |
829 | 0 | goto badport; |
830 | 0 | } |
831 | | |
832 | 0 | newfwd->have_reply = 0; |
833 | 0 | list_append(fwdlist, newfwd); |
834 | |
|
835 | 0 | TRACE(("leave addforward: done")) |
836 | 0 | return; |
837 | | |
838 | 0 | fail: |
839 | 0 | dropbear_exit("Bad TCP forward '%s'", origstr); |
840 | | |
841 | 0 | badport: |
842 | 0 | dropbear_exit("Bad TCP port in '%s'", origstr); |
843 | 0 | } |
844 | | #endif |
845 | | |
846 | 0 | static int match_extendedopt(const char** strptr, const char *optname) { |
847 | 0 | int seen_eq = 0; |
848 | 0 | int optlen = strlen(optname); |
849 | 0 | const char *str = *strptr; |
850 | |
|
851 | 0 | while (isspace(*str)) { |
852 | 0 | ++str; |
853 | 0 | } |
854 | |
|
855 | 0 | if (strncasecmp(str, optname, optlen) != 0) { |
856 | 0 | return DROPBEAR_FAILURE; |
857 | 0 | } |
858 | | |
859 | 0 | str += optlen; |
860 | |
|
861 | 0 | while (isspace(*str) || (!seen_eq && *str == '=')) { |
862 | 0 | if (*str == '=') { |
863 | 0 | seen_eq = 1; |
864 | 0 | } |
865 | 0 | ++str; |
866 | 0 | } |
867 | |
|
868 | 0 | if (str-*strptr == optlen) { |
869 | | /* matched just a prefix of optname */ |
870 | 0 | return DROPBEAR_FAILURE; |
871 | 0 | } |
872 | | |
873 | 0 | *strptr = str; |
874 | 0 | return DROPBEAR_SUCCESS; |
875 | 0 | } |
876 | | |
877 | 0 | static int parse_flag_value(const char *value) { |
878 | 0 | if (strcmp(value, "yes") == 0 || strcmp(value, "true") == 0) { |
879 | 0 | return 1; |
880 | 0 | } else if (strcmp(value, "no") == 0 || strcmp(value, "false") == 0) { |
881 | 0 | return 0; |
882 | 0 | } |
883 | | |
884 | 0 | dropbear_exit("Bad yes/no argument '%s'", value); |
885 | 0 | } |
886 | | |
887 | 0 | static void add_extendedopt(const char* origstr) { |
888 | 0 | const char *optstr = origstr; |
889 | |
|
890 | 0 | if (strcmp(origstr, "help") == 0) { |
891 | 0 | dropbear_log(LOG_INFO, "Available options:\n" |
892 | 0 | #if DROPBEAR_CLI_ANYTCPFWD |
893 | 0 | "\tExitOnForwardFailure\n" |
894 | 0 | #endif |
895 | 0 | "\tDisableTrivialAuth\n" |
896 | 0 | #ifndef DISABLE_SYSLOG |
897 | 0 | "\tUseSyslog\n" |
898 | 0 | #endif |
899 | 0 | "\tPort\n" |
900 | 0 | ); |
901 | 0 | exit(EXIT_SUCCESS); |
902 | 0 | } |
903 | | |
904 | 0 | #if DROPBEAR_CLI_ANYTCPFWD |
905 | 0 | if (match_extendedopt(&optstr, "ExitOnForwardFailure") == DROPBEAR_SUCCESS) { |
906 | 0 | cli_opts.exit_on_fwd_failure = parse_flag_value(optstr); |
907 | 0 | return; |
908 | 0 | } |
909 | 0 | #endif |
910 | | |
911 | 0 | #ifndef DISABLE_SYSLOG |
912 | 0 | if (match_extendedopt(&optstr, "UseSyslog") == DROPBEAR_SUCCESS) { |
913 | 0 | opts.usingsyslog = parse_flag_value(optstr); |
914 | 0 | return; |
915 | 0 | } |
916 | 0 | #endif |
917 | | |
918 | 0 | if (match_extendedopt(&optstr, "Port") == DROPBEAR_SUCCESS) { |
919 | 0 | cli_opts.remoteport = optstr; |
920 | 0 | return; |
921 | 0 | } |
922 | | |
923 | 0 | if (match_extendedopt(&optstr, "DisableTrivialAuth") == DROPBEAR_SUCCESS) { |
924 | 0 | cli_opts.disable_trivial_auth = parse_flag_value(optstr); |
925 | 0 | return; |
926 | 0 | } |
927 | | |
928 | 0 | dropbear_log(LOG_WARNING, "Ignoring unknown configuration option '%s'", origstr); |
929 | 0 | } |