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