]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Miscellaneous cleanups and reorgs in preparation for building
[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 "winstuff.h"
15 #include "storage.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;
122 DWORD orig_console_mode;
123
124 void begin_session(void) {
125     if (!cfg.ldisc_term)
126         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
127     else
128         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
129 }
130
131 void term_out(void)
132 {
133     int reap;
134     DWORD ret;
135     reap = 0;
136     while (reap < inbuf_head) {
137         if (!WriteFile(outhandle, inbuf+reap, inbuf_head-reap, &ret, NULL))
138             return;                    /* give up in panic */
139         reap += ret;
140     }
141     inbuf_head = 0;
142 }
143
144 struct input_data {
145     DWORD len;
146     char buffer[4096];
147     HANDLE event;
148 };
149
150 static int get_password(const char *prompt, char *str, int maxlen)
151 {
152     HANDLE hin, hout;
153     DWORD savemode, i;
154
155     if (password) {
156         static int tried_once = 0;
157
158         if (tried_once) {
159             return 0;
160         } else {
161             strncpy(str, password, maxlen);
162             str[maxlen-1] = '\0';
163             tried_once = 1;
164             return 1;
165         }
166     }
167
168     hin = GetStdHandle(STD_INPUT_HANDLE);
169     hout = GetStdHandle(STD_OUTPUT_HANDLE);
170     if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
171         fprintf(stderr, "Cannot get standard input/output handles");
172         return 0;
173     }
174
175     GetConsoleMode(hin, &savemode);
176     SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
177                    ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
178
179     WriteFile(hout, prompt, strlen(prompt), &i, NULL);
180     ReadFile(hin, str, maxlen-1, &i, NULL);
181
182     SetConsoleMode(hin, savemode);
183
184     if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
185     str[i] = '\0';
186
187     WriteFile(hout, "\r\n", 2, &i, NULL);
188
189     return 1;
190 }
191
192 static DWORD WINAPI stdin_read_thread(void *param) {
193     struct input_data *idata = (struct input_data *)param;
194     HANDLE inhandle;
195
196     inhandle = GetStdHandle(STD_INPUT_HANDLE);
197
198     while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
199                     &idata->len, NULL)) {
200         SetEvent(idata->event);
201     }
202
203     idata->len = 0;
204     SetEvent(idata->event);
205
206     return 0;
207 }
208
209 /*
210  *  Short description of parameters.
211  */
212 static void usage(void)
213 {
214     printf("PuTTY Link: command-line connection utility\n");
215     printf("%s\n", ver);
216     printf("Usage: plink [options] [user@]host [command]\n");
217     printf("Options:\n");
218     printf("  -v        show verbose messages\n");
219     printf("  -ssh      force use of ssh protocol\n");
220     printf("  -P port   connect to specified port\n");
221     printf("  -pw passw login with specified password\n");
222     exit(1);
223 }
224
225 int main(int argc, char **argv) {
226     WSADATA wsadata;
227     WORD winsock_ver;
228     WSAEVENT netevent, stdinevent;
229     HANDLE handles[2];
230     SOCKET socket;
231     DWORD threadid;
232     struct input_data idata;
233     int sending;
234     int portnumber = -1;
235
236     ssh_get_password = get_password;
237
238     flags = FLAG_STDERR;
239     /*
240      * Process the command line.
241      */
242     do_defaults(NULL, &cfg);
243     default_protocol = cfg.protocol;
244     default_port = cfg.port;
245     {
246         /*
247          * Override the default protocol if PLINK_PROTOCOL is set.
248          */
249         char *p = getenv("PLINK_PROTOCOL");
250         int i;
251         if (p) {
252             for (i = 0; backends[i].backend != NULL; i++) {
253                 if (!strcmp(backends[i].name, p)) {
254                     default_protocol = cfg.protocol = backends[i].protocol;
255                     default_port = cfg.port = backends[i].backend->default_port;
256                     break;
257                 }
258             }
259         }
260     }
261     while (--argc) {
262         char *p = *++argv;
263         if (*p == '-') {
264             if (!strcmp(p, "-ssh")) {
265                 default_protocol = cfg.protocol = PROT_SSH;
266                 default_port = cfg.port = 22;
267             } else if (!strcmp(p, "-telnet")) {
268                 default_protocol = cfg.protocol = PROT_TELNET;
269                 default_port = cfg.port = 23;
270             } else if (!strcmp(p, "-raw")) {
271                 default_protocol = cfg.protocol = PROT_RAW;
272             } else if (!strcmp(p, "-v")) {
273                 flags |= FLAG_VERBOSE;
274             } else if (!strcmp(p, "-log")) {
275                 logfile = "putty.log";
276             } else if (!strcmp(p, "-pw") && argc > 1) {
277                 --argc, password = *++argv;
278             } else if (!strcmp(p, "-l") && argc > 1) {
279                 char *username;
280                 --argc, username = *++argv;
281                 strncpy(cfg.username, username, sizeof(cfg.username));
282                 cfg.username[sizeof(cfg.username)-1] = '\0';
283             } else if (!strcmp(p, "-P") && argc > 1) {
284                 --argc, portnumber = atoi(*++argv);
285             }
286         } else if (*p) {
287             if (!*cfg.host) {
288                 char *q = p;
289                 /*
290                  * If the hostname starts with "telnet:", set the
291                  * protocol to Telnet and process the string as a
292                  * Telnet URL.
293                  */
294                 if (!strncmp(q, "telnet:", 7)) {
295                     char c;
296
297                     q += 7;
298                     if (q[0] == '/' && q[1] == '/')
299                         q += 2;
300                     cfg.protocol = PROT_TELNET;
301                     p = q;
302                     while (*p && *p != ':' && *p != '/') p++;
303                     c = *p;
304                     if (*p)
305                         *p++ = '\0';
306                     if (c == ':')
307                         cfg.port = atoi(p);
308                     else
309                         cfg.port = -1;
310                     strncpy (cfg.host, q, sizeof(cfg.host)-1);
311                     cfg.host[sizeof(cfg.host)-1] = '\0';
312                 } else {
313                     char *r;
314                     /*
315                      * Before we process the [user@]host string, we
316                      * first check for the presence of a protocol
317                      * prefix (a protocol name followed by ",").
318                      */
319                     r = strchr(p, ',');
320                     if (r) {
321                         int i, j;
322                         for (i = 0; backends[i].backend != NULL; i++) {
323                             j = strlen(backends[i].name);
324                             if (j == r-p &&
325                                 !memcmp(backends[i].name, p, j)) {
326                                 default_protocol = cfg.protocol = backends[i].protocol;
327                                 portnumber = backends[i].backend->default_port;
328                                 p = r+1;
329                                 break;
330                             }
331                         }
332                     }
333
334                     /*
335                      * Three cases. Either (a) there's a nonzero
336                      * length string followed by an @, in which
337                      * case that's user and the remainder is host.
338                      * Or (b) there's only one string, not counting
339                      * a potential initial @, and it exists in the
340                      * saved-sessions database. Or (c) only one
341                      * string and it _doesn't_ exist in the
342                      * database.
343                      */
344                     r = strrchr(p, '@');
345                     if (r == p) p++, r = NULL;   /* discount initial @ */
346                     if (r == NULL) {
347                         /*
348                          * One string.
349                          */
350                         do_defaults (p, &cfg);
351                         if (cfg.host[0] == '\0') {
352                             /* No settings for this host; use defaults */
353                             strncpy(cfg.host, p, sizeof(cfg.host)-1);
354                             cfg.host[sizeof(cfg.host)-1] = '\0';
355                             cfg.port = 22;
356                         }
357                     } else {
358                         *r++ = '\0';
359                         strncpy(cfg.username, p, sizeof(cfg.username)-1);
360                         cfg.username[sizeof(cfg.username)-1] = '\0';
361                         strncpy(cfg.host, r, sizeof(cfg.host)-1);
362                         cfg.host[sizeof(cfg.host)-1] = '\0';
363                         cfg.port = 22;
364                     }
365                 }
366             } else {
367                 int len = sizeof(cfg.remote_cmd) - 1;
368                 char *cp = cfg.remote_cmd;
369                 int len2;
370
371                 strncpy(cp, p, len); cp[len] = '\0';
372                 len2 = strlen(cp); len -= len2; cp += len2;
373                 while (--argc) {
374                     if (len > 0)
375                         len--, *cp++ = ' ';
376                     strncpy(cp, *++argv, len); cp[len] = '\0';
377                     len2 = strlen(cp); len -= len2; cp += len2;
378                 }
379                 cfg.nopty = TRUE;      /* command => no terminal */
380                 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
381                 break;                 /* done with cmdline */
382             }
383         }
384     }
385
386     if (!*cfg.host) {
387         usage();
388     }
389
390     if (!*cfg.remote_cmd)
391         flags |= FLAG_INTERACTIVE;
392
393     /*
394      * Select protocol. This is farmed out into a table in a
395      * separate file to enable an ssh-free variant.
396      */
397     {
398         int i;
399         back = NULL;
400         for (i = 0; backends[i].backend != NULL; i++)
401             if (backends[i].protocol == cfg.protocol) {
402                 back = backends[i].backend;
403                 break;
404             }
405         if (back == NULL) {
406             fprintf(stderr, "Internal fault: Unsupported protocol found\n");
407             return 1;
408         }
409     }
410
411     /*
412      * Select port.
413      */
414     if (portnumber != -1)
415         cfg.port = portnumber;
416
417     /*
418      * Initialise WinSock.
419      */
420     winsock_ver = MAKEWORD(2, 0);
421     if (WSAStartup(winsock_ver, &wsadata)) {
422         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
423                    MB_OK | MB_ICONEXCLAMATION);
424         return 1;
425     }
426     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
427         MessageBox(NULL, "WinSock version is incompatible with 2.0",
428                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
429         WSACleanup();
430         return 1;
431     }
432
433     /*
434      * Start up the connection.
435      */
436     {
437         char *error;
438         char *realhost;
439
440         error = back->init (NULL, cfg.host, cfg.port, &realhost);
441         if (error) {
442             fprintf(stderr, "Unable to open connection:\n%s", error);
443             return 1;
444         }
445     }
446
447     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
448     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
449
450     GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
451     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
452     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
453
454     /*
455      * Now we must send the back end oodles of stuff.
456      */
457     socket = back->socket();
458     /*
459      * Turn off ECHO and LINE input modes. We don't care if this
460      * call fails, because we know we aren't necessarily running in
461      * a console.
462      */
463     WSAEventSelect(socket, netevent, FD_READ | FD_CLOSE);
464     handles[0] = netevent;
465     handles[1] = stdinevent;
466     sending = FALSE;
467     while (1) {
468         int n;
469
470         if (!sending && back->sendok()) {
471             /*
472              * Create a separate thread to read from stdin. This is
473              * a total pain, but I can't find another way to do it:
474              *
475              *  - an overlapped ReadFile or ReadFileEx just doesn't
476              *    happen; we get failure from ReadFileEx, and
477              *    ReadFile blocks despite being given an OVERLAPPED
478              *    structure. Perhaps we can't do overlapped reads
479              *    on consoles. WHY THE HELL NOT?
480              * 
481              *  - WaitForMultipleObjects(netevent, console) doesn't
482              *    work, because it signals the console when
483              *    _anything_ happens, including mouse motions and
484              *    other things that don't cause data to be readable
485              *    - so we're back to ReadFile blocking.
486              */
487             idata.event = stdinevent;
488             if (!CreateThread(NULL, 0, stdin_read_thread,
489                               &idata, 0, &threadid)) {
490                 fprintf(stderr, "Unable to create second thread\n");
491                 exit(1);
492             }
493             sending = TRUE;
494         }
495
496         n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
497         if (n == 0) {
498             WSANETWORKEVENTS things;
499             if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
500                 if (things.lNetworkEvents & FD_READ)
501                     back->msg(0, FD_READ);
502                 if (things.lNetworkEvents & FD_CLOSE) {
503                     back->msg(0, FD_CLOSE);
504                     break;
505                 }
506             }
507             term_out();
508         } else if (n == 1) {
509             if (idata.len > 0) {
510                 back->send(idata.buffer, idata.len);
511             } else {
512                 back->special(TS_EOF);
513             }
514         }
515         if (back->socket() == INVALID_SOCKET)
516             break;                 /* we closed the connection */
517     }
518     WSACleanup();
519     return 0;
520 }