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