]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Created a shiny new abstraction for the socket handling. Has many
[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 <stdarg.h>
11
12 #define PUTTY_DO_GLOBALS                       /* actually _define_ globals */
13 #include "putty.h"
14 #include "storage.h"
15 #include "tree234.h"
16
17 void fatalbox (char *p, ...) {
18     va_list ap;
19     fprintf(stderr, "FATAL ERROR: ", p);
20     va_start(ap, p);
21     vfprintf(stderr, p, ap);
22     va_end(ap);
23     fputc('\n', stderr);
24     WSACleanup();
25     exit(1);
26 }
27 void connection_fatal (char *p, ...) {
28     va_list ap;
29     fprintf(stderr, "FATAL ERROR: ", p);
30     va_start(ap, p);
31     vfprintf(stderr, p, ap);
32     va_end(ap);
33     fputc('\n', stderr);
34     WSACleanup();
35     exit(1);
36 }
37
38 static char *password = NULL;
39
40 void logevent(char *string) { }
41
42 void verify_ssh_host_key(char *host, int port, char *keytype,
43                          char *keystr, char *fingerprint) {
44     int ret;
45     HANDLE hin;
46     DWORD savemode, i;
47
48     static const char absentmsg[] =
49         "The server's host key is not cached in the registry. You\n"
50         "have no guarantee that the server is the computer you\n"
51         "think it is.\n"
52         "The server's key fingerprint is:\n"
53         "%s\n"
54         "If you trust this host, enter \"y\" to add the key to\n"
55         "PuTTY's cache and carry on connecting.\n"
56         "If you do not trust this host, enter \"n\" to abandon the\n"
57         "connection.\n"
58         "Continue connecting? (y/n) ";
59
60     static const char wrongmsg[] =
61         "WARNING - POTENTIAL SECURITY BREACH!\n"
62         "The server's host key does not match the one PuTTY has\n"
63         "cached in the registry. This means that either the\n"
64         "server administrator has changed the host key, or you\n"
65         "have actually connected to another computer pretending\n"
66         "to be the server.\n"
67         "The new key fingerprint is:\n"
68         "%s\n"
69         "If you were expecting this change and trust the new key,\n"
70         "enter \"y\" to update PuTTY's cache and continue connecting.\n"
71         "If you want to carry on connecting but without updating\n"
72         "the cache, enter \"n\".\n"
73         "If you want to abandon the connection completely, press\n"
74         "Return to cancel. Pressing Return is the ONLY guaranteed\n"
75         "safe choice.\n"
76         "Update cached key? (y/n, Return cancels connection) ";
77
78     static const char abandoned[] = "Connection abandoned.\n";
79
80     char line[32];
81
82     /*
83      * Verify the key against the registry.
84      */
85     ret = verify_host_key(host, port, keytype, keystr);
86
87     if (ret == 0)                      /* success - key matched OK */
88         return;
89
90     if (ret == 2)                      /* key was different */
91         fprintf(stderr, wrongmsg, fingerprint);
92     if (ret == 1)                      /* key was absent */
93         fprintf(stderr, absentmsg, fingerprint);
94
95     hin = GetStdHandle(STD_INPUT_HANDLE);
96     GetConsoleMode(hin, &savemode);
97     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
98                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
99     ReadFile(hin, line, sizeof(line)-1, &i, NULL);
100     SetConsoleMode(hin, savemode);
101
102     if (ret == 2) {                    /* key was different */
103         if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
104             if (line[0] == 'y' || line[0] == 'Y')
105                 store_host_key(host, port, keytype, keystr);
106         } else {
107             fprintf(stderr, abandoned);
108             exit(0);
109         }
110     }
111     if (ret == 1) {                    /* key was absent */
112         if (line[0] == 'y' || line[0] == 'Y')
113             store_host_key(host, port, keytype, keystr);
114         else {
115             fprintf(stderr, abandoned);
116             exit(0);
117         }
118     }
119 }
120
121 HANDLE outhandle, errhandle;
122 DWORD orig_console_mode;
123
124 WSAEVENT netevent;
125
126 void begin_session(void) {
127     if (!cfg.ldisc_term)
128         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
129     else
130         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
131 }
132
133 void from_backend(int is_stderr, char *data, int len) {
134     int pos;
135     DWORD ret;
136     HANDLE h = (is_stderr ? errhandle : outhandle);
137
138     pos = 0;
139     while (pos < len) {
140         if (!WriteFile(h, data+pos, len-pos, &ret, NULL))
141             return;                    /* give up in panic */
142         pos += ret;
143     }
144 }
145
146 struct input_data {
147     DWORD len;
148     char buffer[4096];
149     HANDLE event, eventback;
150 };
151
152 static int get_password(const char *prompt, char *str, int maxlen)
153 {
154     HANDLE hin, hout;
155     DWORD savemode, i;
156
157     if (password) {
158         static int tried_once = 0;
159
160         if (tried_once) {
161             return 0;
162         } else {
163             strncpy(str, password, maxlen);
164             str[maxlen-1] = '\0';
165             tried_once = 1;
166             return 1;
167         }
168     }
169
170     hin = GetStdHandle(STD_INPUT_HANDLE);
171     hout = GetStdHandle(STD_OUTPUT_HANDLE);
172     if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
173         fprintf(stderr, "Cannot get standard input/output handles");
174         return 0;
175     }
176
177     GetConsoleMode(hin, &savemode);
178     SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
179                    ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
180
181     WriteFile(hout, prompt, strlen(prompt), &i, NULL);
182     ReadFile(hin, str, maxlen-1, &i, NULL);
183
184     SetConsoleMode(hin, savemode);
185
186     if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
187     str[i] = '\0';
188
189     WriteFile(hout, "\r\n", 2, &i, NULL);
190
191     return 1;
192 }
193
194 static DWORD WINAPI stdin_read_thread(void *param) {
195     struct input_data *idata = (struct input_data *)param;
196     HANDLE inhandle;
197
198     inhandle = GetStdHandle(STD_INPUT_HANDLE);
199
200     while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
201                     &idata->len, NULL) && idata->len > 0) {
202         SetEvent(idata->event);
203         WaitForSingleObject(idata->eventback, INFINITE);
204     }
205
206     idata->len = 0;
207     SetEvent(idata->event);
208
209     return 0;
210 }
211
212 /*
213  *  Short description of parameters.
214  */
215 static void usage(void)
216 {
217     printf("PuTTY Link: command-line connection utility\n");
218     printf("%s\n", ver);
219     printf("Usage: plink [options] [user@]host [command]\n");
220     printf("Options:\n");
221     printf("  -v        show verbose messages\n");
222     printf("  -ssh      force use of ssh protocol\n");
223     printf("  -P port   connect to specified port\n");
224     printf("  -pw passw login with specified password\n");
225     exit(1);
226 }
227
228 char *do_select(SOCKET skt, int startup) {
229     int events;
230     if (startup) {
231         events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
232     } else {
233         events = 0;
234     }
235     if (WSAEventSelect (skt, netevent, events) == SOCKET_ERROR) {
236         switch (WSAGetLastError()) {
237           case WSAENETDOWN: return "Network is down";
238           default: return "WSAAsyncSelect(): unknown error";
239         }
240     }
241     return NULL;
242 }
243
244 int main(int argc, char **argv) {
245     WSADATA wsadata;
246     WORD winsock_ver;
247     WSAEVENT stdinevent;
248     HANDLE handles[2];
249     DWORD threadid;
250     struct input_data idata;
251     int sending;
252     int portnumber = -1;
253     SOCKET *sklist;
254     int skcount, sksize;
255     int connopen;
256
257     ssh_get_password = get_password;
258
259     sklist = NULL; skcount = sksize = 0;
260
261     flags = FLAG_STDERR;
262     /*
263      * Process the command line.
264      */
265     do_defaults(NULL, &cfg);
266     default_protocol = cfg.protocol;
267     default_port = cfg.port;
268     {
269         /*
270          * Override the default protocol if PLINK_PROTOCOL is set.
271          */
272         char *p = getenv("PLINK_PROTOCOL");
273         int i;
274         if (p) {
275             for (i = 0; backends[i].backend != NULL; i++) {
276                 if (!strcmp(backends[i].name, p)) {
277                     default_protocol = cfg.protocol = backends[i].protocol;
278                     default_port = cfg.port = backends[i].backend->default_port;
279                     break;
280                 }
281             }
282         }
283     }
284     while (--argc) {
285         char *p = *++argv;
286         if (*p == '-') {
287             if (!strcmp(p, "-ssh")) {
288                 default_protocol = cfg.protocol = PROT_SSH;
289                 default_port = cfg.port = 22;
290             } else if (!strcmp(p, "-telnet")) {
291                 default_protocol = cfg.protocol = PROT_TELNET;
292                 default_port = cfg.port = 23;
293             } else if (!strcmp(p, "-raw")) {
294                 default_protocol = cfg.protocol = PROT_RAW;
295             } else if (!strcmp(p, "-v")) {
296                 flags |= FLAG_VERBOSE;
297             } else if (!strcmp(p, "-log")) {
298                 logfile = "putty.log";
299             } else if (!strcmp(p, "-pw") && argc > 1) {
300                 --argc, password = *++argv;
301             } else if (!strcmp(p, "-l") && argc > 1) {
302                 char *username;
303                 --argc, username = *++argv;
304                 strncpy(cfg.username, username, sizeof(cfg.username));
305                 cfg.username[sizeof(cfg.username)-1] = '\0';
306             } else if (!strcmp(p, "-P") && argc > 1) {
307                 --argc, portnumber = atoi(*++argv);
308             }
309         } else if (*p) {
310             if (!*cfg.host) {
311                 char *q = p;
312                 /*
313                  * If the hostname starts with "telnet:", set the
314                  * protocol to Telnet and process the string as a
315                  * Telnet URL.
316                  */
317                 if (!strncmp(q, "telnet:", 7)) {
318                     char c;
319
320                     q += 7;
321                     if (q[0] == '/' && q[1] == '/')
322                         q += 2;
323                     cfg.protocol = PROT_TELNET;
324                     p = q;
325                     while (*p && *p != ':' && *p != '/') p++;
326                     c = *p;
327                     if (*p)
328                         *p++ = '\0';
329                     if (c == ':')
330                         cfg.port = atoi(p);
331                     else
332                         cfg.port = -1;
333                     strncpy (cfg.host, q, sizeof(cfg.host)-1);
334                     cfg.host[sizeof(cfg.host)-1] = '\0';
335                 } else {
336                     char *r;
337                     /*
338                      * Before we process the [user@]host string, we
339                      * first check for the presence of a protocol
340                      * prefix (a protocol name followed by ",").
341                      */
342                     r = strchr(p, ',');
343                     if (r) {
344                         int i, j;
345                         for (i = 0; backends[i].backend != NULL; i++) {
346                             j = strlen(backends[i].name);
347                             if (j == r-p &&
348                                 !memcmp(backends[i].name, p, j)) {
349                                 default_protocol = cfg.protocol = backends[i].protocol;
350                                 portnumber = backends[i].backend->default_port;
351                                 p = r+1;
352                                 break;
353                             }
354                         }
355                     }
356
357                     /*
358                      * Three cases. Either (a) there's a nonzero
359                      * length string followed by an @, in which
360                      * case that's user and the remainder is host.
361                      * Or (b) there's only one string, not counting
362                      * a potential initial @, and it exists in the
363                      * saved-sessions database. Or (c) only one
364                      * string and it _doesn't_ exist in the
365                      * database.
366                      */
367                     r = strrchr(p, '@');
368                     if (r == p) p++, r = NULL;   /* discount initial @ */
369                     if (r == NULL) {
370                         /*
371                          * One string.
372                          */
373                         do_defaults (p, &cfg);
374                         if (cfg.host[0] == '\0') {
375                             /* No settings for this host; use defaults */
376                             strncpy(cfg.host, p, sizeof(cfg.host)-1);
377                             cfg.host[sizeof(cfg.host)-1] = '\0';
378                             cfg.port = 22;
379                         }
380                     } else {
381                         *r++ = '\0';
382                         strncpy(cfg.username, p, sizeof(cfg.username)-1);
383                         cfg.username[sizeof(cfg.username)-1] = '\0';
384                         strncpy(cfg.host, r, sizeof(cfg.host)-1);
385                         cfg.host[sizeof(cfg.host)-1] = '\0';
386                         cfg.port = 22;
387                     }
388                 }
389             } else {
390                 int len = sizeof(cfg.remote_cmd) - 1;
391                 char *cp = cfg.remote_cmd;
392                 int len2;
393
394                 strncpy(cp, p, len); cp[len] = '\0';
395                 len2 = strlen(cp); len -= len2; cp += len2;
396                 while (--argc) {
397                     if (len > 0)
398                         len--, *cp++ = ' ';
399                     strncpy(cp, *++argv, len); cp[len] = '\0';
400                     len2 = strlen(cp); len -= len2; cp += len2;
401                 }
402                 cfg.nopty = TRUE;      /* command => no terminal */
403                 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
404                 break;                 /* done with cmdline */
405             }
406         }
407     }
408
409     if (!*cfg.host) {
410         usage();
411     }
412
413     if (!*cfg.remote_cmd)
414         flags |= FLAG_INTERACTIVE;
415
416     /*
417      * Select protocol. This is farmed out into a table in a
418      * separate file to enable an ssh-free variant.
419      */
420     {
421         int i;
422         back = NULL;
423         for (i = 0; backends[i].backend != NULL; i++)
424             if (backends[i].protocol == cfg.protocol) {
425                 back = backends[i].backend;
426                 break;
427             }
428         if (back == NULL) {
429             fprintf(stderr, "Internal fault: Unsupported protocol found\n");
430             return 1;
431         }
432     }
433
434     /*
435      * Select port.
436      */
437     if (portnumber != -1)
438         cfg.port = portnumber;
439
440     /*
441      * Initialise WinSock.
442      */
443     winsock_ver = MAKEWORD(2, 0);
444     if (WSAStartup(winsock_ver, &wsadata)) {
445         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
446                    MB_OK | MB_ICONEXCLAMATION);
447         return 1;
448     }
449     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
450         MessageBox(NULL, "WinSock version is incompatible with 2.0",
451                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
452         WSACleanup();
453         return 1;
454     }
455     sk_init();
456
457     /*
458      * Start up the connection.
459      */
460     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
461     {
462         char *error;
463         char *realhost;
464
465         error = back->init (cfg.host, cfg.port, &realhost);
466         if (error) {
467             fprintf(stderr, "Unable to open connection:\n%s", error);
468             return 1;
469         }
470     }
471     connopen = 1;
472
473     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
474
475     GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
476     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
477     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
478     errhandle = GetStdHandle(STD_ERROR_HANDLE);
479
480     /*
481      * Turn off ECHO and LINE input modes. We don't care if this
482      * call fails, because we know we aren't necessarily running in
483      * a console.
484      */
485     handles[0] = netevent;
486     handles[1] = stdinevent;
487     sending = FALSE;
488     while (1) {
489         int n;
490
491         if (!sending && back->sendok()) {
492             /*
493              * Create a separate thread to read from stdin. This is
494              * a total pain, but I can't find another way to do it:
495              *
496              *  - an overlapped ReadFile or ReadFileEx just doesn't
497              *    happen; we get failure from ReadFileEx, and
498              *    ReadFile blocks despite being given an OVERLAPPED
499              *    structure. Perhaps we can't do overlapped reads
500              *    on consoles. WHY THE HELL NOT?
501              * 
502              *  - WaitForMultipleObjects(netevent, console) doesn't
503              *    work, because it signals the console when
504              *    _anything_ happens, including mouse motions and
505              *    other things that don't cause data to be readable
506              *    - so we're back to ReadFile blocking.
507              */
508             idata.event = stdinevent;
509             idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
510             if (!CreateThread(NULL, 0, stdin_read_thread,
511                               &idata, 0, &threadid)) {
512                 fprintf(stderr, "Unable to create second thread\n");
513                 exit(1);
514             }
515             sending = TRUE;
516         }
517
518         n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
519         if (n == 0) {
520             WSANETWORKEVENTS things;
521             enum234 e;
522             SOCKET socket;
523             extern SOCKET first_socket(enum234 *), next_socket(enum234 *);
524             extern int select_result(WPARAM, LPARAM);
525             int i;
526
527             /*
528              * We must not call select_result() for any socket
529              * until we have finished enumerating within the tree.
530              * This is because select_result() may close the socket
531              * and modify the tree.
532              */
533             /* Count the active sockets. */
534             i = 0;
535             for (socket = first_socket(&e); socket != INVALID_SOCKET;
536                  socket = next_socket(&e))
537                 i++;
538
539             /* Expand the buffer if necessary. */
540             if (i > sksize) {
541                 sksize = i+16;
542                 sklist = srealloc(sklist, sksize * sizeof(*sklist));
543             }
544
545             /* Retrieve the sockets into sklist. */
546             skcount = 0;
547             for (socket = first_socket(&e); socket != INVALID_SOCKET;
548                  socket = next_socket(&e)) {
549                 sklist[skcount++] = socket;
550             }
551
552             /* Now we're done enumerating; go through the list. */
553             for (i = 0; i < skcount; i++) {
554                 WPARAM wp;
555                 socket = sklist[i];
556                 wp = (WPARAM)socket;
557                 if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
558                     if (things.lNetworkEvents & FD_READ)
559                         connopen &= select_result(wp, (LPARAM)FD_READ);
560                     if (things.lNetworkEvents & FD_CLOSE)
561                         connopen &= select_result(wp, (LPARAM)FD_CLOSE);
562                     if (things.lNetworkEvents & FD_OOB)
563                         connopen &= select_result(wp, (LPARAM)FD_OOB);
564                     if (things.lNetworkEvents & FD_WRITE)
565                         connopen &= select_result(wp, (LPARAM)FD_WRITE);
566                 }
567             }
568         } else if (n == 1) {
569             if (idata.len > 0) {
570                 back->send(idata.buffer, idata.len);
571             } else {
572                 back->special(TS_EOF);
573             }
574             SetEvent(idata.eventback);
575         }
576         if (!connopen || back->socket() == NULL)
577             break;                 /* we closed the connection */
578     }
579     WSACleanup();
580     return 0;
581 }