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