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