1 /*
2 * PLink - a Windows command-line (stdin/stdout) variant of PuTTY.
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <assert.h>
8 #include <stdarg.h>
9
10 #include "putty.h"
11 #include "storage.h"
12 #include "tree234.h"
13 #include "winsecur.h"
14
cmdline_error(const char * fmt,...)15 void cmdline_error(const char *fmt, ...)
16 {
17 va_list ap;
18 va_start(ap, fmt);
19 console_print_error_msg_fmt_v("plink", fmt, ap);
20 va_end(ap);
21 exit(1);
22 }
23
24 static HANDLE inhandle, outhandle, errhandle;
25 static struct handle *stdin_handle, *stdout_handle, *stderr_handle;
26 static handle_sink stdout_hs, stderr_hs;
27 static StripCtrlChars *stdout_scc, *stderr_scc;
28 static BinarySink *stdout_bs, *stderr_bs;
29 static DWORD orig_console_mode;
30
31 static Backend *backend;
32 static LogContext *logctx;
33 static Conf *conf;
34
plink_echoedit_update(Seat * seat,bool echo,bool edit)35 static void plink_echoedit_update(Seat *seat, bool echo, bool edit)
36 {
37 /* Update stdin read mode to reflect changes in line discipline. */
38 DWORD mode;
39
40 mode = ENABLE_PROCESSED_INPUT;
41 if (echo)
42 mode = mode | ENABLE_ECHO_INPUT;
43 else
44 mode = mode & ~ENABLE_ECHO_INPUT;
45 if (edit)
46 mode = mode | ENABLE_LINE_INPUT;
47 else
48 mode = mode & ~ENABLE_LINE_INPUT;
49 SetConsoleMode(inhandle, mode);
50 }
51
plink_output(Seat * seat,bool is_stderr,const void * data,size_t len)52 static size_t plink_output(
53 Seat *seat, bool is_stderr, const void *data, size_t len)
54 {
55 BinarySink *bs = is_stderr ? stderr_bs : stdout_bs;
56 put_data(bs, data, len);
57
58 return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);
59 }
60
plink_eof(Seat * seat)61 static bool plink_eof(Seat *seat)
62 {
63 handle_write_eof(stdout_handle);
64 return false; /* do not respond to incoming EOF with outgoing */
65 }
66
plink_get_userpass_input(Seat * seat,prompts_t * p,bufchain * input)67 static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input)
68 {
69 int ret;
70 ret = cmdline_get_passwd_input(p);
71 if (ret == -1)
72 ret = console_get_userpass_input(p);
73 return ret;
74 }
75
plink_seat_interactive(Seat * seat)76 static bool plink_seat_interactive(Seat *seat)
77 {
78 return (!*conf_get_str(conf, CONF_remote_cmd) &&
79 !*conf_get_str(conf, CONF_remote_cmd2) &&
80 !*conf_get_str(conf, CONF_ssh_nc_host));
81 }
82
83 static const SeatVtable plink_seat_vt = {
84 .output = plink_output,
85 .eof = plink_eof,
86 .get_userpass_input = plink_get_userpass_input,
87 .notify_remote_exit = nullseat_notify_remote_exit,
88 .connection_fatal = console_connection_fatal,
89 .update_specials_menu = nullseat_update_specials_menu,
90 .get_ttymode = nullseat_get_ttymode,
91 .set_busy_status = nullseat_set_busy_status,
92 .verify_ssh_host_key = console_verify_ssh_host_key,
93 .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive,
94 .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey,
95 .is_utf8 = nullseat_is_never_utf8,
96 .echoedit_update = plink_echoedit_update,
97 .get_x_display = nullseat_get_x_display,
98 .get_windowid = nullseat_get_windowid,
99 .get_window_pixel_size = nullseat_get_window_pixel_size,
100 .stripctrl_new = console_stripctrl_new,
101 .set_trust_status = console_set_trust_status,
102 .verbose = cmdline_seat_verbose,
103 .interactive = plink_seat_interactive,
104 .get_cursor_position = nullseat_get_cursor_position,
105 };
106 static Seat plink_seat[1] = {{ &plink_seat_vt }};
107
108 static DWORD main_thread_id;
109
110 /*
111 * Short description of parameters.
112 */
usage(void)113 static void usage(void)
114 {
115 printf("Plink: command-line connection utility\n");
116 printf("%s\n", ver);
117 printf("Usage: plink [options] [user@]host [command]\n");
118 printf(" (\"host\" can also be a PuTTY saved session name)\n");
119 printf("Options:\n");
120 printf(" -V print version information and exit\n");
121 printf(" -pgpfp print PGP key fingerprints and exit\n");
122 printf(" -v show verbose messages\n");
123 printf(" -load sessname Load settings from saved session\n");
124 printf(" -ssh -telnet -rlogin -raw -serial\n");
125 printf(" force use of a particular protocol\n");
126 printf(" -ssh-connection\n");
127 printf(" force use of the bare ssh-connection protocol\n");
128 printf(" -P port connect to specified port\n");
129 printf(" -l user connect with specified username\n");
130 printf(" -batch disable all interactive prompts\n");
131 printf(" -proxycmd command\n");
132 printf(" use 'command' as local proxy\n");
133 printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
134 printf(" Specify the serial configuration (serial only)\n");
135 printf("The following options only apply to SSH connections:\n");
136 printf(" -pw passw login with specified password\n");
137 printf(" -D [listen-IP:]listen-port\n");
138 printf(" Dynamic SOCKS-based port forwarding\n");
139 printf(" -L [listen-IP:]listen-port:host:port\n");
140 printf(" Forward local port to remote address\n");
141 printf(" -R [listen-IP:]listen-port:host:port\n");
142 printf(" Forward remote port to local address\n");
143 printf(" -X -x enable / disable X11 forwarding\n");
144 printf(" -A -a enable / disable agent forwarding\n");
145 printf(" -t -T enable / disable pty allocation\n");
146 printf(" -1 -2 force use of particular SSH protocol version\n");
147 printf(" -4 -6 force use of IPv4 or IPv6\n");
148 printf(" -C enable compression\n");
149 printf(" -i key private key file for user authentication\n");
150 printf(" -noagent disable use of Pageant\n");
151 printf(" -agent enable use of Pageant\n");
152 printf(" -no-trivial-auth\n");
153 printf(" disconnect if SSH authentication succeeds trivially\n");
154 printf(" -noshare disable use of connection sharing\n");
155 printf(" -share enable use of connection sharing\n");
156 printf(" -hostkey keyid\n");
157 printf(" manually specify a host key (may be repeated)\n");
158 printf(" -sanitise-stderr, -sanitise-stdout, "
159 "-no-sanitise-stderr, -no-sanitise-stdout\n");
160 printf(" do/don't strip control chars from standard "
161 "output/error\n");
162 printf(" -no-antispoof omit anti-spoofing prompt after "
163 "authentication\n");
164 printf(" -m file read remote command(s) from file\n");
165 printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
166 printf(" -N don't start a shell/command (SSH-2 only)\n");
167 printf(" -nc host:port\n");
168 printf(" open tunnel in place of session (SSH-2 only)\n");
169 printf(" -sshlog file\n");
170 printf(" -sshrawlog file\n");
171 printf(" log protocol details to a file\n");
172 printf(" -logoverwrite\n");
173 printf(" -logappend\n");
174 printf(" control what happens when a log file already exists\n");
175 printf(" -shareexists\n");
176 printf(" test whether a connection-sharing upstream exists\n");
177 exit(1);
178 }
179
version(void)180 static void version(void)
181 {
182 char *buildinfo_text = buildinfo("\n");
183 printf("plink: %s\n%s\n", ver, buildinfo_text);
184 sfree(buildinfo_text);
185 exit(0);
186 }
187
stdin_gotdata(struct handle * h,const void * data,size_t len,int err)188 size_t stdin_gotdata(struct handle *h, const void *data, size_t len, int err)
189 {
190 if (err) {
191 char buf[4096];
192 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0,
193 buf, lenof(buf), NULL);
194 buf[lenof(buf)-1] = '\0';
195 if (buf[strlen(buf)-1] == '\n')
196 buf[strlen(buf)-1] = '\0';
197 fprintf(stderr, "Unable to read from standard input: %s\n", buf);
198 cleanup_exit(0);
199 }
200
201 noise_ultralight(NOISE_SOURCE_IOLEN, len);
202 if (backend_connected(backend)) {
203 if (len > 0) {
204 return backend_send(backend, data, len);
205 } else {
206 backend_special(backend, SS_EOF, 0);
207 return 0;
208 }
209 } else
210 return 0;
211 }
212
stdouterr_sent(struct handle * h,size_t new_backlog,int err)213 void stdouterr_sent(struct handle *h, size_t new_backlog, int err)
214 {
215 if (err) {
216 char buf[4096];
217 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0,
218 buf, lenof(buf), NULL);
219 buf[lenof(buf)-1] = '\0';
220 if (buf[strlen(buf)-1] == '\n')
221 buf[strlen(buf)-1] = '\0';
222 fprintf(stderr, "Unable to write to standard %s: %s\n",
223 (h == stdout_handle ? "output" : "error"), buf);
224 cleanup_exit(0);
225 }
226
227 if (backend_connected(backend)) {
228 backend_unthrottle(backend, (handle_backlog(stdout_handle) +
229 handle_backlog(stderr_handle)));
230 }
231 }
232
233 const bool share_can_be_downstream = true;
234 const bool share_can_be_upstream = true;
235
236 const unsigned cmdline_tooltype =
237 TOOLTYPE_HOST_ARG |
238 TOOLTYPE_HOST_ARG_CAN_BE_SESSION |
239 TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX |
240 TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD;
241
242 static bool sending;
243
plink_mainloop_pre(void * vctx,const HANDLE ** extra_handles,size_t * n_extra_handles)244 static bool plink_mainloop_pre(void *vctx, const HANDLE **extra_handles,
245 size_t *n_extra_handles)
246 {
247 if (!sending && backend_sendok(backend)) {
248 stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL,
249 0);
250 sending = true;
251 }
252
253 return true;
254 }
255
plink_mainloop_post(void * vctx,size_t extra_handle_index)256 static bool plink_mainloop_post(void *vctx, size_t extra_handle_index)
257 {
258 if (sending)
259 handle_unthrottle(stdin_handle, backend_sendbuffer(backend));
260
261 if (!backend_connected(backend) &&
262 handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)
263 return false; /* we closed the connection */
264
265 return true;
266 }
267
main(int argc,char ** argv)268 int main(int argc, char **argv)
269 {
270 int exitcode;
271 bool errors;
272 bool use_subsystem = false;
273 bool just_test_share_exists = false;
274 enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO;
275 const struct BackendVtable *vt;
276
277 dll_hijacking_protection();
278
279 /*
280 * Initialise port and protocol to sensible defaults. (These
281 * will be overridden by more or less anything.)
282 */
283 settings_set_default_protocol(PROT_SSH);
284 settings_set_default_port(22);
285
286 /*
287 * Process the command line.
288 */
289 conf = conf_new();
290 do_defaults(NULL, conf);
291 settings_set_default_protocol(conf_get_int(conf, CONF_protocol));
292 settings_set_default_port(conf_get_int(conf, CONF_port));
293 errors = false;
294 {
295 /*
296 * Override the default protocol if PLINK_PROTOCOL is set.
297 */
298 char *p = getenv("PLINK_PROTOCOL");
299 if (p) {
300 const struct BackendVtable *vt = backend_vt_from_name(p);
301 if (vt) {
302 settings_set_default_protocol(vt->protocol);
303 settings_set_default_port(vt->default_port);
304 conf_set_int(conf, CONF_protocol, vt->protocol);
305 conf_set_int(conf, CONF_port, vt->default_port);
306 }
307 }
308 }
309 while (--argc) {
310 char *p = *++argv;
311 int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
312 1, conf);
313 if (ret == -2) {
314 fprintf(stderr,
315 "plink: option \"%s\" requires an argument\n", p);
316 errors = true;
317 } else if (ret == 2) {
318 --argc, ++argv;
319 } else if (ret == 1) {
320 continue;
321 } else if (!strcmp(p, "-batch")) {
322 console_batch_mode = true;
323 } else if (!strcmp(p, "-s")) {
324 /* Save status to write to conf later. */
325 use_subsystem = true;
326 } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
327 version();
328 } else if (!strcmp(p, "--help")) {
329 usage();
330 } else if (!strcmp(p, "-pgpfp")) {
331 pgp_fingerprints();
332 exit(1);
333 } else if (!strcmp(p, "-shareexists")) {
334 just_test_share_exists = true;
335 } else if (!strcmp(p, "-sanitise-stdout") ||
336 !strcmp(p, "-sanitize-stdout")) {
337 sanitise_stdout = FORCE_ON;
338 } else if (!strcmp(p, "-no-sanitise-stdout") ||
339 !strcmp(p, "-no-sanitize-stdout")) {
340 sanitise_stdout = FORCE_OFF;
341 } else if (!strcmp(p, "-sanitise-stderr") ||
342 !strcmp(p, "-sanitize-stderr")) {
343 sanitise_stderr = FORCE_ON;
344 } else if (!strcmp(p, "-no-sanitise-stderr") ||
345 !strcmp(p, "-no-sanitize-stderr")) {
346 sanitise_stderr = FORCE_OFF;
347 } else if (!strcmp(p, "-no-antispoof")) {
348 console_antispoof_prompt = false;
349 } else if (*p != '-') {
350 strbuf *cmdbuf = strbuf_new();
351
352 while (argc > 0) {
353 if (cmdbuf->len > 0)
354 put_byte(cmdbuf, ' '); /* add space separator */
355 put_datapl(cmdbuf, ptrlen_from_asciz(p));
356 if (--argc > 0)
357 p = *++argv;
358 }
359
360 conf_set_str(conf, CONF_remote_cmd, cmdbuf->s);
361 conf_set_str(conf, CONF_remote_cmd2, "");
362 conf_set_bool(conf, CONF_nopty, true); /* command => no tty */
363
364 strbuf_free(cmdbuf);
365 break; /* done with cmdline */
366 } else {
367 fprintf(stderr, "plink: unknown option \"%s\"\n", p);
368 errors = true;
369 }
370 }
371
372 if (errors)
373 return 1;
374
375 if (!cmdline_host_ok(conf)) {
376 usage();
377 }
378
379 prepare_session(conf);
380
381 /*
382 * Perform command-line overrides on session configuration.
383 */
384 cmdline_run_saved(conf);
385
386 /*
387 * Apply subsystem status.
388 */
389 if (use_subsystem)
390 conf_set_bool(conf, CONF_ssh_subsys, true);
391
392 /*
393 * Select protocol. This is farmed out into a table in a
394 * separate file to enable an ssh-free variant.
395 */
396 vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
397 if (vt == NULL) {
398 fprintf(stderr,
399 "Internal fault: Unsupported protocol found\n");
400 return 1;
401 }
402
403 if (vt->flags & BACKEND_NEEDS_TERMINAL) {
404 fprintf(stderr,
405 "Plink doesn't support %s, which needs terminal emulation\n",
406 vt->displayname);
407 return 1;
408 }
409
410 sk_init();
411 if (p_WSAEventSelect == NULL) {
412 fprintf(stderr, "Plink requires WinSock 2\n");
413 return 1;
414 }
415
416 /*
417 * Plink doesn't provide any way to add forwardings after the
418 * connection is set up, so if there are none now, we can safely set
419 * the "simple" flag.
420 */
421 if (conf_get_int(conf, CONF_protocol) == PROT_SSH &&
422 !conf_get_bool(conf, CONF_x11_forward) &&
423 !conf_get_bool(conf, CONF_agentfwd) &&
424 !conf_get_str_nthstrkey(conf, CONF_portfwd, 0))
425 conf_set_bool(conf, CONF_ssh_simple, true);
426
427 logctx = log_init(console_cli_logpolicy, conf);
428
429 if (just_test_share_exists) {
430 if (!vt->test_for_upstream) {
431 fprintf(stderr, "Connection sharing not supported for this "
432 "connection type (%s)'\n", vt->displayname);
433 return 1;
434 }
435 if (vt->test_for_upstream(conf_get_str(conf, CONF_host),
436 conf_get_int(conf, CONF_port), conf))
437 return 0;
438 else
439 return 1;
440 }
441
442 if (restricted_acl()) {
443 lp_eventlog(console_cli_logpolicy,
444 "Running with restricted process ACL");
445 }
446
447 inhandle = GetStdHandle(STD_INPUT_HANDLE);
448 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
449 errhandle = GetStdHandle(STD_ERROR_HANDLE);
450
451 /*
452 * Turn off ECHO and LINE input modes. We don't care if this
453 * call fails, because we know we aren't necessarily running in
454 * a console.
455 */
456 GetConsoleMode(inhandle, &orig_console_mode);
457 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
458
459 /*
460 * Pass the output handles to the handle-handling subsystem.
461 * (The input one we leave until we're through the
462 * authentication process.)
463 */
464 stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
465 stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
466 handle_sink_init(&stdout_hs, stdout_handle);
467 handle_sink_init(&stderr_hs, stderr_handle);
468 stdout_bs = BinarySink_UPCAST(&stdout_hs);
469 stderr_bs = BinarySink_UPCAST(&stderr_hs);
470
471 /*
472 * Decide whether to sanitise control sequences out of standard
473 * output and standard error.
474 *
475 * If we weren't given a command-line override, we do this if (a)
476 * the fd in question is pointing at a console, and (b) we aren't
477 * trying to allocate a terminal as part of the session.
478 *
479 * (Rationale: the risk of control sequences is that they cause
480 * confusion when sent to a local console, so if there isn't one,
481 * no problem. Also, if we allocate a remote terminal, then we
482 * sent a terminal type, i.e. we told it what kind of escape
483 * sequences we _like_, i.e. we were expecting to receive some.)
484 */
485 if (sanitise_stdout == FORCE_ON ||
486 (sanitise_stdout == AUTO && is_console_handle(outhandle) &&
487 conf_get_bool(conf, CONF_nopty))) {
488 stdout_scc = stripctrl_new(stdout_bs, true, L'\0');
489 stdout_bs = BinarySink_UPCAST(stdout_scc);
490 }
491 if (sanitise_stderr == FORCE_ON ||
492 (sanitise_stderr == AUTO && is_console_handle(errhandle) &&
493 conf_get_bool(conf, CONF_nopty))) {
494 stderr_scc = stripctrl_new(stderr_bs, true, L'\0');
495 stderr_bs = BinarySink_UPCAST(stderr_scc);
496 }
497
498 /*
499 * Start up the connection.
500 */
501 winselcli_setup(); /* ensure event object exists */
502 {
503 char *error, *realhost;
504 /* nodelay is only useful if stdin is a character device (console) */
505 bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) &&
506 (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
507
508 error = backend_init(vt, plink_seat, &backend, logctx, conf,
509 conf_get_str(conf, CONF_host),
510 conf_get_int(conf, CONF_port),
511 &realhost, nodelay,
512 conf_get_bool(conf, CONF_tcp_keepalives));
513 if (error) {
514 fprintf(stderr, "Unable to open connection:\n%s", error);
515 sfree(error);
516 return 1;
517 }
518 ldisc_create(conf, NULL, backend, plink_seat);
519 sfree(realhost);
520 }
521
522 main_thread_id = GetCurrentThreadId();
523
524 sending = false;
525
526 cli_main_loop(plink_mainloop_pre, plink_mainloop_post, NULL);
527
528 exitcode = backend_exitcode(backend);
529 if (exitcode < 0) {
530 fprintf(stderr, "Remote process exit code unavailable\n");
531 exitcode = 1; /* this is an error condition */
532 }
533 cleanup_exit(exitcode);
534 return 0; /* placate compiler warning */
535 }
536