Improved entropy gathering.
[u/mdw/putty] / plink.c
CommitLineData
12dc4ec0 1/*
2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
3 */
4
4d331a77 5#ifndef AUTO_WINSOCK
12dc4ec0 6#include <winsock2.h>
4d331a77 7#endif
12dc4ec0 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"
a9422f39 14#include "storage.h"
8df7a775 15#include "tree234.h"
12dc4ec0 16
17void 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}
8d5de777 27void 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}
12dc4ec0 37
d8426c54 38static char *password = NULL;
39
a9422f39 40void logevent(char *string) { }
41
42void verify_ssh_host_key(char *host, int port, char *keytype,
43 char *keystr, char *fingerprint) {
44 int ret;
fcbb94d3 45 HANDLE hin;
46 DWORD savemode, i;
a9422f39 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;
fcbb94d3 89
90 if (ret == 2) /* key was different */
a9422f39 91 fprintf(stderr, wrongmsg, fingerprint);
fcbb94d3 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') {
a9422f39 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 */
fcbb94d3 112 if (line[0] == 'y' || line[0] == 'Y')
a9422f39 113 store_host_key(host, port, keytype, keystr);
114 else {
115 fprintf(stderr, abandoned);
116 exit(0);
117 }
118 }
119}
989b10e9 120
fe50e814 121HANDLE outhandle, errhandle;
6f34e365 122DWORD orig_console_mode;
123
8df7a775 124WSAEVENT netevent;
125
6f34e365 126void 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}
12dc4ec0 132
fe50e814 133void from_backend(int is_stderr, char *data, int len) {
134 int pos;
12dc4ec0 135 DWORD ret;
fe50e814 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))
12dc4ec0 141 return; /* give up in panic */
fe50e814 142 pos += ret;
12dc4ec0 143 }
12dc4ec0 144}
145
146struct input_data {
147 DWORD len;
148 char buffer[4096];
8df7a775 149 HANDLE event, eventback;
12dc4ec0 150};
151
67779be7 152static int get_password(const char *prompt, char *str, int maxlen)
153{
154 HANDLE hin, hout;
155 DWORD savemode, i;
156
67779be7 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 }
67779be7 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
6d2d5e8d 194static DWORD WINAPI stdin_read_thread(void *param) {
12dc4ec0 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),
8df7a775 201 &idata->len, NULL) && idata->len > 0) {
12dc4ec0 202 SetEvent(idata->event);
8df7a775 203 WaitForSingleObject(idata->eventback, INFINITE);
12dc4ec0 204 }
205
206 idata->len = 0;
207 SetEvent(idata->event);
208
209 return 0;
210}
211
d8426c54 212/*
213 * Short description of parameters.
214 */
215static 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
8df7a775 228char *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
12dc4ec0 244int main(int argc, char **argv) {
245 WSADATA wsadata;
246 WORD winsock_ver;
8df7a775 247 WSAEVENT stdinevent;
12dc4ec0 248 HANDLE handles[2];
12dc4ec0 249 DWORD threadid;
250 struct input_data idata;
251 int sending;
d8426c54 252 int portnumber = -1;
8df7a775 253 SOCKET *sklist;
254 int skcount, sksize;
255 int connopen;
12dc4ec0 256
67779be7 257 ssh_get_password = get_password;
258
8df7a775 259 sklist = NULL; skcount = sksize = 0;
260
67779be7 261 flags = FLAG_STDERR;
12dc4ec0 262 /*
263 * Process the command line.
264 */
a9422f39 265 do_defaults(NULL, &cfg);
e7a7383f 266 default_protocol = cfg.protocol;
267 default_port = cfg.port;
8cb9c947 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 }
12dc4ec0 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;
9d33ebdd 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;
67779be7 295 } else if (!strcmp(p, "-v")) {
296 flags |= FLAG_VERBOSE;
12dc4ec0 297 } else if (!strcmp(p, "-log")) {
298 logfile = "putty.log";
d8426c54 299 } else if (!strcmp(p, "-pw") && argc > 1) {
300 --argc, password = *++argv;
e7a7383f 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';
d8426c54 306 } else if (!strcmp(p, "-P") && argc > 1) {
307 --argc, portnumber = atoi(*++argv);
308 }
12dc4ec0 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 {
9d33ebdd 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
12dc4ec0 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 */
9d33ebdd 367 r = strrchr(p, '@');
12dc4ec0 368 if (r == p) p++, r = NULL; /* discount initial @ */
369 if (r == NULL) {
370 /*
371 * One string.
372 */
9c41b8ea 373 Config cfg2;
374 do_defaults (p, &cfg2);
375 if (cfg2.host[0] == '\0') {
12dc4ec0 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;
9c41b8ea 380 } else
381 cfg = cfg2;
12dc4ec0 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
d8426c54 411 if (!*cfg.host) {
412 usage();
413 }
d8426c54 414
67779be7 415 if (!*cfg.remote_cmd)
416 flags |= FLAG_INTERACTIVE;
417
12dc4ec0 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 /*
8cb9c947 437 * Select port.
438 */
439 if (portnumber != -1)
440 cfg.port = portnumber;
441
442 /*
12dc4ec0 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 }
8df7a775 457 sk_init();
12dc4ec0 458
459 /*
460 * Start up the connection.
461 */
8df7a775 462 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 463 {
464 char *error;
465 char *realhost;
466
8df7a775 467 error = back->init (cfg.host, cfg.port, &realhost);
12dc4ec0 468 if (error) {
469 fprintf(stderr, "Unable to open connection:\n%s", error);
470 return 1;
471 }
472 }
8df7a775 473 connopen = 1;
12dc4ec0 474
12dc4ec0 475 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
476
6f34e365 477 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
478 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
12dc4ec0 479 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
fe50e814 480 errhandle = GetStdHandle(STD_ERROR_HANDLE);
12dc4ec0 481
482 /*
12dc4ec0 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 */
12dc4ec0 487 handles[0] = netevent;
488 handles[1] = stdinevent;
489 sending = FALSE;
490 while (1) {
491 int n;
8cb9c947 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;
8df7a775 511 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
8cb9c947 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
12dc4ec0 520 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
521 if (n == 0) {
522 WSANETWORKEVENTS things;
8df7a775 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;
12dc4ec0 552 }
8df7a775 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)) {
7d6ee6ff 560 noise_ultralight(socket);
561 noise_ultralight(things.lNetworkEvents);
8df7a775 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 }
12dc4ec0 572 } else if (n == 1) {
7d6ee6ff 573 noise_ultralight(idata.len);
12dc4ec0 574 if (idata.len > 0) {
575 back->send(idata.buffer, idata.len);
576 } else {
577 back->special(TS_EOF);
578 }
8df7a775 579 SetEvent(idata.eventback);
12dc4ec0 580 }
8df7a775 581 if (!connopen || back->socket() == NULL)
cf6e59d6 582 break; /* we closed the connection */
12dc4ec0 583 }
584 WSACleanup();
585 return 0;
586}