]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/winplink.c
New piece of Windows infrastructure: winhandl.c takes Plink's
[PuTTY.git] / windows / winplink.c
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 #define PUTTY_DO_GLOBALS               /* actually _define_ globals */
11 #include "putty.h"
12 #include "storage.h"
13 #include "tree234.h"
14
15 #define WM_AGENT_CALLBACK (WM_APP + 4)
16
17 struct agent_callback {
18     void (*callback)(void *, void *, int);
19     void *callback_ctx;
20     void *data;
21     int len;
22 };
23
24 void fatalbox(char *p, ...)
25 {
26     va_list ap;
27     fprintf(stderr, "FATAL ERROR: ");
28     va_start(ap, p);
29     vfprintf(stderr, p, ap);
30     va_end(ap);
31     fputc('\n', stderr);
32     cleanup_exit(1);
33 }
34 void modalfatalbox(char *p, ...)
35 {
36     va_list ap;
37     fprintf(stderr, "FATAL ERROR: ");
38     va_start(ap, p);
39     vfprintf(stderr, p, ap);
40     va_end(ap);
41     fputc('\n', stderr);
42     cleanup_exit(1);
43 }
44 void connection_fatal(void *frontend, char *p, ...)
45 {
46     va_list ap;
47     fprintf(stderr, "FATAL ERROR: ");
48     va_start(ap, p);
49     vfprintf(stderr, p, ap);
50     va_end(ap);
51     fputc('\n', stderr);
52     cleanup_exit(1);
53 }
54 void cmdline_error(char *p, ...)
55 {
56     va_list ap;
57     fprintf(stderr, "plink: ");
58     va_start(ap, p);
59     vfprintf(stderr, p, ap);
60     va_end(ap);
61     fputc('\n', stderr);
62     exit(1);
63 }
64
65 HANDLE inhandle, outhandle, errhandle;
66 struct handle *stdin_handle, *stdout_handle, *stderr_handle;
67 DWORD orig_console_mode;
68 int connopen;
69
70 WSAEVENT netevent;
71
72 static Backend *back;
73 static void *backhandle;
74 static Config cfg;
75
76 int term_ldisc(Terminal *term, int mode)
77 {
78     return FALSE;
79 }
80 void ldisc_update(void *frontend, int echo, int edit)
81 {
82     /* Update stdin read mode to reflect changes in line discipline. */
83     DWORD mode;
84
85     mode = ENABLE_PROCESSED_INPUT;
86     if (echo)
87         mode = mode | ENABLE_ECHO_INPUT;
88     else
89         mode = mode & ~ENABLE_ECHO_INPUT;
90     if (edit)
91         mode = mode | ENABLE_LINE_INPUT;
92     else
93         mode = mode & ~ENABLE_LINE_INPUT;
94     SetConsoleMode(inhandle, mode);
95 }
96
97 char *get_ttymode(void *frontend, const char *mode) { return NULL; }
98
99 int from_backend(void *frontend_handle, int is_stderr,
100                  const char *data, int len)
101 {
102     if (is_stderr) {
103         handle_write(stderr_handle, data, len);
104     } else {
105         handle_write(stdout_handle, data, len);
106     }
107
108     return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);
109 }
110
111 int from_backend_untrusted(void *frontend_handle, const char *data, int len)
112 {
113     /*
114      * No "untrusted" output should get here (the way the code is
115      * currently, it's all diverted by FLAG_STDERR).
116      */
117     assert(!"Unexpected call to from_backend_untrusted()");
118     return 0; /* not reached */
119 }
120
121 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
122 {
123     int ret;
124     ret = cmdline_get_passwd_input(p, in, inlen);
125     if (ret == -1)
126         ret = console_get_userpass_input(p, in, inlen);
127     return ret;
128 }
129
130 static DWORD main_thread_id;
131
132 void agent_schedule_callback(void (*callback)(void *, void *, int),
133                              void *callback_ctx, void *data, int len)
134 {
135     struct agent_callback *c = snew(struct agent_callback);
136     c->callback = callback;
137     c->callback_ctx = callback_ctx;
138     c->data = data;
139     c->len = len;
140     PostThreadMessage(main_thread_id, WM_AGENT_CALLBACK, 0, (LPARAM)c);
141 }
142
143 /*
144  *  Short description of parameters.
145  */
146 static void usage(void)
147 {
148     printf("PuTTY Link: command-line connection utility\n");
149     printf("%s\n", ver);
150     printf("Usage: plink [options] [user@]host [command]\n");
151     printf("       (\"host\" can also be a PuTTY saved session name)\n");
152     printf("Options:\n");
153     printf("  -V        print version information and exit\n");
154     printf("  -pgpfp    print PGP key fingerprints and exit\n");
155     printf("  -v        show verbose messages\n");
156     printf("  -load sessname  Load settings from saved session\n");
157     printf("  -ssh -telnet -rlogin -raw\n");
158     printf("            force use of a particular protocol\n");
159     printf("  -P port   connect to specified port\n");
160     printf("  -l user   connect with specified username\n");
161     printf("  -batch    disable all interactive prompts\n");
162     printf("The following options only apply to SSH connections:\n");
163     printf("  -pw passw login with specified password\n");
164     printf("  -D [listen-IP:]listen-port\n");
165     printf("            Dynamic SOCKS-based port forwarding\n");
166     printf("  -L [listen-IP:]listen-port:host:port\n");
167     printf("            Forward local port to remote address\n");
168     printf("  -R [listen-IP:]listen-port:host:port\n");
169     printf("            Forward remote port to local address\n");
170     printf("  -X -x     enable / disable X11 forwarding\n");
171     printf("  -A -a     enable / disable agent forwarding\n");
172     printf("  -t -T     enable / disable pty allocation\n");
173     printf("  -1 -2     force use of particular protocol version\n");
174     printf("  -4 -6     force use of IPv4 or IPv6\n");
175     printf("  -C        enable compression\n");
176     printf("  -i key    private key file for authentication\n");
177     printf("  -noagent  disable use of Pageant\n");
178     printf("  -agent    enable use of Pageant\n");
179     printf("  -m file   read remote command(s) from file\n");
180     printf("  -s        remote command is an SSH subsystem (SSH-2 only)\n");
181     printf("  -N        don't start a shell/command (SSH-2 only)\n");
182     exit(1);
183 }
184
185 static void version(void)
186 {
187     printf("plink: %s\n", ver);
188     exit(1);
189 }
190
191 char *do_select(SOCKET skt, int startup)
192 {
193     int events;
194     if (startup) {
195         events = (FD_CONNECT | FD_READ | FD_WRITE |
196                   FD_OOB | FD_CLOSE | FD_ACCEPT);
197     } else {
198         events = 0;
199     }
200     if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
201         switch (p_WSAGetLastError()) {
202           case WSAENETDOWN:
203             return "Network is down";
204           default:
205             return "WSAEventSelect(): unknown error";
206         }
207     }
208     return NULL;
209 }
210
211 int stdin_gotdata(struct handle *h, void *data, int len)
212 {
213     if (len < 0) {
214         /*
215          * Special case: report read error.
216          */
217         fprintf(stderr, "Unable to read from standard input\n");
218         cleanup_exit(0);
219     }
220     noise_ultralight(len);
221     if (connopen && back->socket(backhandle) != NULL) {
222         if (len > 0) {
223             return back->send(backhandle, data, len);
224         } else {
225             back->special(backhandle, TS_EOF);
226             return 0;
227         }
228     } else
229         return 0;
230 }
231
232 void stdouterr_sent(struct handle *h, int new_backlog)
233 {
234     if (new_backlog < 0) {
235         /*
236          * Special case: report write error.
237          */
238         fprintf(stderr, "Unable to write to standard %s\n",
239                 (h == stdout_handle ? "output" : "error"));
240         cleanup_exit(0);
241     }
242     if (connopen && back->socket(backhandle) != NULL) {
243         back->unthrottle(backhandle, (handle_backlog(stdout_handle) +
244                                       handle_backlog(stderr_handle)));
245     }
246 }
247
248 int main(int argc, char **argv)
249 {
250     int sending;
251     int portnumber = -1;
252     SOCKET *sklist;
253     int skcount, sksize;
254     int exitcode;
255     int errors;
256     int use_subsystem = 0;
257     long now, next;
258
259     sklist = NULL;
260     skcount = sksize = 0;
261     /*
262      * Initialise port and protocol to sensible defaults. (These
263      * will be overridden by more or less anything.)
264      */
265     default_protocol = PROT_SSH;
266     default_port = 22;
267
268     flags = FLAG_STDERR;
269     /*
270      * Process the command line.
271      */
272     do_defaults(NULL, &cfg);
273     loaded_session = FALSE;
274     default_protocol = cfg.protocol;
275     default_port = cfg.port;
276     errors = 0;
277     {
278         /*
279          * Override the default protocol if PLINK_PROTOCOL is set.
280          */
281         char *p = getenv("PLINK_PROTOCOL");
282         int i;
283         if (p) {
284             for (i = 0; backends[i].backend != NULL; i++) {
285                 if (!strcmp(backends[i].name, p)) {
286                     default_protocol = cfg.protocol = backends[i].protocol;
287                     default_port = cfg.port =
288                         backends[i].backend->default_port;
289                     break;
290                 }
291             }
292         }
293     }
294     while (--argc) {
295         char *p = *++argv;
296         if (*p == '-') {
297             int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
298                                             1, &cfg);
299             if (ret == -2) {
300                 fprintf(stderr,
301                         "plink: option \"%s\" requires an argument\n", p);
302                 errors = 1;
303             } else if (ret == 2) {
304                 --argc, ++argv;
305             } else if (ret == 1) {
306                 continue;
307             } else if (!strcmp(p, "-batch")) {
308                 console_batch_mode = 1;
309             } else if (!strcmp(p, "-s")) {
310                 /* Save status to write to cfg later. */
311                 use_subsystem = 1;
312             } else if (!strcmp(p, "-V")) {
313                 version();
314             } else if (!strcmp(p, "-pgpfp")) {
315                 pgp_fingerprints();
316                 exit(1);
317             } else {
318                 fprintf(stderr, "plink: unknown option \"%s\"\n", p);
319                 errors = 1;
320             }
321         } else if (*p) {
322             if (!*cfg.host) {
323                 char *q = p;
324                 /*
325                  * If the hostname starts with "telnet:", set the
326                  * protocol to Telnet and process the string as a
327                  * Telnet URL.
328                  */
329                 if (!strncmp(q, "telnet:", 7)) {
330                     char c;
331
332                     q += 7;
333                     if (q[0] == '/' && q[1] == '/')
334                         q += 2;
335                     cfg.protocol = PROT_TELNET;
336                     p = q;
337                     while (*p && *p != ':' && *p != '/')
338                         p++;
339                     c = *p;
340                     if (*p)
341                         *p++ = '\0';
342                     if (c == ':')
343                         cfg.port = atoi(p);
344                     else
345                         cfg.port = -1;
346                     strncpy(cfg.host, q, sizeof(cfg.host) - 1);
347                     cfg.host[sizeof(cfg.host) - 1] = '\0';
348                 } else {
349                     char *r, *user, *host;
350                     /*
351                      * Before we process the [user@]host string, we
352                      * first check for the presence of a protocol
353                      * prefix (a protocol name followed by ",").
354                      */
355                     r = strchr(p, ',');
356                     if (r) {
357                         int i, j;
358                         for (i = 0; backends[i].backend != NULL; i++) {
359                             j = strlen(backends[i].name);
360                             if (j == r - p &&
361                                 !memcmp(backends[i].name, p, j)) {
362                                 default_protocol = cfg.protocol =
363                                     backends[i].protocol;
364                                 portnumber =
365                                     backends[i].backend->default_port;
366                                 p = r + 1;
367                                 break;
368                             }
369                         }
370                     }
371
372                     /*
373                      * A nonzero length string followed by an @ is treated
374                      * as a username. (We discount an _initial_ @.) The
375                      * rest of the string (or the whole string if no @)
376                      * is treated as a session name and/or hostname.
377                      */
378                     r = strrchr(p, '@');
379                     if (r == p)
380                         p++, r = NULL; /* discount initial @ */
381                     if (r) {
382                         *r++ = '\0';
383                         user = p, host = r;
384                     } else {
385                         user = NULL, host = p;
386                     }
387
388                     /*
389                      * Now attempt to load a saved session with the
390                      * same name as the hostname.
391                      */
392                     {
393                         Config cfg2;
394                         do_defaults(host, &cfg2);
395                         if (loaded_session || cfg2.host[0] == '\0') {
396                             /* No settings for this host; use defaults */
397                             /* (or session was already loaded with -load) */
398                             strncpy(cfg.host, host, sizeof(cfg.host) - 1);
399                             cfg.host[sizeof(cfg.host) - 1] = '\0';
400                             cfg.port = default_port;
401                         } else {
402                             cfg = cfg2;
403                         }
404                     }
405
406                     if (user) {
407                         /* Patch in specified username. */
408                         strncpy(cfg.username, user,
409                                 sizeof(cfg.username) - 1);
410                         cfg.username[sizeof(cfg.username) - 1] = '\0';
411                     }
412
413                 }
414             } else {
415                 char *command;
416                 int cmdlen, cmdsize;
417                 cmdlen = cmdsize = 0;
418                 command = NULL;
419
420                 while (argc) {
421                     while (*p) {
422                         if (cmdlen >= cmdsize) {
423                             cmdsize = cmdlen + 512;
424                             command = sresize(command, cmdsize, char);
425                         }
426                         command[cmdlen++]=*p++;
427                     }
428                     if (cmdlen >= cmdsize) {
429                         cmdsize = cmdlen + 512;
430                         command = sresize(command, cmdsize, char);
431                     }
432                     command[cmdlen++]=' '; /* always add trailing space */
433                     if (--argc) p = *++argv;
434                 }
435                 if (cmdlen) command[--cmdlen]='\0';
436                                        /* change trailing blank to NUL */
437                 cfg.remote_cmd_ptr = command;
438                 cfg.remote_cmd_ptr2 = NULL;
439                 cfg.nopty = TRUE;      /* command => no terminal */
440
441                 break;                 /* done with cmdline */
442             }
443         }
444     }
445
446     if (errors)
447         return 1;
448
449     if (!*cfg.host) {
450         usage();
451     }
452
453     /*
454      * Trim leading whitespace off the hostname if it's there.
455      */
456     {
457         int space = strspn(cfg.host, " \t");
458         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
459     }
460
461     /* See if host is of the form user@host */
462     if (cfg.host[0] != '\0') {
463         char *atsign = strrchr(cfg.host, '@');
464         /* Make sure we're not overflowing the user field */
465         if (atsign) {
466             if (atsign - cfg.host < sizeof cfg.username) {
467                 strncpy(cfg.username, cfg.host, atsign - cfg.host);
468                 cfg.username[atsign - cfg.host] = '\0';
469             }
470             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
471         }
472     }
473
474     /*
475      * Perform command-line overrides on session configuration.
476      */
477     cmdline_run_saved(&cfg);
478
479     /*
480      * Apply subsystem status.
481      */
482     if (use_subsystem)
483         cfg.ssh_subsys = TRUE;
484
485     /*
486      * Trim a colon suffix off the hostname if it's there.
487      */
488     cfg.host[strcspn(cfg.host, ":")] = '\0';
489
490     /*
491      * Remove any remaining whitespace from the hostname.
492      */
493     {
494         int p1 = 0, p2 = 0;
495         while (cfg.host[p2] != '\0') {
496             if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
497                 cfg.host[p1] = cfg.host[p2];
498                 p1++;
499             }
500             p2++;
501         }
502         cfg.host[p1] = '\0';
503     }
504
505     if (!cfg.remote_cmd_ptr && !*cfg.remote_cmd)
506         flags |= FLAG_INTERACTIVE;
507
508     /*
509      * Select protocol. This is farmed out into a table in a
510      * separate file to enable an ssh-free variant.
511      */
512     {
513         int i;
514         back = NULL;
515         for (i = 0; backends[i].backend != NULL; i++)
516             if (backends[i].protocol == cfg.protocol) {
517                 back = backends[i].backend;
518                 break;
519             }
520         if (back == NULL) {
521             fprintf(stderr,
522                     "Internal fault: Unsupported protocol found\n");
523             return 1;
524         }
525     }
526
527     /*
528      * Select port.
529      */
530     if (portnumber != -1)
531         cfg.port = portnumber;
532
533     sk_init();
534     if (p_WSAEventSelect == NULL) {
535         fprintf(stderr, "Plink requires WinSock 2\n");
536         return 1;
537     }
538
539     /*
540      * Start up the connection.
541      */
542     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
543     {
544         const char *error;
545         char *realhost;
546         /* nodelay is only useful if stdin is a character device (console) */
547         int nodelay = cfg.tcp_nodelay &&
548             (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
549
550         error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port,
551                            &realhost, nodelay, cfg.tcp_keepalives);
552         if (error) {
553             fprintf(stderr, "Unable to open connection:\n%s", error);
554             return 1;
555         }
556         logctx = log_init(NULL, &cfg);
557         back->provide_logctx(backhandle, logctx);
558         console_provide_logctx(logctx);
559         sfree(realhost);
560     }
561     connopen = 1;
562
563     inhandle = GetStdHandle(STD_INPUT_HANDLE);
564     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
565     errhandle = GetStdHandle(STD_ERROR_HANDLE);
566
567     /*
568      * Turn off ECHO and LINE input modes. We don't care if this
569      * call fails, because we know we aren't necessarily running in
570      * a console.
571      */
572     GetConsoleMode(inhandle, &orig_console_mode);
573     SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
574
575     /*
576      * Pass the output handles to the handle-handling subsystem.
577      * (The input one we leave until we're through the
578      * authentication process.)
579      */
580     stdout_handle = handle_output_new(outhandle, stdouterr_sent);
581     stderr_handle = handle_output_new(errhandle, stdouterr_sent);
582
583     main_thread_id = GetCurrentThreadId();
584
585     sending = FALSE;
586
587     now = GETTICKCOUNT();
588
589     while (1) {
590         int nhandles;
591         HANDLE *handles;        
592         int n;
593         DWORD ticks;
594
595         if (!sending && back->sendok(backhandle)) {
596             stdin_handle = handle_input_new(inhandle, stdin_gotdata);
597             sending = TRUE;
598         }
599
600         if (run_timers(now, &next)) {
601             ticks = next - GETTICKCOUNT();
602             if (ticks < 0) ticks = 0;  /* just in case */
603         } else {
604             ticks = INFINITE;
605         }
606
607         handles = handle_get_events(&nhandles);
608         handles = sresize(handles, nhandles+1, HANDLE);
609         handles[nhandles] = netevent;
610         n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks,
611                                       QS_POSTMESSAGE);
612         if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
613             handle_got_event(handles[n - WAIT_OBJECT_0]);
614         } else if (n == WAIT_OBJECT_0 + nhandles) {
615             WSANETWORKEVENTS things;
616             SOCKET socket;
617             extern SOCKET first_socket(int *), next_socket(int *);
618             extern int select_result(WPARAM, LPARAM);
619             int i, socketstate;
620
621             /*
622              * We must not call select_result() for any socket
623              * until we have finished enumerating within the tree.
624              * This is because select_result() may close the socket
625              * and modify the tree.
626              */
627             /* Count the active sockets. */
628             i = 0;
629             for (socket = first_socket(&socketstate);
630                  socket != INVALID_SOCKET;
631                  socket = next_socket(&socketstate)) i++;
632
633             /* Expand the buffer if necessary. */
634             if (i > sksize) {
635                 sksize = i + 16;
636                 sklist = sresize(sklist, sksize, SOCKET);
637             }
638
639             /* Retrieve the sockets into sklist. */
640             skcount = 0;
641             for (socket = first_socket(&socketstate);
642                  socket != INVALID_SOCKET;
643                  socket = next_socket(&socketstate)) {
644                 sklist[skcount++] = socket;
645             }
646
647             /* Now we're done enumerating; go through the list. */
648             for (i = 0; i < skcount; i++) {
649                 WPARAM wp;
650                 socket = sklist[i];
651                 wp = (WPARAM) socket;
652                 if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
653                     static const struct { int bit, mask; } eventtypes[] = {
654                         {FD_CONNECT_BIT, FD_CONNECT},
655                         {FD_READ_BIT, FD_READ},
656                         {FD_CLOSE_BIT, FD_CLOSE},
657                         {FD_OOB_BIT, FD_OOB},
658                         {FD_WRITE_BIT, FD_WRITE},
659                         {FD_ACCEPT_BIT, FD_ACCEPT},
660                     };
661                     int e;
662
663                     noise_ultralight(socket);
664                     noise_ultralight(things.lNetworkEvents);
665
666                     for (e = 0; e < lenof(eventtypes); e++)
667                         if (things.lNetworkEvents & eventtypes[e].mask) {
668                             LPARAM lp;
669                             int err = things.iErrorCode[eventtypes[e].bit];
670                             lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
671                             connopen &= select_result(wp, lp);
672                         }
673                 }
674             }
675         } else if (n == WAIT_OBJECT_0 + nhandles + 1) {
676             MSG msg;
677             while (PeekMessage(&msg, INVALID_HANDLE_VALUE,
678                                WM_AGENT_CALLBACK, WM_AGENT_CALLBACK,
679                                PM_REMOVE)) {
680                 struct agent_callback *c = (struct agent_callback *)msg.lParam;
681                 c->callback(c->callback_ctx, c->data, c->len);
682                 sfree(c);
683             }
684         }
685
686         if (n == WAIT_TIMEOUT) {
687             now = next;
688         } else {
689             now = GETTICKCOUNT();
690         }
691
692         sfree(handles);
693
694         if (sending)
695             handle_unthrottle(stdin_handle, back->sendbuffer(backhandle));
696
697         if ((!connopen || back->socket(backhandle) == NULL) &&
698             handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)
699             break;                     /* we closed the connection */
700     }
701     exitcode = back->exitcode(backhandle);
702     if (exitcode < 0) {
703         fprintf(stderr, "Remote process exit code unavailable\n");
704         exitcode = 1;                  /* this is an error condition */
705     }
706     cleanup_exit(exitcode);
707     return 0;                          /* placate compiler warning */
708 }