Improved entropy gathering.
[u/mdw/putty] / 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 noise_ultralight(socket);
561 noise_ultralight(things.lNetworkEvents);
562 if (things.lNetworkEvents & FD_READ)
563 connopen &= select_result(wp, (LPARAM)FD_READ);
564 if (things.lNetworkEvents & FD_CLOSE)
565 connopen &= select_result(wp, (LPARAM)FD_CLOSE);
566 if (things.lNetworkEvents & FD_OOB)
567 connopen &= select_result(wp, (LPARAM)FD_OOB);
568 if (things.lNetworkEvents & FD_WRITE)
569 connopen &= select_result(wp, (LPARAM)FD_WRITE);
570 }
571 }
572 } else if (n == 1) {
573 noise_ultralight(idata.len);
574 if (idata.len > 0) {
575 back->send(idata.buffer, idata.len);
576 } else {
577 back->special(TS_EOF);
578 }
579 SetEvent(idata.eventback);
580 }
581 if (!connopen || back->socket() == NULL)
582 break; /* we closed the connection */
583 }
584 WSACleanup();
585 return 0;
586 }