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