]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Make sure the default protocol doesn't ever end up undefined in Plink.
[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      * Initialise port and protocol to sensible defaults. (These
294      * will be overridden by more or less anything.)
295      */
296     default_protocol = PROT_SSH;
297     default_port = 22;
298
299     flags = FLAG_STDERR;
300     /*
301      * Process the command line.
302      */
303     do_defaults(NULL, &cfg);
304     default_protocol = cfg.protocol;
305     default_port = cfg.port;
306     {
307         /*
308          * Override the default protocol if PLINK_PROTOCOL is set.
309          */
310         char *p = getenv("PLINK_PROTOCOL");
311         int i;
312         if (p) {
313             for (i = 0; backends[i].backend != NULL; i++) {
314                 if (!strcmp(backends[i].name, p)) {
315                     default_protocol = cfg.protocol = backends[i].protocol;
316                     default_port = cfg.port =
317                         backends[i].backend->default_port;
318                     break;
319                 }
320             }
321         }
322     }
323     while (--argc) {
324         char *p = *++argv;
325         if (*p == '-') {
326             if (!strcmp(p, "-ssh")) {
327                 default_protocol = cfg.protocol = PROT_SSH;
328                 default_port = cfg.port = 22;
329             } else if (!strcmp(p, "-telnet")) {
330                 default_protocol = cfg.protocol = PROT_TELNET;
331                 default_port = cfg.port = 23;
332             } else if (!strcmp(p, "-raw")) {
333                 default_protocol = cfg.protocol = PROT_RAW;
334             } else if (!strcmp(p, "-v")) {
335                 flags |= FLAG_VERBOSE;
336             } else if (!strcmp(p, "-log")) {
337                 logfile = "putty.log";
338             } else if (!strcmp(p, "-pw") && argc > 1) {
339                 --argc, password = *++argv;
340             } else if (!strcmp(p, "-l") && argc > 1) {
341                 char *username;
342                 --argc, username = *++argv;
343                 strncpy(cfg.username, username, sizeof(cfg.username));
344                 cfg.username[sizeof(cfg.username) - 1] = '\0';
345             } else if (!strcmp(p, "-m") && argc > 1) {
346                 char *filename, *command;
347                 int cmdlen, cmdsize;
348                 FILE *fp;
349                 int c, d;
350
351                 --argc, filename = *++argv;
352
353                 cmdlen = cmdsize = 0;
354                 command = NULL;
355                 fp = fopen(filename, "r");
356                 if (!fp) {
357                     fprintf(stderr, "plink: unable to open command "
358                             "file \"%s\"\n", filename);
359                     return 1;
360                 }
361                 do {
362                     c = fgetc(fp);
363                     d = c;
364                     if (c == EOF)
365                         d = 0;
366                     if (cmdlen >= cmdsize) {
367                         cmdsize = cmdlen + 512;
368                         command = srealloc(command, cmdsize);
369                     }
370                     command[cmdlen++] = d;
371                 } while (c != EOF);
372                 cfg.remote_cmd_ptr = command;
373                 cfg.nopty = TRUE;      /* command => no terminal */
374             } else if (!strcmp(p, "-P") && argc > 1) {
375                 --argc, portnumber = atoi(*++argv);
376             }
377         } else if (*p) {
378             if (!*cfg.host) {
379                 char *q = p;
380                 /*
381                  * If the hostname starts with "telnet:", set the
382                  * protocol to Telnet and process the string as a
383                  * Telnet URL.
384                  */
385                 if (!strncmp(q, "telnet:", 7)) {
386                     char c;
387
388                     q += 7;
389                     if (q[0] == '/' && q[1] == '/')
390                         q += 2;
391                     cfg.protocol = PROT_TELNET;
392                     p = q;
393                     while (*p && *p != ':' && *p != '/')
394                         p++;
395                     c = *p;
396                     if (*p)
397                         *p++ = '\0';
398                     if (c == ':')
399                         cfg.port = atoi(p);
400                     else
401                         cfg.port = -1;
402                     strncpy(cfg.host, q, sizeof(cfg.host) - 1);
403                     cfg.host[sizeof(cfg.host) - 1] = '\0';
404                 } else {
405                     char *r;
406                     /*
407                      * Before we process the [user@]host string, we
408                      * first check for the presence of a protocol
409                      * prefix (a protocol name followed by ",").
410                      */
411                     r = strchr(p, ',');
412                     if (r) {
413                         int i, j;
414                         for (i = 0; backends[i].backend != NULL; i++) {
415                             j = strlen(backends[i].name);
416                             if (j == r - p &&
417                                 !memcmp(backends[i].name, p, j)) {
418                                 default_protocol = cfg.protocol =
419                                     backends[i].protocol;
420                                 portnumber =
421                                     backends[i].backend->default_port;
422                                 p = r + 1;
423                                 break;
424                             }
425                         }
426                     }
427
428                     /*
429                      * Three cases. Either (a) there's a nonzero
430                      * length string followed by an @, in which
431                      * case that's user and the remainder is host.
432                      * Or (b) there's only one string, not counting
433                      * a potential initial @, and it exists in the
434                      * saved-sessions database. Or (c) only one
435                      * string and it _doesn't_ exist in the
436                      * database.
437                      */
438                     r = strrchr(p, '@');
439                     if (r == p)
440                         p++, r = NULL; /* discount initial @ */
441                     if (r == NULL) {
442                         /*
443                          * One string.
444                          */
445                         Config cfg2;
446                         do_defaults(p, &cfg2);
447                         if (cfg2.host[0] == '\0') {
448                             /* No settings for this host; use defaults */
449                             strncpy(cfg.host, p, sizeof(cfg.host) - 1);
450                             cfg.host[sizeof(cfg.host) - 1] = '\0';
451                             cfg.port = default_port;
452                         } else {
453                             cfg = cfg2;
454                             cfg.remote_cmd_ptr = cfg.remote_cmd;
455                         }
456                     } else {
457                         *r++ = '\0';
458                         strncpy(cfg.username, p, sizeof(cfg.username) - 1);
459                         cfg.username[sizeof(cfg.username) - 1] = '\0';
460                         strncpy(cfg.host, r, sizeof(cfg.host) - 1);
461                         cfg.host[sizeof(cfg.host) - 1] = '\0';
462                         cfg.port = default_port;
463                     }
464                 }
465             } else {
466                 int len = sizeof(cfg.remote_cmd) - 1;
467                 char *cp = cfg.remote_cmd;
468                 int len2;
469
470                 strncpy(cp, p, len);
471                 cp[len] = '\0';
472                 len2 = strlen(cp);
473                 len -= len2;
474                 cp += len2;
475                 while (--argc) {
476                     if (len > 0)
477                         len--, *cp++ = ' ';
478                     strncpy(cp, *++argv, len);
479                     cp[len] = '\0';
480                     len2 = strlen(cp);
481                     len -= len2;
482                     cp += len2;
483                 }
484                 cfg.nopty = TRUE;      /* command => no terminal */
485                 break;                 /* done with cmdline */
486             }
487         }
488     }
489
490     if (!*cfg.host) {
491         usage();
492     }
493
494     if (!*cfg.remote_cmd_ptr)
495         flags |= FLAG_INTERACTIVE;
496
497     /*
498      * Select protocol. This is farmed out into a table in a
499      * separate file to enable an ssh-free variant.
500      */
501     {
502         int i;
503         back = NULL;
504         for (i = 0; backends[i].backend != NULL; i++)
505             if (backends[i].protocol == cfg.protocol) {
506                 back = backends[i].backend;
507                 break;
508             }
509         if (back == NULL) {
510             fprintf(stderr,
511                     "Internal fault: Unsupported protocol found\n");
512             return 1;
513         }
514     }
515
516     /*
517      * Select port.
518      */
519     if (portnumber != -1)
520         cfg.port = portnumber;
521
522     /*
523      * Initialise WinSock.
524      */
525     winsock_ver = MAKEWORD(2, 0);
526     if (WSAStartup(winsock_ver, &wsadata)) {
527         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
528                    MB_OK | MB_ICONEXCLAMATION);
529         return 1;
530     }
531     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
532         MessageBox(NULL, "WinSock version is incompatible with 2.0",
533                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
534         WSACleanup();
535         return 1;
536     }
537     sk_init();
538
539     /*
540      * Start up the connection.
541      */
542     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
543     {
544         char *error;
545         char *realhost;
546
547         error = back->init(cfg.host, cfg.port, &realhost);
548         if (error) {
549             fprintf(stderr, "Unable to open connection:\n%s", error);
550             return 1;
551         }
552         sfree(realhost);
553     }
554     connopen = 1;
555
556     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
557
558     inhandle = GetStdHandle(STD_INPUT_HANDLE);
559     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
560     errhandle = GetStdHandle(STD_ERROR_HANDLE);
561     GetConsoleMode(inhandle, &orig_console_mode);
562     SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
563
564     /*
565      * Turn off ECHO and LINE input modes. We don't care if this
566      * call fails, because we know we aren't necessarily running in
567      * a console.
568      */
569     handles[0] = netevent;
570     handles[1] = stdinevent;
571     sending = FALSE;
572     while (1) {
573         int n;
574
575         if (!sending && back->sendok()) {
576             /*
577              * Create a separate thread to read from stdin. This is
578              * a total pain, but I can't find another way to do it:
579              *
580              *  - an overlapped ReadFile or ReadFileEx just doesn't
581              *    happen; we get failure from ReadFileEx, and
582              *    ReadFile blocks despite being given an OVERLAPPED
583              *    structure. Perhaps we can't do overlapped reads
584              *    on consoles. WHY THE HELL NOT?
585              * 
586              *  - WaitForMultipleObjects(netevent, console) doesn't
587              *    work, because it signals the console when
588              *    _anything_ happens, including mouse motions and
589              *    other things that don't cause data to be readable
590              *    - so we're back to ReadFile blocking.
591              */
592             idata.event = stdinevent;
593             idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
594             if (!CreateThread(NULL, 0, stdin_read_thread,
595                               &idata, 0, &threadid)) {
596                 fprintf(stderr, "Unable to create second thread\n");
597                 exit(1);
598             }
599             sending = TRUE;
600         }
601
602         n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
603         if (n == 0) {
604             WSANETWORKEVENTS things;
605             SOCKET socket;
606             extern SOCKET first_socket(int *), next_socket(int *);
607             extern int select_result(WPARAM, LPARAM);
608             int i, socketstate;
609
610             /*
611              * We must not call select_result() for any socket
612              * until we have finished enumerating within the tree.
613              * This is because select_result() may close the socket
614              * and modify the tree.
615              */
616             /* Count the active sockets. */
617             i = 0;
618             for (socket = first_socket(&socketstate);
619                  socket != INVALID_SOCKET;
620                  socket = next_socket(&socketstate)) i++;
621
622             /* Expand the buffer if necessary. */
623             if (i > sksize) {
624                 sksize = i + 16;
625                 sklist = srealloc(sklist, sksize * sizeof(*sklist));
626             }
627
628             /* Retrieve the sockets into sklist. */
629             skcount = 0;
630             for (socket = first_socket(&socketstate);
631                  socket != INVALID_SOCKET;
632                  socket = next_socket(&socketstate)) {
633                 sklist[skcount++] = socket;
634             }
635
636             /* Now we're done enumerating; go through the list. */
637             for (i = 0; i < skcount; i++) {
638                 WPARAM wp;
639                 socket = sklist[i];
640                 wp = (WPARAM) socket;
641                 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
642                     noise_ultralight(socket);
643                     noise_ultralight(things.lNetworkEvents);
644                     if (things.lNetworkEvents & FD_READ)
645                         connopen &= select_result(wp, (LPARAM) FD_READ);
646                     if (things.lNetworkEvents & FD_CLOSE)
647                         connopen &= select_result(wp, (LPARAM) FD_CLOSE);
648                     if (things.lNetworkEvents & FD_OOB)
649                         connopen &= select_result(wp, (LPARAM) FD_OOB);
650                     if (things.lNetworkEvents & FD_WRITE)
651                         connopen &= select_result(wp, (LPARAM) FD_WRITE);
652                 }
653             }
654         } else if (n == 1) {
655             noise_ultralight(idata.len);
656             if (idata.len > 0) {
657                 back->send(idata.buffer, idata.len);
658             } else {
659                 back->special(TS_EOF);
660             }
661             SetEvent(idata.eventback);
662         }
663         if (!connopen || back->socket() == NULL)
664             break;                     /* we closed the connection */
665     }
666     WSACleanup();
667     return 0;
668 }