]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Fix a subtle bug affecting multiple-socket handling in Plink. (Was
[PuTTY.git] / plink.c
1 /*
2  * PLink - a command-line (stdin/stdout) variant of PuTTY.
3  */
4
5 #ifndef AUTO_WINSOCK
6 #include <winsock2.h>
7 #endif
8 #include <windows.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <stdarg.h>
12
13 #define PUTTY_DO_GLOBALS                       /* actually _define_ globals */
14 #include "putty.h"
15 #include "storage.h"
16 #include "tree234.h"
17
18 void fatalbox (char *p, ...) {
19     va_list ap;
20     fprintf(stderr, "FATAL ERROR: ");
21     va_start(ap, p);
22     vfprintf(stderr, p, ap);
23     va_end(ap);
24     fputc('\n', stderr);
25     WSACleanup();
26     exit(1);
27 }
28 void connection_fatal (char *p, ...) {
29     va_list ap;
30     fprintf(stderr, "FATAL ERROR: ");
31     va_start(ap, p);
32     vfprintf(stderr, p, ap);
33     va_end(ap);
34     fputc('\n', stderr);
35     WSACleanup();
36     exit(1);
37 }
38
39 static char *password = NULL;
40
41 void logevent(char *string) { }
42
43 void verify_ssh_host_key(char *host, int port, char *keytype,
44                          char *keystr, char *fingerprint) {
45     int ret;
46     HANDLE hin;
47     DWORD savemode, i;
48
49     static const char absentmsg[] =
50         "The server's host key is not cached in the registry. You\n"
51         "have no guarantee that the server is the computer you\n"
52         "think it is.\n"
53         "The server's key fingerprint is:\n"
54         "%s\n"
55         "If you trust this host, enter \"y\" to add the key to\n"
56         "PuTTY's cache and carry on connecting.\n"
57         "If you do not trust this host, enter \"n\" to abandon the\n"
58         "connection.\n"
59         "Continue connecting? (y/n) ";
60
61     static const char wrongmsg[] =
62         "WARNING - POTENTIAL SECURITY BREACH!\n"
63         "The server's host key does not match the one PuTTY has\n"
64         "cached in the registry. This means that either the\n"
65         "server administrator has changed the host key, or you\n"
66         "have actually connected to another computer pretending\n"
67         "to be the server.\n"
68         "The new key fingerprint is:\n"
69         "%s\n"
70         "If you were expecting this change and trust the new key,\n"
71         "enter \"y\" to update PuTTY's cache and continue connecting.\n"
72         "If you want to carry on connecting but without updating\n"
73         "the cache, enter \"n\".\n"
74         "If you want to abandon the connection completely, press\n"
75         "Return to cancel. Pressing Return is the ONLY guaranteed\n"
76         "safe choice.\n"
77         "Update cached key? (y/n, Return cancels connection) ";
78
79     static const char abandoned[] = "Connection abandoned.\n";
80
81     char line[32];
82
83     /*
84      * Verify the key against the registry.
85      */
86     ret = verify_host_key(host, port, keytype, keystr);
87
88     if (ret == 0)                      /* success - key matched OK */
89         return;
90
91     if (ret == 2)                      /* key was different */
92         fprintf(stderr, wrongmsg, fingerprint);
93     if (ret == 1)                      /* key was absent */
94         fprintf(stderr, absentmsg, fingerprint);
95
96     hin = GetStdHandle(STD_INPUT_HANDLE);
97     GetConsoleMode(hin, &savemode);
98     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
99                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
100     ReadFile(hin, line, sizeof(line)-1, &i, NULL);
101     SetConsoleMode(hin, savemode);
102
103     if (ret == 2) {                    /* key was different */
104         if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
105             if (line[0] == 'y' || line[0] == 'Y')
106                 store_host_key(host, port, keytype, keystr);
107         } else {
108             fprintf(stderr, abandoned);
109             exit(0);
110         }
111     }
112     if (ret == 1) {                    /* key was absent */
113         if (line[0] == 'y' || line[0] == 'Y')
114             store_host_key(host, port, keytype, keystr);
115         else {
116             fprintf(stderr, abandoned);
117             exit(0);
118         }
119     }
120 }
121
122 HANDLE outhandle, errhandle;
123 DWORD orig_console_mode;
124
125 WSAEVENT netevent;
126
127 void begin_session(void) {
128     if (!cfg.ldisc_term)
129         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
130     else
131         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
132 }
133
134 void from_backend(int is_stderr, char *data, int len) {
135     int pos;
136     DWORD ret;
137     HANDLE h = (is_stderr ? errhandle : outhandle);
138
139     pos = 0;
140     while (pos < len) {
141         if (!WriteFile(h, data+pos, len-pos, &ret, NULL))
142             return;                    /* give up in panic */
143         pos += ret;
144     }
145 }
146
147 struct input_data {
148     DWORD len;
149     char buffer[4096];
150     HANDLE event, eventback;
151 };
152
153 static int get_password(const char *prompt, char *str, int maxlen)
154 {
155     HANDLE hin, hout;
156     DWORD savemode, i;
157
158     if (password) {
159         static int tried_once = 0;
160
161         if (tried_once) {
162             return 0;
163         } else {
164             strncpy(str, password, maxlen);
165             str[maxlen-1] = '\0';
166             tried_once = 1;
167             return 1;
168         }
169     }
170
171     hin = GetStdHandle(STD_INPUT_HANDLE);
172     hout = GetStdHandle(STD_OUTPUT_HANDLE);
173     if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
174         fprintf(stderr, "Cannot get standard input/output handles");
175         return 0;
176     }
177
178     GetConsoleMode(hin, &savemode);
179     SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
180                    ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
181
182     WriteFile(hout, prompt, strlen(prompt), &i, NULL);
183     ReadFile(hin, str, maxlen-1, &i, NULL);
184
185     SetConsoleMode(hin, savemode);
186
187     if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
188     str[i] = '\0';
189
190     WriteFile(hout, "\r\n", 2, &i, NULL);
191
192     return 1;
193 }
194
195 static DWORD WINAPI stdin_read_thread(void *param) {
196     struct input_data *idata = (struct input_data *)param;
197     HANDLE inhandle;
198
199     inhandle = GetStdHandle(STD_INPUT_HANDLE);
200
201     while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
202                     &idata->len, NULL) && idata->len > 0) {
203         SetEvent(idata->event);
204         WaitForSingleObject(idata->eventback, INFINITE);
205     }
206
207     idata->len = 0;
208     SetEvent(idata->event);
209
210     return 0;
211 }
212
213 /*
214  *  Short description of parameters.
215  */
216 static void usage(void)
217 {
218     printf("PuTTY Link: command-line connection utility\n");
219     printf("%s\n", ver);
220     printf("Usage: plink [options] [user@]host [command]\n");
221     printf("Options:\n");
222     printf("  -v        show verbose messages\n");
223     printf("  -ssh      force use of ssh protocol\n");
224     printf("  -P port   connect to specified port\n");
225     printf("  -pw passw login with specified password\n");
226     exit(1);
227 }
228
229 char *do_select(SOCKET skt, int startup) {
230     int events;
231     if (startup) {
232         events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
233     } else {
234         events = 0;
235     }
236     if (WSAEventSelect (skt, netevent, events) == SOCKET_ERROR) {
237         switch (WSAGetLastError()) {
238           case WSAENETDOWN: return "Network is down";
239           default: return "WSAAsyncSelect(): unknown error";
240         }
241     }
242     return NULL;
243 }
244
245 int main(int argc, char **argv) {
246     WSADATA wsadata;
247     WORD winsock_ver;
248     WSAEVENT stdinevent;
249     HANDLE handles[2];
250     DWORD threadid;
251     struct input_data idata;
252     int sending;
253     int portnumber = -1;
254     SOCKET *sklist;
255     int skcount, sksize;
256     int connopen;
257
258     ssh_get_password = get_password;
259
260     sklist = NULL; skcount = sksize = 0;
261
262     flags = FLAG_STDERR;
263     /*
264      * Process the command line.
265      */
266     do_defaults(NULL, &cfg);
267     default_protocol = cfg.protocol;
268     default_port = cfg.port;
269     {
270         /*
271          * Override the default protocol if PLINK_PROTOCOL is set.
272          */
273         char *p = getenv("PLINK_PROTOCOL");
274         int i;
275         if (p) {
276             for (i = 0; backends[i].backend != NULL; i++) {
277                 if (!strcmp(backends[i].name, p)) {
278                     default_protocol = cfg.protocol = backends[i].protocol;
279                     default_port = cfg.port = backends[i].backend->default_port;
280                     break;
281                 }
282             }
283         }
284     }
285     while (--argc) {
286         char *p = *++argv;
287         if (*p == '-') {
288             if (!strcmp(p, "-ssh")) {
289                 default_protocol = cfg.protocol = PROT_SSH;
290                 default_port = cfg.port = 22;
291             } else if (!strcmp(p, "-telnet")) {
292                 default_protocol = cfg.protocol = PROT_TELNET;
293                 default_port = cfg.port = 23;
294             } else if (!strcmp(p, "-raw")) {
295                 default_protocol = cfg.protocol = PROT_RAW;
296             } else if (!strcmp(p, "-v")) {
297                 flags |= FLAG_VERBOSE;
298             } else if (!strcmp(p, "-log")) {
299                 logfile = "putty.log";
300             } else if (!strcmp(p, "-pw") && argc > 1) {
301                 --argc, password = *++argv;
302             } else if (!strcmp(p, "-l") && argc > 1) {
303                 char *username;
304                 --argc, username = *++argv;
305                 strncpy(cfg.username, username, sizeof(cfg.username));
306                 cfg.username[sizeof(cfg.username)-1] = '\0';
307             } else if (!strcmp(p, "-P") && argc > 1) {
308                 --argc, portnumber = atoi(*++argv);
309             }
310         } else if (*p) {
311             if (!*cfg.host) {
312                 char *q = p;
313                 /*
314                  * If the hostname starts with "telnet:", set the
315                  * protocol to Telnet and process the string as a
316                  * Telnet URL.
317                  */
318                 if (!strncmp(q, "telnet:", 7)) {
319                     char c;
320
321                     q += 7;
322                     if (q[0] == '/' && q[1] == '/')
323                         q += 2;
324                     cfg.protocol = PROT_TELNET;
325                     p = q;
326                     while (*p && *p != ':' && *p != '/') p++;
327                     c = *p;
328                     if (*p)
329                         *p++ = '\0';
330                     if (c == ':')
331                         cfg.port = atoi(p);
332                     else
333                         cfg.port = -1;
334                     strncpy (cfg.host, q, sizeof(cfg.host)-1);
335                     cfg.host[sizeof(cfg.host)-1] = '\0';
336                 } else {
337                     char *r;
338                     /*
339                      * Before we process the [user@]host string, we
340                      * first check for the presence of a protocol
341                      * prefix (a protocol name followed by ",").
342                      */
343                     r = strchr(p, ',');
344                     if (r) {
345                         int i, j;
346                         for (i = 0; backends[i].backend != NULL; i++) {
347                             j = strlen(backends[i].name);
348                             if (j == r-p &&
349                                 !memcmp(backends[i].name, p, j)) {
350                                 default_protocol = cfg.protocol = backends[i].protocol;
351                                 portnumber = backends[i].backend->default_port;
352                                 p = r+1;
353                                 break;
354                             }
355                         }
356                     }
357
358                     /*
359                      * Three cases. Either (a) there's a nonzero
360                      * length string followed by an @, in which
361                      * case that's user and the remainder is host.
362                      * Or (b) there's only one string, not counting
363                      * a potential initial @, and it exists in the
364                      * saved-sessions database. Or (c) only one
365                      * string and it _doesn't_ exist in the
366                      * database.
367                      */
368                     r = strrchr(p, '@');
369                     if (r == p) p++, r = NULL;   /* discount initial @ */
370                     if (r == NULL) {
371                         /*
372                          * One string.
373                          */
374                         Config cfg2;
375                         do_defaults (p, &cfg2);
376                         if (cfg2.host[0] == '\0') {
377                             /* No settings for this host; use defaults */
378                             strncpy(cfg.host, p, sizeof(cfg.host)-1);
379                             cfg.host[sizeof(cfg.host)-1] = '\0';
380                             cfg.port = 22;
381                         } else
382                             cfg = cfg2;
383                     } else {
384                         *r++ = '\0';
385                         strncpy(cfg.username, p, sizeof(cfg.username)-1);
386                         cfg.username[sizeof(cfg.username)-1] = '\0';
387                         strncpy(cfg.host, r, sizeof(cfg.host)-1);
388                         cfg.host[sizeof(cfg.host)-1] = '\0';
389                         cfg.port = 22;
390                     }
391                 }
392             } else {
393                 int len = sizeof(cfg.remote_cmd) - 1;
394                 char *cp = cfg.remote_cmd;
395                 int len2;
396
397                 strncpy(cp, p, len); cp[len] = '\0';
398                 len2 = strlen(cp); len -= len2; cp += len2;
399                 while (--argc) {
400                     if (len > 0)
401                         len--, *cp++ = ' ';
402                     strncpy(cp, *++argv, len); cp[len] = '\0';
403                     len2 = strlen(cp); len -= len2; cp += len2;
404                 }
405                 cfg.nopty = TRUE;      /* command => no terminal */
406                 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
407                 break;                 /* done with cmdline */
408             }
409         }
410     }
411
412     if (!*cfg.host) {
413         usage();
414     }
415
416     if (!*cfg.remote_cmd)
417         flags |= FLAG_INTERACTIVE;
418
419     /*
420      * Select protocol. This is farmed out into a table in a
421      * separate file to enable an ssh-free variant.
422      */
423     {
424         int i;
425         back = NULL;
426         for (i = 0; backends[i].backend != NULL; i++)
427             if (backends[i].protocol == cfg.protocol) {
428                 back = backends[i].backend;
429                 break;
430             }
431         if (back == NULL) {
432             fprintf(stderr, "Internal fault: Unsupported protocol found\n");
433             return 1;
434         }
435     }
436
437     /*
438      * Select port.
439      */
440     if (portnumber != -1)
441         cfg.port = portnumber;
442
443     /*
444      * Initialise WinSock.
445      */
446     winsock_ver = MAKEWORD(2, 0);
447     if (WSAStartup(winsock_ver, &wsadata)) {
448         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
449                    MB_OK | MB_ICONEXCLAMATION);
450         return 1;
451     }
452     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
453         MessageBox(NULL, "WinSock version is incompatible with 2.0",
454                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
455         WSACleanup();
456         return 1;
457     }
458     sk_init();
459
460     /*
461      * Start up the connection.
462      */
463     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
464     {
465         char *error;
466         char *realhost;
467
468         error = back->init (cfg.host, cfg.port, &realhost);
469         if (error) {
470             fprintf(stderr, "Unable to open connection:\n%s", error);
471             return 1;
472         }
473     }
474     connopen = 1;
475
476     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
477
478     GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
479     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
480     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
481     errhandle = GetStdHandle(STD_ERROR_HANDLE);
482
483     /*
484      * Turn off ECHO and LINE input modes. We don't care if this
485      * call fails, because we know we aren't necessarily running in
486      * a console.
487      */
488     handles[0] = netevent;
489     handles[1] = stdinevent;
490     sending = FALSE;
491     while (1) {
492         int n;
493
494         if (!sending && back->sendok()) {
495             /*
496              * Create a separate thread to read from stdin. This is
497              * a total pain, but I can't find another way to do it:
498              *
499              *  - an overlapped ReadFile or ReadFileEx just doesn't
500              *    happen; we get failure from ReadFileEx, and
501              *    ReadFile blocks despite being given an OVERLAPPED
502              *    structure. Perhaps we can't do overlapped reads
503              *    on consoles. WHY THE HELL NOT?
504              * 
505              *  - WaitForMultipleObjects(netevent, console) doesn't
506              *    work, because it signals the console when
507              *    _anything_ happens, including mouse motions and
508              *    other things that don't cause data to be readable
509              *    - so we're back to ReadFile blocking.
510              */
511             idata.event = stdinevent;
512             idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
513             if (!CreateThread(NULL, 0, stdin_read_thread,
514                               &idata, 0, &threadid)) {
515                 fprintf(stderr, "Unable to create second thread\n");
516                 exit(1);
517             }
518             sending = TRUE;
519         }
520
521         n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
522         if (n == 0) {
523             WSANETWORKEVENTS things;
524             enum234 e;
525             SOCKET socket;
526             extern SOCKET first_socket(enum234 *), next_socket(enum234 *);
527             extern int select_result(WPARAM, LPARAM);
528             int i;
529
530             /*
531              * We must not call select_result() for any socket
532              * until we have finished enumerating within the tree.
533              * This is because select_result() may close the socket
534              * and modify the tree.
535              */
536             /* Count the active sockets. */
537             i = 0;
538             for (socket = first_socket(&e); socket != INVALID_SOCKET;
539                  socket = next_socket(&e))
540                 i++;
541
542             /* Expand the buffer if necessary. */
543             if (i > sksize) {
544                 sksize = i+16;
545                 sklist = srealloc(sklist, sksize * sizeof(*sklist));
546             }
547
548             /* Retrieve the sockets into sklist. */
549             skcount = 0;
550             for (socket = first_socket(&e); socket != INVALID_SOCKET;
551                  socket = next_socket(&e)) {
552                 sklist[skcount++] = socket;
553             }
554
555             /* Now we're done enumerating; go through the list. */
556             for (i = 0; i < skcount; i++) {
557                 WPARAM wp;
558                 socket = sklist[i];
559                 wp = (WPARAM)socket;
560                 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
561                     noise_ultralight(socket);
562                     noise_ultralight(things.lNetworkEvents);
563                     if (things.lNetworkEvents & FD_READ)
564                         connopen &= select_result(wp, (LPARAM)FD_READ);
565                     if (things.lNetworkEvents & FD_CLOSE)
566                         connopen &= select_result(wp, (LPARAM)FD_CLOSE);
567                     if (things.lNetworkEvents & FD_OOB)
568                         connopen &= select_result(wp, (LPARAM)FD_OOB);
569                     if (things.lNetworkEvents & FD_WRITE)
570                         connopen &= select_result(wp, (LPARAM)FD_WRITE);
571                 }
572             }
573         } else if (n == 1) {
574             noise_ultralight(idata.len);
575             if (idata.len > 0) {
576                 back->send(idata.buffer, idata.len);
577             } else {
578                 back->special(TS_EOF);
579             }
580             SetEvent(idata.eventback);
581         }
582         if (!connopen || back->socket() == NULL)
583             break;                 /* we closed the connection */
584     }
585     WSACleanup();
586     return 0;
587 }