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