]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Run entire source base through GNU indent to tidy up the varying
[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 do not trust this host, enter \"n\" to abandon the\n"
63         "connection.\n" "Continue connecting? (y/n) ";
64
65     static const char wrongmsg[] =
66         "WARNING - POTENTIAL SECURITY BREACH!\n"
67         "The server's host key does not match the one PuTTY has\n"
68         "cached in the registry. This means that either the\n"
69         "server administrator has changed the host key, or you\n"
70         "have actually connected to another computer pretending\n"
71         "to be the server.\n"
72         "The new key fingerprint is:\n"
73         "%s\n"
74         "If you were expecting this change and trust the new key,\n"
75         "enter \"y\" to update PuTTY's cache and continue connecting.\n"
76         "If you want to carry on connecting but without updating\n"
77         "the cache, enter \"n\".\n"
78         "If you want to abandon the connection completely, press\n"
79         "Return to cancel. Pressing Return is the ONLY guaranteed\n"
80         "safe choice.\n"
81         "Update cached key? (y/n, Return cancels connection) ";
82
83     static const char abandoned[] = "Connection abandoned.\n";
84
85     char line[32];
86
87     /*
88      * Verify the key against the registry.
89      */
90     ret = verify_host_key(host, port, keytype, keystr);
91
92     if (ret == 0)                      /* success - key matched OK */
93         return;
94
95     if (ret == 2) {                    /* key was different */
96         fprintf(stderr, wrongmsg, fingerprint);
97         fflush(stderr);
98     }
99     if (ret == 1) {                    /* key was absent */
100         fprintf(stderr, absentmsg, fingerprint);
101         fflush(stderr);
102     }
103
104     hin = GetStdHandle(STD_INPUT_HANDLE);
105     GetConsoleMode(hin, &savemode);
106     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
107                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
108     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
109     SetConsoleMode(hin, savemode);
110
111     if (ret == 2) {                    /* key was different */
112         if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
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     if (ret == 1) {                    /* key was absent */
121         if (line[0] == 'y' || line[0] == 'Y')
122             store_host_key(host, port, keytype, keystr);
123         else {
124             fprintf(stderr, abandoned);
125             exit(0);
126         }
127     }
128 }
129
130 HANDLE inhandle, outhandle, errhandle;
131 DWORD orig_console_mode;
132
133 WSAEVENT netevent;
134
135 void from_backend(int is_stderr, char *data, int len)
136 {
137     int pos;
138     DWORD ret;
139     HANDLE h = (is_stderr ? errhandle : outhandle);
140
141     pos = 0;
142     while (pos < len) {
143         if (!WriteFile(h, data + pos, len - pos, &ret, NULL))
144             return;                    /* give up in panic */
145         pos += ret;
146     }
147 }
148
149 int term_ldisc(int mode)
150 {
151     return FALSE;
152 }
153 void ldisc_update(int echo, int edit)
154 {
155     /* Update stdin read mode to reflect changes in line discipline. */
156     DWORD mode;
157
158     mode = ENABLE_PROCESSED_INPUT;
159     if (echo)
160         mode = mode | ENABLE_ECHO_INPUT;
161     else
162         mode = mode & ~ENABLE_ECHO_INPUT;
163     if (edit)
164         mode = mode | ENABLE_LINE_INPUT;
165     else
166         mode = mode & ~ENABLE_LINE_INPUT;
167     SetConsoleMode(inhandle, mode);
168 }
169
170 struct input_data {
171     DWORD len;
172     char buffer[4096];
173     HANDLE event, eventback;
174 };
175
176 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
177 {
178     HANDLE hin, hout;
179     DWORD savemode, newmode, i;
180
181     if (is_pw && password) {
182         static int tried_once = 0;
183
184         if (tried_once) {
185             return 0;
186         } else {
187             strncpy(str, password, maxlen);
188             str[maxlen - 1] = '\0';
189             tried_once = 1;
190             return 1;
191         }
192     }
193
194     hin = GetStdHandle(STD_INPUT_HANDLE);
195     hout = GetStdHandle(STD_OUTPUT_HANDLE);
196     if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
197         fprintf(stderr, "Cannot get standard input/output handles");
198         return 0;
199     }
200
201     GetConsoleMode(hin, &savemode);
202     newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
203     if (is_pw)
204         newmode &= ~ENABLE_ECHO_INPUT;
205     else
206         newmode |= ENABLE_ECHO_INPUT;
207     SetConsoleMode(hin, newmode);
208
209     WriteFile(hout, prompt, strlen(prompt), &i, NULL);
210     ReadFile(hin, str, maxlen - 1, &i, NULL);
211
212     SetConsoleMode(hin, savemode);
213
214     if ((int) i > maxlen)
215         i = maxlen - 1;
216     else
217         i = i - 2;
218     str[i] = '\0';
219
220     if (is_pw)
221         WriteFile(hout, "\r\n", 2, &i, NULL);
222
223     return 1;
224 }
225
226 static DWORD WINAPI stdin_read_thread(void *param)
227 {
228     struct input_data *idata = (struct input_data *) param;
229     HANDLE inhandle;
230
231     inhandle = GetStdHandle(STD_INPUT_HANDLE);
232
233     while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
234                     &idata->len, NULL) && idata->len > 0) {
235         SetEvent(idata->event);
236         WaitForSingleObject(idata->eventback, INFINITE);
237     }
238
239     idata->len = 0;
240     SetEvent(idata->event);
241
242     return 0;
243 }
244
245 /*
246  *  Short description of parameters.
247  */
248 static void usage(void)
249 {
250     printf("PuTTY Link: command-line connection utility\n");
251     printf("%s\n", ver);
252     printf("Usage: plink [options] [user@]host [command]\n");
253     printf("Options:\n");
254     printf("  -v        show verbose messages\n");
255     printf("  -ssh      force use of ssh protocol\n");
256     printf("  -P port   connect to specified port\n");
257     printf("  -pw passw login with specified password\n");
258     printf("  -m file   read remote command(s) from file\n");
259     exit(1);
260 }
261
262 char *do_select(SOCKET skt, int startup)
263 {
264     int events;
265     if (startup) {
266         events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
267     } else {
268         events = 0;
269     }
270     if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
271         switch (WSAGetLastError()) {
272           case WSAENETDOWN:
273             return "Network is down";
274           default:
275             return "WSAAsyncSelect(): unknown error";
276         }
277     }
278     return NULL;
279 }
280
281 int main(int argc, char **argv)
282 {
283     WSADATA wsadata;
284     WORD winsock_ver;
285     WSAEVENT stdinevent;
286     HANDLE handles[2];
287     DWORD threadid;
288     struct input_data idata;
289     int sending;
290     int portnumber = -1;
291     SOCKET *sklist;
292     int skcount, sksize;
293     int connopen;
294
295     ssh_get_line = get_line;
296
297     sklist = NULL;
298     skcount = sksize = 0;
299
300     flags = FLAG_STDERR;
301     /*
302      * Process the command line.
303      */
304     do_defaults(NULL, &cfg);
305     default_protocol = cfg.protocol;
306     default_port = cfg.port;
307     {
308         /*
309          * Override the default protocol if PLINK_PROTOCOL is set.
310          */
311         char *p = getenv("PLINK_PROTOCOL");
312         int i;
313         if (p) {
314             for (i = 0; backends[i].backend != NULL; i++) {
315                 if (!strcmp(backends[i].name, p)) {
316                     default_protocol = cfg.protocol = backends[i].protocol;
317                     default_port = cfg.port =
318                         backends[i].backend->default_port;
319                     break;
320                 }
321             }
322         }
323     }
324     while (--argc) {
325         char *p = *++argv;
326         if (*p == '-') {
327             if (!strcmp(p, "-ssh")) {
328                 default_protocol = cfg.protocol = PROT_SSH;
329                 default_port = cfg.port = 22;
330             } else if (!strcmp(p, "-telnet")) {
331                 default_protocol = cfg.protocol = PROT_TELNET;
332                 default_port = cfg.port = 23;
333             } else if (!strcmp(p, "-raw")) {
334                 default_protocol = cfg.protocol = PROT_RAW;
335             } else if (!strcmp(p, "-v")) {
336                 flags |= FLAG_VERBOSE;
337             } else if (!strcmp(p, "-log")) {
338                 logfile = "putty.log";
339             } else if (!strcmp(p, "-pw") && argc > 1) {
340                 --argc, password = *++argv;
341             } else if (!strcmp(p, "-l") && argc > 1) {
342                 char *username;
343                 --argc, username = *++argv;
344                 strncpy(cfg.username, username, sizeof(cfg.username));
345                 cfg.username[sizeof(cfg.username) - 1] = '\0';
346             } else if (!strcmp(p, "-m") && argc > 1) {
347                 char *filename, *command;
348                 int cmdlen, cmdsize;
349                 FILE *fp;
350                 int c, d;
351
352                 --argc, filename = *++argv;
353
354                 cmdlen = cmdsize = 0;
355                 command = NULL;
356                 fp = fopen(filename, "r");
357                 if (!fp) {
358                     fprintf(stderr, "plink: unable to open command "
359                             "file \"%s\"\n", filename);
360                     return 1;
361                 }
362                 do {
363                     c = fgetc(fp);
364                     d = c;
365                     if (c == EOF)
366                         d = 0;
367                     if (cmdlen >= cmdsize) {
368                         cmdsize = cmdlen + 512;
369                         command = srealloc(command, cmdsize);
370                     }
371                     command[cmdlen++] = d;
372                 } while (c != EOF);
373                 cfg.remote_cmd_ptr = command;
374                 cfg.nopty = TRUE;      /* command => no terminal */
375             } else if (!strcmp(p, "-P") && argc > 1) {
376                 --argc, portnumber = atoi(*++argv);
377             }
378         } else if (*p) {
379             if (!*cfg.host) {
380                 char *q = p;
381                 /*
382                  * If the hostname starts with "telnet:", set the
383                  * protocol to Telnet and process the string as a
384                  * Telnet URL.
385                  */
386                 if (!strncmp(q, "telnet:", 7)) {
387                     char c;
388
389                     q += 7;
390                     if (q[0] == '/' && q[1] == '/')
391                         q += 2;
392                     cfg.protocol = PROT_TELNET;
393                     p = q;
394                     while (*p && *p != ':' && *p != '/')
395                         p++;
396                     c = *p;
397                     if (*p)
398                         *p++ = '\0';
399                     if (c == ':')
400                         cfg.port = atoi(p);
401                     else
402                         cfg.port = -1;
403                     strncpy(cfg.host, q, sizeof(cfg.host) - 1);
404                     cfg.host[sizeof(cfg.host) - 1] = '\0';
405                 } else {
406                     char *r;
407                     /*
408                      * Before we process the [user@]host string, we
409                      * first check for the presence of a protocol
410                      * prefix (a protocol name followed by ",").
411                      */
412                     r = strchr(p, ',');
413                     if (r) {
414                         int i, j;
415                         for (i = 0; backends[i].backend != NULL; i++) {
416                             j = strlen(backends[i].name);
417                             if (j == r - p &&
418                                 !memcmp(backends[i].name, p, j)) {
419                                 default_protocol = cfg.protocol =
420                                     backends[i].protocol;
421                                 portnumber =
422                                     backends[i].backend->default_port;
423                                 p = r + 1;
424                                 break;
425                             }
426                         }
427                     }
428
429                     /*
430                      * Three cases. Either (a) there's a nonzero
431                      * length string followed by an @, in which
432                      * case that's user and the remainder is host.
433                      * Or (b) there's only one string, not counting
434                      * a potential initial @, and it exists in the
435                      * saved-sessions database. Or (c) only one
436                      * string and it _doesn't_ exist in the
437                      * database.
438                      */
439                     r = strrchr(p, '@');
440                     if (r == p)
441                         p++, r = NULL; /* discount initial @ */
442                     if (r == NULL) {
443                         /*
444                          * One string.
445                          */
446                         Config cfg2;
447                         do_defaults(p, &cfg2);
448                         if (cfg2.host[0] == '\0') {
449                             /* No settings for this host; use defaults */
450                             strncpy(cfg.host, p, sizeof(cfg.host) - 1);
451                             cfg.host[sizeof(cfg.host) - 1] = '\0';
452                             cfg.port = default_port;
453                         } else {
454                             cfg = cfg2;
455                             cfg.remote_cmd_ptr = cfg.remote_cmd;
456                         }
457                     } else {
458                         *r++ = '\0';
459                         strncpy(cfg.username, p, sizeof(cfg.username) - 1);
460                         cfg.username[sizeof(cfg.username) - 1] = '\0';
461                         strncpy(cfg.host, r, sizeof(cfg.host) - 1);
462                         cfg.host[sizeof(cfg.host) - 1] = '\0';
463                         cfg.port = default_port;
464                     }
465                 }
466             } else {
467                 int len = sizeof(cfg.remote_cmd) - 1;
468                 char *cp = cfg.remote_cmd;
469                 int len2;
470
471                 strncpy(cp, p, len);
472                 cp[len] = '\0';
473                 len2 = strlen(cp);
474                 len -= len2;
475                 cp += len2;
476                 while (--argc) {
477                     if (len > 0)
478                         len--, *cp++ = ' ';
479                     strncpy(cp, *++argv, len);
480                     cp[len] = '\0';
481                     len2 = strlen(cp);
482                     len -= len2;
483                     cp += len2;
484                 }
485                 cfg.nopty = TRUE;      /* command => no terminal */
486                 break;                 /* done with cmdline */
487             }
488         }
489     }
490
491     if (!*cfg.host) {
492         usage();
493     }
494
495     if (!*cfg.remote_cmd_ptr)
496         flags |= FLAG_INTERACTIVE;
497
498     /*
499      * Select protocol. This is farmed out into a table in a
500      * separate file to enable an ssh-free variant.
501      */
502     {
503         int i;
504         back = NULL;
505         for (i = 0; backends[i].backend != NULL; i++)
506             if (backends[i].protocol == cfg.protocol) {
507                 back = backends[i].backend;
508                 break;
509             }
510         if (back == NULL) {
511             fprintf(stderr,
512                     "Internal fault: Unsupported protocol found\n");
513             return 1;
514         }
515     }
516
517     /*
518      * Select port.
519      */
520     if (portnumber != -1)
521         cfg.port = portnumber;
522
523     /*
524      * Initialise WinSock.
525      */
526     winsock_ver = MAKEWORD(2, 0);
527     if (WSAStartup(winsock_ver, &wsadata)) {
528         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
529                    MB_OK | MB_ICONEXCLAMATION);
530         return 1;
531     }
532     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
533         MessageBox(NULL, "WinSock version is incompatible with 2.0",
534                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
535         WSACleanup();
536         return 1;
537     }
538     sk_init();
539
540     /*
541      * Start up the connection.
542      */
543     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
544     {
545         char *error;
546         char *realhost;
547
548         error = back->init(cfg.host, cfg.port, &realhost);
549         if (error) {
550             fprintf(stderr, "Unable to open connection:\n%s", error);
551             return 1;
552         }
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 }