]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Oops. The new remote_cmd_ptr points in the wrong place when you copy a
[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                             cfg.remote_cmd_ptr = cfg.remote_cmd;
429                         }
430                     } else {
431                         *r++ = '\0';
432                         strncpy(cfg.username, p, sizeof(cfg.username)-1);
433                         cfg.username[sizeof(cfg.username)-1] = '\0';
434                         strncpy(cfg.host, r, sizeof(cfg.host)-1);
435                         cfg.host[sizeof(cfg.host)-1] = '\0';
436                         cfg.port = default_port;
437                     }
438                 }
439             } else {
440                 int len = sizeof(cfg.remote_cmd) - 1;
441                 char *cp = cfg.remote_cmd;
442                 int len2;
443
444                 strncpy(cp, p, len); cp[len] = '\0';
445                 len2 = strlen(cp); len -= len2; cp += len2;
446                 while (--argc) {
447                     if (len > 0)
448                         len--, *cp++ = ' ';
449                     strncpy(cp, *++argv, len); cp[len] = '\0';
450                     len2 = strlen(cp); len -= len2; cp += len2;
451                 }
452                 cfg.nopty = TRUE;      /* command => no terminal */
453                 break;                 /* done with cmdline */
454             }
455         }
456     }
457
458     if (!*cfg.host) {
459         usage();
460     }
461
462     if (!*cfg.remote_cmd_ptr)
463         flags |= FLAG_INTERACTIVE;
464
465     /*
466      * Select protocol. This is farmed out into a table in a
467      * separate file to enable an ssh-free variant.
468      */
469     {
470         int i;
471         back = NULL;
472         for (i = 0; backends[i].backend != NULL; i++)
473             if (backends[i].protocol == cfg.protocol) {
474                 back = backends[i].backend;
475                 break;
476             }
477         if (back == NULL) {
478             fprintf(stderr, "Internal fault: Unsupported protocol found\n");
479             return 1;
480         }
481     }
482
483     /*
484      * Select port.
485      */
486     if (portnumber != -1)
487         cfg.port = portnumber;
488
489     /*
490      * Initialise WinSock.
491      */
492     winsock_ver = MAKEWORD(2, 0);
493     if (WSAStartup(winsock_ver, &wsadata)) {
494         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
495                    MB_OK | MB_ICONEXCLAMATION);
496         return 1;
497     }
498     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
499         MessageBox(NULL, "WinSock version is incompatible with 2.0",
500                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
501         WSACleanup();
502         return 1;
503     }
504     sk_init();
505
506     /*
507      * Start up the connection.
508      */
509     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
510     {
511         char *error;
512         char *realhost;
513
514         error = back->init (cfg.host, cfg.port, &realhost);
515         if (error) {
516             fprintf(stderr, "Unable to open connection:\n%s", error);
517             return 1;
518         }
519     }
520     connopen = 1;
521
522     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
523
524     inhandle = GetStdHandle(STD_INPUT_HANDLE);
525     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
526     errhandle = GetStdHandle(STD_ERROR_HANDLE);
527     GetConsoleMode(inhandle, &orig_console_mode);
528     SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
529
530     /*
531      * Turn off ECHO and LINE input modes. We don't care if this
532      * call fails, because we know we aren't necessarily running in
533      * a console.
534      */
535     handles[0] = netevent;
536     handles[1] = stdinevent;
537     sending = FALSE;
538     while (1) {
539         int n;
540
541         if (!sending && back->sendok()) {
542             /*
543              * Create a separate thread to read from stdin. This is
544              * a total pain, but I can't find another way to do it:
545              *
546              *  - an overlapped ReadFile or ReadFileEx just doesn't
547              *    happen; we get failure from ReadFileEx, and
548              *    ReadFile blocks despite being given an OVERLAPPED
549              *    structure. Perhaps we can't do overlapped reads
550              *    on consoles. WHY THE HELL NOT?
551              * 
552              *  - WaitForMultipleObjects(netevent, console) doesn't
553              *    work, because it signals the console when
554              *    _anything_ happens, including mouse motions and
555              *    other things that don't cause data to be readable
556              *    - so we're back to ReadFile blocking.
557              */
558             idata.event = stdinevent;
559             idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
560             if (!CreateThread(NULL, 0, stdin_read_thread,
561                               &idata, 0, &threadid)) {
562                 fprintf(stderr, "Unable to create second thread\n");
563                 exit(1);
564             }
565             sending = TRUE;
566         }
567
568         n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
569         if (n == 0) {
570             WSANETWORKEVENTS things;
571             enum234 e;
572             SOCKET socket;
573             extern SOCKET first_socket(enum234 *), next_socket(enum234 *);
574             extern int select_result(WPARAM, LPARAM);
575             int i;
576
577             /*
578              * We must not call select_result() for any socket
579              * until we have finished enumerating within the tree.
580              * This is because select_result() may close the socket
581              * and modify the tree.
582              */
583             /* Count the active sockets. */
584             i = 0;
585             for (socket = first_socket(&e); socket != INVALID_SOCKET;
586                  socket = next_socket(&e))
587                 i++;
588
589             /* Expand the buffer if necessary. */
590             if (i > sksize) {
591                 sksize = i+16;
592                 sklist = srealloc(sklist, sksize * sizeof(*sklist));
593             }
594
595             /* Retrieve the sockets into sklist. */
596             skcount = 0;
597             for (socket = first_socket(&e); socket != INVALID_SOCKET;
598                  socket = next_socket(&e)) {
599                 sklist[skcount++] = socket;
600             }
601
602             /* Now we're done enumerating; go through the list. */
603             for (i = 0; i < skcount; i++) {
604                 WPARAM wp;
605                 socket = sklist[i];
606                 wp = (WPARAM)socket;
607                 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
608                     noise_ultralight(socket);
609                     noise_ultralight(things.lNetworkEvents);
610                     if (things.lNetworkEvents & FD_READ)
611                         connopen &= select_result(wp, (LPARAM)FD_READ);
612                     if (things.lNetworkEvents & FD_CLOSE)
613                         connopen &= select_result(wp, (LPARAM)FD_CLOSE);
614                     if (things.lNetworkEvents & FD_OOB)
615                         connopen &= select_result(wp, (LPARAM)FD_OOB);
616                     if (things.lNetworkEvents & FD_WRITE)
617                         connopen &= select_result(wp, (LPARAM)FD_WRITE);
618                 }
619             }
620         } else if (n == 1) {
621             noise_ultralight(idata.len);
622             if (idata.len > 0) {
623                 back->send(idata.buffer, idata.len);
624             } else {
625                 back->special(TS_EOF);
626             }
627             SetEvent(idata.eventback);
628         }
629         if (!connopen || back->socket() == NULL)
630             break;                 /* we closed the connection */
631     }
632     WSACleanup();
633     return 0;
634 }