Improve socket error handling so that a socket error isn't an
[sgt/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>
49bad831 10#include <stdlib.h>
12dc4ec0 11#include <stdarg.h>
12
13#define PUTTY_DO_GLOBALS /* actually _define_ globals */
14#include "putty.h"
a9422f39 15#include "storage.h"
8df7a775 16#include "tree234.h"
12dc4ec0 17
18void fatalbox (char *p, ...) {
19 va_list ap;
49bad831 20 fprintf(stderr, "FATAL ERROR: ");
12dc4ec0 21 va_start(ap, p);
22 vfprintf(stderr, p, ap);
23 va_end(ap);
24 fputc('\n', stderr);
25 WSACleanup();
26 exit(1);
27}
8d5de777 28void connection_fatal (char *p, ...) {
29 va_list ap;
49bad831 30 fprintf(stderr, "FATAL ERROR: ");
8d5de777 31 va_start(ap, p);
32 vfprintf(stderr, p, ap);
33 va_end(ap);
34 fputc('\n', stderr);
35 WSACleanup();
36 exit(1);
37}
12dc4ec0 38
d8426c54 39static char *password = NULL;
40
a9422f39 41void logevent(char *string) { }
42
43void verify_ssh_host_key(char *host, int port, char *keytype,
44 char *keystr, char *fingerprint) {
45 int ret;
fcbb94d3 46 HANDLE hin;
47 DWORD savemode, i;
a9422f39 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;
fcbb94d3 90
91 if (ret == 2) /* key was different */
a9422f39 92 fprintf(stderr, wrongmsg, fingerprint);
fcbb94d3 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') {
a9422f39 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 */
fcbb94d3 113 if (line[0] == 'y' || line[0] == 'Y')
a9422f39 114 store_host_key(host, port, keytype, keystr);
115 else {
116 fprintf(stderr, abandoned);
117 exit(0);
118 }
119 }
120}
989b10e9 121
fe50e814 122HANDLE outhandle, errhandle;
6f34e365 123DWORD orig_console_mode;
124
8df7a775 125WSAEVENT netevent;
126
6f34e365 127void begin_session(void) {
128 if (!cfg.ldisc_term)
129 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
130 else
131 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), orig_console_mode);
132}
12dc4ec0 133
fe50e814 134void from_backend(int is_stderr, char *data, int len) {
135 int pos;
12dc4ec0 136 DWORD ret;
fe50e814 137 HANDLE h = (is_stderr ? errhandle : outhandle);
138
139 pos = 0;
140 while (pos < len) {
141 if (!WriteFile(h, data+pos, len-pos, &ret, NULL))
12dc4ec0 142 return; /* give up in panic */
fe50e814 143 pos += ret;
12dc4ec0 144 }
12dc4ec0 145}
146
147struct input_data {
148 DWORD len;
149 char buffer[4096];
8df7a775 150 HANDLE event, eventback;
12dc4ec0 151};
152
67779be7 153static int get_password(const char *prompt, char *str, int maxlen)
154{
155 HANDLE hin, hout;
156 DWORD savemode, i;
157
67779be7 158 if (password) {
159 static int tried_once = 0;
160
161 if (tried_once) {
162 return 0;
163 } else {
164 strncpy(str, password, maxlen);
165 str[maxlen-1] = '\0';
166 tried_once = 1;
167 return 1;
168 }
169 }
67779be7 170
171 hin = GetStdHandle(STD_INPUT_HANDLE);
172 hout = GetStdHandle(STD_OUTPUT_HANDLE);
173 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
174 fprintf(stderr, "Cannot get standard input/output handles");
175 return 0;
176 }
177
178 GetConsoleMode(hin, &savemode);
179 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
180 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
181
182 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
183 ReadFile(hin, str, maxlen-1, &i, NULL);
184
185 SetConsoleMode(hin, savemode);
186
187 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
188 str[i] = '\0';
189
190 WriteFile(hout, "\r\n", 2, &i, NULL);
191
192 return 1;
193}
194
6d2d5e8d 195static DWORD WINAPI stdin_read_thread(void *param) {
12dc4ec0 196 struct input_data *idata = (struct input_data *)param;
197 HANDLE inhandle;
198
199 inhandle = GetStdHandle(STD_INPUT_HANDLE);
200
201 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
8df7a775 202 &idata->len, NULL) && idata->len > 0) {
12dc4ec0 203 SetEvent(idata->event);
8df7a775 204 WaitForSingleObject(idata->eventback, INFINITE);
12dc4ec0 205 }
206
207 idata->len = 0;
208 SetEvent(idata->event);
209
210 return 0;
211}
212
d8426c54 213/*
214 * Short description of parameters.
215 */
216static void usage(void)
217{
218 printf("PuTTY Link: command-line connection utility\n");
219 printf("%s\n", ver);
220 printf("Usage: plink [options] [user@]host [command]\n");
221 printf("Options:\n");
222 printf(" -v show verbose messages\n");
223 printf(" -ssh force use of ssh protocol\n");
224 printf(" -P port connect to specified port\n");
225 printf(" -pw passw login with specified password\n");
226 exit(1);
227}
228
8df7a775 229char *do_select(SOCKET skt, int startup) {
230 int events;
231 if (startup) {
232 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE;
233 } else {
234 events = 0;
235 }
236 if (WSAEventSelect (skt, netevent, events) == SOCKET_ERROR) {
237 switch (WSAGetLastError()) {
238 case WSAENETDOWN: return "Network is down";
239 default: return "WSAAsyncSelect(): unknown error";
240 }
241 }
242 return NULL;
243}
244
12dc4ec0 245int main(int argc, char **argv) {
246 WSADATA wsadata;
247 WORD winsock_ver;
8df7a775 248 WSAEVENT stdinevent;
12dc4ec0 249 HANDLE handles[2];
12dc4ec0 250 DWORD threadid;
251 struct input_data idata;
252 int sending;
d8426c54 253 int portnumber = -1;
8df7a775 254 SOCKET *sklist;
255 int skcount, sksize;
256 int connopen;
12dc4ec0 257
67779be7 258 ssh_get_password = get_password;
259
8df7a775 260 sklist = NULL; skcount = sksize = 0;
261
67779be7 262 flags = FLAG_STDERR;
12dc4ec0 263 /*
264 * Process the command line.
265 */
a9422f39 266 do_defaults(NULL, &cfg);
e7a7383f 267 default_protocol = cfg.protocol;
268 default_port = cfg.port;
8cb9c947 269 {
270 /*
271 * Override the default protocol if PLINK_PROTOCOL is set.
272 */
273 char *p = getenv("PLINK_PROTOCOL");
274 int i;
275 if (p) {
276 for (i = 0; backends[i].backend != NULL; i++) {
277 if (!strcmp(backends[i].name, p)) {
278 default_protocol = cfg.protocol = backends[i].protocol;
279 default_port = cfg.port = backends[i].backend->default_port;
280 break;
281 }
282 }
283 }
284 }
12dc4ec0 285 while (--argc) {
286 char *p = *++argv;
287 if (*p == '-') {
288 if (!strcmp(p, "-ssh")) {
289 default_protocol = cfg.protocol = PROT_SSH;
290 default_port = cfg.port = 22;
9d33ebdd 291 } else if (!strcmp(p, "-telnet")) {
292 default_protocol = cfg.protocol = PROT_TELNET;
293 default_port = cfg.port = 23;
294 } else if (!strcmp(p, "-raw")) {
295 default_protocol = cfg.protocol = PROT_RAW;
67779be7 296 } else if (!strcmp(p, "-v")) {
297 flags |= FLAG_VERBOSE;
12dc4ec0 298 } else if (!strcmp(p, "-log")) {
299 logfile = "putty.log";
d8426c54 300 } else if (!strcmp(p, "-pw") && argc > 1) {
301 --argc, password = *++argv;
e7a7383f 302 } else if (!strcmp(p, "-l") && argc > 1) {
303 char *username;
304 --argc, username = *++argv;
305 strncpy(cfg.username, username, sizeof(cfg.username));
306 cfg.username[sizeof(cfg.username)-1] = '\0';
d8426c54 307 } else if (!strcmp(p, "-P") && argc > 1) {
308 --argc, portnumber = atoi(*++argv);
309 }
12dc4ec0 310 } else if (*p) {
311 if (!*cfg.host) {
312 char *q = p;
313 /*
314 * If the hostname starts with "telnet:", set the
315 * protocol to Telnet and process the string as a
316 * Telnet URL.
317 */
318 if (!strncmp(q, "telnet:", 7)) {
319 char c;
320
321 q += 7;
322 if (q[0] == '/' && q[1] == '/')
323 q += 2;
324 cfg.protocol = PROT_TELNET;
325 p = q;
326 while (*p && *p != ':' && *p != '/') p++;
327 c = *p;
328 if (*p)
329 *p++ = '\0';
330 if (c == ':')
331 cfg.port = atoi(p);
332 else
333 cfg.port = -1;
334 strncpy (cfg.host, q, sizeof(cfg.host)-1);
335 cfg.host[sizeof(cfg.host)-1] = '\0';
336 } else {
9d33ebdd 337 char *r;
338 /*
339 * Before we process the [user@]host string, we
340 * first check for the presence of a protocol
341 * prefix (a protocol name followed by ",").
342 */
343 r = strchr(p, ',');
344 if (r) {
345 int i, j;
346 for (i = 0; backends[i].backend != NULL; i++) {
347 j = strlen(backends[i].name);
348 if (j == r-p &&
349 !memcmp(backends[i].name, p, j)) {
350 default_protocol = cfg.protocol = backends[i].protocol;
351 portnumber = backends[i].backend->default_port;
352 p = r+1;
353 break;
354 }
355 }
356 }
357
12dc4ec0 358 /*
359 * Three cases. Either (a) there's a nonzero
360 * length string followed by an @, in which
361 * case that's user and the remainder is host.
362 * Or (b) there's only one string, not counting
363 * a potential initial @, and it exists in the
364 * saved-sessions database. Or (c) only one
365 * string and it _doesn't_ exist in the
366 * database.
367 */
9d33ebdd 368 r = strrchr(p, '@');
12dc4ec0 369 if (r == p) p++, r = NULL; /* discount initial @ */
370 if (r == NULL) {
371 /*
372 * One string.
373 */
9c41b8ea 374 Config cfg2;
375 do_defaults (p, &cfg2);
376 if (cfg2.host[0] == '\0') {
12dc4ec0 377 /* No settings for this host; use defaults */
378 strncpy(cfg.host, p, sizeof(cfg.host)-1);
379 cfg.host[sizeof(cfg.host)-1] = '\0';
380 cfg.port = 22;
9c41b8ea 381 } else
382 cfg = cfg2;
12dc4ec0 383 } else {
384 *r++ = '\0';
385 strncpy(cfg.username, p, sizeof(cfg.username)-1);
386 cfg.username[sizeof(cfg.username)-1] = '\0';
387 strncpy(cfg.host, r, sizeof(cfg.host)-1);
388 cfg.host[sizeof(cfg.host)-1] = '\0';
389 cfg.port = 22;
390 }
391 }
392 } else {
393 int len = sizeof(cfg.remote_cmd) - 1;
394 char *cp = cfg.remote_cmd;
395 int len2;
396
397 strncpy(cp, p, len); cp[len] = '\0';
398 len2 = strlen(cp); len -= len2; cp += len2;
399 while (--argc) {
400 if (len > 0)
401 len--, *cp++ = ' ';
402 strncpy(cp, *++argv, len); cp[len] = '\0';
403 len2 = strlen(cp); len -= len2; cp += len2;
404 }
405 cfg.nopty = TRUE; /* command => no terminal */
406 cfg.ldisc_term = TRUE; /* use stdin like a line buffer */
407 break; /* done with cmdline */
408 }
409 }
410 }
411
d8426c54 412 if (!*cfg.host) {
413 usage();
414 }
d8426c54 415
67779be7 416 if (!*cfg.remote_cmd)
417 flags |= FLAG_INTERACTIVE;
418
12dc4ec0 419 /*
420 * Select protocol. This is farmed out into a table in a
421 * separate file to enable an ssh-free variant.
422 */
423 {
424 int i;
425 back = NULL;
426 for (i = 0; backends[i].backend != NULL; i++)
427 if (backends[i].protocol == cfg.protocol) {
428 back = backends[i].backend;
429 break;
430 }
431 if (back == NULL) {
432 fprintf(stderr, "Internal fault: Unsupported protocol found\n");
433 return 1;
434 }
435 }
436
437 /*
8cb9c947 438 * Select port.
439 */
440 if (portnumber != -1)
441 cfg.port = portnumber;
442
443 /*
12dc4ec0 444 * Initialise WinSock.
445 */
446 winsock_ver = MAKEWORD(2, 0);
447 if (WSAStartup(winsock_ver, &wsadata)) {
448 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
449 MB_OK | MB_ICONEXCLAMATION);
450 return 1;
451 }
452 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
453 MessageBox(NULL, "WinSock version is incompatible with 2.0",
454 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
455 WSACleanup();
456 return 1;
457 }
8df7a775 458 sk_init();
12dc4ec0 459
460 /*
461 * Start up the connection.
462 */
8df7a775 463 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 464 {
465 char *error;
466 char *realhost;
467
8df7a775 468 error = back->init (cfg.host, cfg.port, &realhost);
12dc4ec0 469 if (error) {
470 fprintf(stderr, "Unable to open connection:\n%s", error);
471 return 1;
472 }
473 }
8df7a775 474 connopen = 1;
12dc4ec0 475
12dc4ec0 476 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
477
6f34e365 478 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &orig_console_mode);
479 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
12dc4ec0 480 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
fe50e814 481 errhandle = GetStdHandle(STD_ERROR_HANDLE);
12dc4ec0 482
483 /*
12dc4ec0 484 * Turn off ECHO and LINE input modes. We don't care if this
485 * call fails, because we know we aren't necessarily running in
486 * a console.
487 */
12dc4ec0 488 handles[0] = netevent;
489 handles[1] = stdinevent;
490 sending = FALSE;
491 while (1) {
492 int n;
8cb9c947 493
494 if (!sending && back->sendok()) {
495 /*
496 * Create a separate thread to read from stdin. This is
497 * a total pain, but I can't find another way to do it:
498 *
499 * - an overlapped ReadFile or ReadFileEx just doesn't
500 * happen; we get failure from ReadFileEx, and
501 * ReadFile blocks despite being given an OVERLAPPED
502 * structure. Perhaps we can't do overlapped reads
503 * on consoles. WHY THE HELL NOT?
504 *
505 * - WaitForMultipleObjects(netevent, console) doesn't
506 * work, because it signals the console when
507 * _anything_ happens, including mouse motions and
508 * other things that don't cause data to be readable
509 * - so we're back to ReadFile blocking.
510 */
511 idata.event = stdinevent;
8df7a775 512 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
8cb9c947 513 if (!CreateThread(NULL, 0, stdin_read_thread,
514 &idata, 0, &threadid)) {
515 fprintf(stderr, "Unable to create second thread\n");
516 exit(1);
517 }
518 sending = TRUE;
519 }
520
12dc4ec0 521 n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
522 if (n == 0) {
523 WSANETWORKEVENTS things;
8df7a775 524 enum234 e;
525 SOCKET socket;
526 extern SOCKET first_socket(enum234 *), next_socket(enum234 *);
527 extern int select_result(WPARAM, LPARAM);
528 int i;
529
530 /*
531 * We must not call select_result() for any socket
532 * until we have finished enumerating within the tree.
533 * This is because select_result() may close the socket
534 * and modify the tree.
535 */
536 /* Count the active sockets. */
537 i = 0;
538 for (socket = first_socket(&e); socket != INVALID_SOCKET;
539 socket = next_socket(&e))
540 i++;
541
542 /* Expand the buffer if necessary. */
543 if (i > sksize) {
544 sksize = i+16;
545 sklist = srealloc(sklist, sksize * sizeof(*sklist));
546 }
547
548 /* Retrieve the sockets into sklist. */
549 skcount = 0;
550 for (socket = first_socket(&e); socket != INVALID_SOCKET;
551 socket = next_socket(&e)) {
552 sklist[skcount++] = socket;
12dc4ec0 553 }
8df7a775 554
555 /* Now we're done enumerating; go through the list. */
556 for (i = 0; i < skcount; i++) {
557 WPARAM wp;
558 socket = sklist[i];
559 wp = (WPARAM)socket;
ffb959c7 560 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
7d6ee6ff 561 noise_ultralight(socket);
562 noise_ultralight(things.lNetworkEvents);
8df7a775 563 if (things.lNetworkEvents & FD_READ)
564 connopen &= select_result(wp, (LPARAM)FD_READ);
565 if (things.lNetworkEvents & FD_CLOSE)
566 connopen &= select_result(wp, (LPARAM)FD_CLOSE);
567 if (things.lNetworkEvents & FD_OOB)
568 connopen &= select_result(wp, (LPARAM)FD_OOB);
569 if (things.lNetworkEvents & FD_WRITE)
570 connopen &= select_result(wp, (LPARAM)FD_WRITE);
571 }
572 }
12dc4ec0 573 } else if (n == 1) {
7d6ee6ff 574 noise_ultralight(idata.len);
12dc4ec0 575 if (idata.len > 0) {
576 back->send(idata.buffer, idata.len);
577 } else {
578 back->special(TS_EOF);
579 }
8df7a775 580 SetEvent(idata.eventback);
12dc4ec0 581 }
8df7a775 582 if (!connopen || back->socket() == NULL)
cf6e59d6 583 break; /* we closed the connection */
12dc4ec0 584 }
585 WSACleanup();
586 return 0;
587}