]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Plink and PSCP were failing to load the `Default Settings' options
[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                         Config cfg2;
374                         do_defaults (p, &cfg2);
375                         if (cfg2.host[0] == '\0') {
376                             /* No settings for this host; use defaults */
377                             strncpy(cfg.host, p, sizeof(cfg.host)-1);
378                             cfg.host[sizeof(cfg.host)-1] = '\0';
379                             cfg.port = 22;
380                         } else
381                             cfg = cfg2;
382                     } else {
383                         *r++ = '\0';
384                         strncpy(cfg.username, p, sizeof(cfg.username)-1);
385                         cfg.username[sizeof(cfg.username)-1] = '\0';
386                         strncpy(cfg.host, r, sizeof(cfg.host)-1);
387                         cfg.host[sizeof(cfg.host)-1] = '\0';
388                         cfg.port = 22;
389                     }
390                 }
391             } else {
392                 int len = sizeof(cfg.remote_cmd) - 1;
393                 char *cp = cfg.remote_cmd;
394                 int len2;
395
396                 strncpy(cp, p, len); cp[len] = '\0';
397                 len2 = strlen(cp); len -= len2; cp += len2;
398                 while (--argc) {
399                     if (len > 0)
400                         len--, *cp++ = ' ';
401                     strncpy(cp, *++argv, len); cp[len] = '\0';
402                     len2 = strlen(cp); len -= len2; cp += len2;
403                 }
404                 cfg.nopty = TRUE;      /* command => no terminal */
405                 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
406                 break;                 /* done with cmdline */
407             }
408         }
409     }
410
411     if (!*cfg.host) {
412         usage();
413     }
414
415     if (!*cfg.remote_cmd)
416         flags |= FLAG_INTERACTIVE;
417
418     /*
419      * Select protocol. This is farmed out into a table in a
420      * separate file to enable an ssh-free variant.
421      */
422     {
423         int i;
424         back = NULL;
425         for (i = 0; backends[i].backend != NULL; i++)
426             if (backends[i].protocol == cfg.protocol) {
427                 back = backends[i].backend;
428                 break;
429             }
430         if (back == NULL) {
431             fprintf(stderr, "Internal fault: Unsupported protocol found\n");
432             return 1;
433         }
434     }
435
436     /*
437      * Select port.
438      */
439     if (portnumber != -1)
440         cfg.port = portnumber;
441
442     /*
443      * Initialise WinSock.
444      */
445     winsock_ver = MAKEWORD(2, 0);
446     if (WSAStartup(winsock_ver, &wsadata)) {
447         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
448                    MB_OK | MB_ICONEXCLAMATION);
449         return 1;
450     }
451     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
452         MessageBox(NULL, "WinSock version is incompatible with 2.0",
453                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
454         WSACleanup();
455         return 1;
456     }
457     sk_init();
458
459     /*
460      * Start up the connection.
461      */
462     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
463     {
464         char *error;
465         char *realhost;
466
467         error = back->init (cfg.host, cfg.port, &realhost);
468         if (error) {
469             fprintf(stderr, "Unable to open connection:\n%s", error);
470             return 1;
471         }
472     }
473     connopen = 1;
474
475     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
476
477     GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
478     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
479     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
480     errhandle = GetStdHandle(STD_ERROR_HANDLE);
481
482     /*
483      * Turn off ECHO and LINE input modes. We don't care if this
484      * call fails, because we know we aren't necessarily running in
485      * a console.
486      */
487     handles[0] = netevent;
488     handles[1] = stdinevent;
489     sending = FALSE;
490     while (1) {
491         int n;
492
493         if (!sending && back->sendok()) {
494             /*
495              * Create a separate thread to read from stdin. This is
496              * a total pain, but I can't find another way to do it:
497              *
498              *  - an overlapped ReadFile or ReadFileEx just doesn't
499              *    happen; we get failure from ReadFileEx, and
500              *    ReadFile blocks despite being given an OVERLAPPED
501              *    structure. Perhaps we can't do overlapped reads
502              *    on consoles. WHY THE HELL NOT?
503              * 
504              *  - WaitForMultipleObjects(netevent, console) doesn't
505              *    work, because it signals the console when
506              *    _anything_ happens, including mouse motions and
507              *    other things that don't cause data to be readable
508              *    - so we're back to ReadFile blocking.
509              */
510             idata.event = stdinevent;
511             idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
512             if (!CreateThread(NULL, 0, stdin_read_thread,
513                               &idata, 0, &threadid)) {
514                 fprintf(stderr, "Unable to create second thread\n");
515                 exit(1);
516             }
517             sending = TRUE;
518         }
519
520         n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
521         if (n == 0) {
522             WSANETWORKEVENTS things;
523             enum234 e;
524             SOCKET socket;
525             extern SOCKET first_socket(enum234 *), next_socket(enum234 *);
526             extern int select_result(WPARAM, LPARAM);
527             int i;
528
529             /*
530              * We must not call select_result() for any socket
531              * until we have finished enumerating within the tree.
532              * This is because select_result() may close the socket
533              * and modify the tree.
534              */
535             /* Count the active sockets. */
536             i = 0;
537             for (socket = first_socket(&e); socket != INVALID_SOCKET;
538                  socket = next_socket(&e))
539                 i++;
540
541             /* Expand the buffer if necessary. */
542             if (i > sksize) {
543                 sksize = i+16;
544                 sklist = srealloc(sklist, sksize * sizeof(*sklist));
545             }
546
547             /* Retrieve the sockets into sklist. */
548             skcount = 0;
549             for (socket = first_socket(&e); socket != INVALID_SOCKET;
550                  socket = next_socket(&e)) {
551                 sklist[skcount++] = socket;
552             }
553
554             /* Now we're done enumerating; go through the list. */
555             for (i = 0; i < skcount; i++) {
556                 WPARAM wp;
557                 socket = sklist[i];
558                 wp = (WPARAM)socket;
559                 if (!WSAEnumNetworkEvents(socket, netevent, &things)) {
560                     if (things.lNetworkEvents & FD_READ)
561                         connopen &= select_result(wp, (LPARAM)FD_READ);
562                     if (things.lNetworkEvents & FD_CLOSE)
563                         connopen &= select_result(wp, (LPARAM)FD_CLOSE);
564                     if (things.lNetworkEvents & FD_OOB)
565                         connopen &= select_result(wp, (LPARAM)FD_OOB);
566                     if (things.lNetworkEvents & FD_WRITE)
567                         connopen &= select_result(wp, (LPARAM)FD_WRITE);
568                 }
569             }
570         } else if (n == 1) {
571             if (idata.len > 0) {
572                 back->send(idata.buffer, idata.len);
573             } else {
574                 back->special(TS_EOF);
575             }
576             SetEvent(idata.eventback);
577         }
578         if (!connopen || back->socket() == NULL)
579             break;                 /* we closed the connection */
580     }
581     WSACleanup();
582     return 0;
583 }