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