Configurable TCP_NODELAY option on network connections
[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 <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 #define MAX_STDIN_BACKLOG 4096
19
20 void fatalbox(char *p, ...)
21 {
22 va_list ap;
23 fprintf(stderr, "FATAL ERROR: ");
24 va_start(ap, p);
25 vfprintf(stderr, p, ap);
26 va_end(ap);
27 fputc('\n', stderr);
28 WSACleanup();
29 exit(1);
30 }
31 void connection_fatal(char *p, ...)
32 {
33 va_list ap;
34 fprintf(stderr, "FATAL ERROR: ");
35 va_start(ap, p);
36 vfprintf(stderr, p, ap);
37 va_end(ap);
38 fputc('\n', stderr);
39 WSACleanup();
40 exit(1);
41 }
42
43 static char *password = NULL;
44
45 void logevent(char *string)
46 {
47 }
48
49 void verify_ssh_host_key(char *host, int port, char *keytype,
50 char *keystr, char *fingerprint)
51 {
52 int ret;
53 HANDLE hin;
54 DWORD savemode, i;
55
56 static const char absentmsg[] =
57 "The server's host key is not cached in the registry. You\n"
58 "have no guarantee that the server is the computer you\n"
59 "think it is.\n"
60 "The server's key fingerprint is:\n"
61 "%s\n"
62 "If you trust this host, enter \"y\" to add the key to\n"
63 "PuTTY's cache and carry on connecting.\n"
64 "If you want to carry on connecting just once, without\n"
65 "adding the key to the cache, enter \"n\".\n"
66 "If you do not trust this host, press Return to abandon the\n"
67 "connection.\n"
68 "Store key in cache? (y/n) ";
69
70 static const char wrongmsg[] =
71 "WARNING - POTENTIAL SECURITY BREACH!\n"
72 "The server's host key does not match the one PuTTY has\n"
73 "cached in the registry. This means that either the\n"
74 "server administrator has changed the host key, or you\n"
75 "have actually connected to another computer pretending\n"
76 "to be the server.\n"
77 "The new key fingerprint is:\n"
78 "%s\n"
79 "If you were expecting this change and trust the new key,\n"
80 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
81 "If you want to carry on connecting but without updating\n"
82 "the cache, enter \"n\".\n"
83 "If you want to abandon the connection completely, press\n"
84 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
85 "safe choice.\n"
86 "Update cached key? (y/n, Return cancels connection) ";
87
88 static const char abandoned[] = "Connection abandoned.\n";
89
90 char line[32];
91
92 /*
93 * Verify the key against the registry.
94 */
95 ret = verify_host_key(host, port, keytype, keystr);
96
97 if (ret == 0) /* success - key matched OK */
98 return;
99
100 if (ret == 2) { /* key was different */
101 fprintf(stderr, wrongmsg, fingerprint);
102 fflush(stderr);
103 }
104 if (ret == 1) { /* key was absent */
105 fprintf(stderr, absentmsg, fingerprint);
106 fflush(stderr);
107 }
108
109 hin = GetStdHandle(STD_INPUT_HANDLE);
110 GetConsoleMode(hin, &savemode);
111 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
112 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
113 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
114 SetConsoleMode(hin, savemode);
115
116 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
117 if (line[0] == 'y' || line[0] == 'Y')
118 store_host_key(host, port, keytype, keystr);
119 } else {
120 fprintf(stderr, abandoned);
121 exit(0);
122 }
123 }
124
125 /*
126 * Ask whether the selected cipher is acceptable (since it was
127 * below the configured 'warn' threshold).
128 * cs: 0 = both ways, 1 = client->server, 2 = server->client
129 */
130 void askcipher(char *ciphername, int cs)
131 {
132 HANDLE hin;
133 DWORD savemode, i;
134
135 static const char msg[] =
136 "The first %scipher supported by the server is\n"
137 "%s, which is below the configured warning threshold.\n"
138 "Continue with connection? (y/n) ";
139 static const char abandoned[] = "Connection abandoned.\n";
140
141 char line[32];
142
143 fprintf(stderr, msg,
144 (cs == 0) ? "" :
145 (cs == 1) ? "client-to-server " :
146 "server-to-client ",
147 ciphername);
148 fflush(stderr);
149
150 hin = GetStdHandle(STD_INPUT_HANDLE);
151 GetConsoleMode(hin, &savemode);
152 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
153 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
154 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
155 SetConsoleMode(hin, savemode);
156
157 if (line[0] == 'y' || line[0] == 'Y') {
158 return;
159 } else {
160 fprintf(stderr, abandoned);
161 exit(0);
162 }
163 }
164
165 /*
166 * Warn about the obsolescent key file format.
167 */
168 void old_keyfile_warning(void)
169 {
170 static const char message[] =
171 "You are loading an SSH 2 private key which has an\n"
172 "old version of the file format. This means your key\n"
173 "file is not fully tamperproof. Future versions of\n"
174 "PuTTY may stop supporting this private key format,\n"
175 "so we recommend you convert your key to the new\n"
176 "format.\n"
177 "\n"
178 "Once the key is loaded into PuTTYgen, you can perform\n"
179 "this conversion simply by saving it again.\n";
180
181 fputs(message, stderr);
182 }
183
184 HANDLE inhandle, outhandle, errhandle;
185 DWORD orig_console_mode;
186
187 WSAEVENT netevent;
188
189 int term_ldisc(int mode)
190 {
191 return FALSE;
192 }
193 void ldisc_update(int echo, int edit)
194 {
195 /* Update stdin read mode to reflect changes in line discipline. */
196 DWORD mode;
197
198 mode = ENABLE_PROCESSED_INPUT;
199 if (echo)
200 mode = mode | ENABLE_ECHO_INPUT;
201 else
202 mode = mode & ~ENABLE_ECHO_INPUT;
203 if (edit)
204 mode = mode | ENABLE_LINE_INPUT;
205 else
206 mode = mode & ~ENABLE_LINE_INPUT;
207 SetConsoleMode(inhandle, mode);
208 }
209
210 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
211 {
212 HANDLE hin, hout;
213 DWORD savemode, newmode, i;
214
215 if (is_pw && password) {
216 static int tried_once = 0;
217
218 if (tried_once) {
219 return 0;
220 } else {
221 strncpy(str, password, maxlen);
222 str[maxlen - 1] = '\0';
223 tried_once = 1;
224 return 1;
225 }
226 }
227
228 hin = GetStdHandle(STD_INPUT_HANDLE);
229 hout = GetStdHandle(STD_OUTPUT_HANDLE);
230 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
231 fprintf(stderr, "Cannot get standard input/output handles");
232 return 0;
233 }
234
235 GetConsoleMode(hin, &savemode);
236 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
237 if (is_pw)
238 newmode &= ~ENABLE_ECHO_INPUT;
239 else
240 newmode |= ENABLE_ECHO_INPUT;
241 SetConsoleMode(hin, newmode);
242
243 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
244 ReadFile(hin, str, maxlen - 1, &i, NULL);
245
246 SetConsoleMode(hin, savemode);
247
248 if ((int) i > maxlen)
249 i = maxlen - 1;
250 else
251 i = i - 2;
252 str[i] = '\0';
253
254 if (is_pw)
255 WriteFile(hout, "\r\n", 2, &i, NULL);
256
257 return 1;
258 }
259
260 struct input_data {
261 DWORD len;
262 char buffer[4096];
263 HANDLE event, eventback;
264 };
265
266 static DWORD WINAPI stdin_read_thread(void *param)
267 {
268 struct input_data *idata = (struct input_data *) param;
269 HANDLE inhandle;
270
271 inhandle = GetStdHandle(STD_INPUT_HANDLE);
272
273 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
274 &idata->len, NULL) && idata->len > 0) {
275 SetEvent(idata->event);
276 WaitForSingleObject(idata->eventback, INFINITE);
277 }
278
279 idata->len = 0;
280 SetEvent(idata->event);
281
282 return 0;
283 }
284
285 struct output_data {
286 DWORD len, lenwritten;
287 int writeret;
288 char *buffer;
289 int is_stderr, done;
290 HANDLE event, eventback;
291 int busy;
292 };
293
294 static DWORD WINAPI stdout_write_thread(void *param)
295 {
296 struct output_data *odata = (struct output_data *) param;
297 HANDLE outhandle, errhandle;
298
299 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
300 errhandle = GetStdHandle(STD_ERROR_HANDLE);
301
302 while (1) {
303 WaitForSingleObject(odata->eventback, INFINITE);
304 if (odata->done)
305 break;
306 odata->writeret =
307 WriteFile(odata->is_stderr ? errhandle : outhandle,
308 odata->buffer, odata->len, &odata->lenwritten, NULL);
309 SetEvent(odata->event);
310 }
311
312 return 0;
313 }
314
315 bufchain stdout_data, stderr_data;
316 struct output_data odata, edata;
317
318 void try_output(int is_stderr)
319 {
320 struct output_data *data = (is_stderr ? &edata : &odata);
321 void *senddata;
322 int sendlen;
323
324 if (!data->busy) {
325 bufchain_prefix(is_stderr ? &stderr_data : &stdout_data,
326 &senddata, &sendlen);
327 data->buffer = senddata;
328 data->len = sendlen;
329 SetEvent(data->eventback);
330 data->busy = 1;
331 }
332 }
333
334 int from_backend(int is_stderr, char *data, int len)
335 {
336 HANDLE h = (is_stderr ? errhandle : outhandle);
337 int osize, esize;
338
339 if (is_stderr) {
340 bufchain_add(&stderr_data, data, len);
341 try_output(1);
342 } else {
343 bufchain_add(&stdout_data, data, len);
344 try_output(0);
345 }
346
347 osize = bufchain_size(&stdout_data);
348 esize = bufchain_size(&stderr_data);
349
350 return osize + esize;
351 }
352
353 /*
354 * Short description of parameters.
355 */
356 static void usage(void)
357 {
358 printf("PuTTY Link: command-line connection utility\n");
359 printf("%s\n", ver);
360 printf("Usage: plink [options] [user@]host [command]\n");
361 printf(" (\"host\" can also be a PuTTY saved session name)\n");
362 printf("Options:\n");
363 printf(" -v show verbose messages\n");
364 printf(" -ssh force use of ssh protocol\n");
365 printf(" -P port connect to specified port\n");
366 printf(" -pw passw login with specified password\n");
367 printf(" -m file read remote command(s) from file\n");
368 printf(" -L listen-port:host:port Forward local port to "
369 "remote address\n");
370 printf(" -R listen-port:host:port Forward remote port to"
371 " local address\n");
372 exit(1);
373 }
374
375 char *do_select(SOCKET skt, int startup)
376 {
377 int events;
378 if (startup) {
379 events = (FD_CONNECT | FD_READ | FD_WRITE |
380 FD_OOB | FD_CLOSE | FD_ACCEPT);
381 } else {
382 events = 0;
383 }
384 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
385 switch (WSAGetLastError()) {
386 case WSAENETDOWN:
387 return "Network is down";
388 default:
389 return "WSAAsyncSelect(): unknown error";
390 }
391 }
392 return NULL;
393 }
394
395 int main(int argc, char **argv)
396 {
397 WSADATA wsadata;
398 WORD winsock_ver;
399 WSAEVENT stdinevent, stdoutevent, stderrevent;
400 HANDLE handles[4];
401 DWORD in_threadid, out_threadid, err_threadid;
402 struct input_data idata;
403 int reading;
404 int sending;
405 int portnumber = -1;
406 SOCKET *sklist;
407 int skcount, sksize;
408 int connopen;
409 char extra_portfwd[sizeof(cfg.portfwd)];
410
411 ssh_get_line = get_line;
412
413 sklist = NULL;
414 skcount = sksize = 0;
415 /*
416 * Initialise port and protocol to sensible defaults. (These
417 * will be overridden by more or less anything.)
418 */
419 default_protocol = PROT_SSH;
420 default_port = 22;
421
422 flags = FLAG_STDERR;
423 /*
424 * Process the command line.
425 */
426 do_defaults(NULL, &cfg);
427 default_protocol = cfg.protocol;
428 default_port = cfg.port;
429 {
430 /*
431 * Override the default protocol if PLINK_PROTOCOL is set.
432 */
433 char *p = getenv("PLINK_PROTOCOL");
434 int i;
435 if (p) {
436 for (i = 0; backends[i].backend != NULL; i++) {
437 if (!strcmp(backends[i].name, p)) {
438 default_protocol = cfg.protocol = backends[i].protocol;
439 default_port = cfg.port =
440 backends[i].backend->default_port;
441 break;
442 }
443 }
444 }
445 }
446 while (--argc) {
447 char *p = *++argv;
448 if (*p == '-') {
449 if (!strcmp(p, "-ssh")) {
450 default_protocol = cfg.protocol = PROT_SSH;
451 default_port = cfg.port = 22;
452 } else if (!strcmp(p, "-telnet")) {
453 default_protocol = cfg.protocol = PROT_TELNET;
454 default_port = cfg.port = 23;
455 } else if (!strcmp(p, "-raw")) {
456 default_protocol = cfg.protocol = PROT_RAW;
457 } else if (!strcmp(p, "-v")) {
458 flags |= FLAG_VERBOSE;
459 } else if (!strcmp(p, "-log")) {
460 logfile = "putty.log";
461 } else if (!strcmp(p, "-pw") && argc > 1) {
462 --argc, password = *++argv;
463 } else if (!strcmp(p, "-l") && argc > 1) {
464 char *username;
465 --argc, username = *++argv;
466 strncpy(cfg.username, username, sizeof(cfg.username));
467 cfg.username[sizeof(cfg.username) - 1] = '\0';
468 } else if ((!strcmp(p, "-L") || !strcmp(p, "-R")) && argc > 1) {
469 char *fwd, *ptr, *q;
470 int i=0;
471 --argc, fwd = *++argv;
472 ptr = extra_portfwd;
473 /* if multiple forwards, find end of list */
474 if (ptr[0]=='R' || ptr[0]=='L') {
475 for (i = 0; i < sizeof(extra_portfwd) - 2; i++)
476 if (ptr[i]=='\000' && ptr[i+1]=='\000')
477 break;
478 ptr = ptr + i + 1; /* point to next forward slot */
479 }
480 ptr[0] = p[1]; /* insert a 'L' or 'R' at the start */
481 strncpy(ptr+1, fwd, sizeof(extra_portfwd) - i);
482 q = strchr(ptr, ':');
483 if (q) *q = '\t'; /* replace first : with \t */
484 ptr[strlen(ptr)+1] = '\000'; /* append two '\000' */
485 extra_portfwd[sizeof(extra_portfwd) - 1] = '\0';
486 } else if (!strcmp(p, "-m") && argc > 1) {
487 char *filename, *command;
488 int cmdlen, cmdsize;
489 FILE *fp;
490 int c, d;
491
492 --argc, filename = *++argv;
493
494 cmdlen = cmdsize = 0;
495 command = NULL;
496 fp = fopen(filename, "r");
497 if (!fp) {
498 fprintf(stderr, "plink: unable to open command "
499 "file \"%s\"\n", filename);
500 return 1;
501 }
502 do {
503 c = fgetc(fp);
504 d = c;
505 if (c == EOF)
506 d = 0;
507 if (cmdlen >= cmdsize) {
508 cmdsize = cmdlen + 512;
509 command = srealloc(command, cmdsize);
510 }
511 command[cmdlen++] = d;
512 } while (c != EOF);
513 cfg.remote_cmd_ptr = command;
514 cfg.remote_cmd_ptr2 = NULL;
515 cfg.nopty = TRUE; /* command => no terminal */
516 } else if (!strcmp(p, "-P") && argc > 1) {
517 --argc, portnumber = atoi(*++argv);
518 }
519 } else if (*p) {
520 if (!*cfg.host) {
521 char *q = p;
522 /*
523 * If the hostname starts with "telnet:", set the
524 * protocol to Telnet and process the string as a
525 * Telnet URL.
526 */
527 if (!strncmp(q, "telnet:", 7)) {
528 char c;
529
530 q += 7;
531 if (q[0] == '/' && q[1] == '/')
532 q += 2;
533 cfg.protocol = PROT_TELNET;
534 p = q;
535 while (*p && *p != ':' && *p != '/')
536 p++;
537 c = *p;
538 if (*p)
539 *p++ = '\0';
540 if (c == ':')
541 cfg.port = atoi(p);
542 else
543 cfg.port = -1;
544 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
545 cfg.host[sizeof(cfg.host) - 1] = '\0';
546 } else {
547 char *r;
548 /*
549 * Before we process the [user@]host string, we
550 * first check for the presence of a protocol
551 * prefix (a protocol name followed by ",").
552 */
553 r = strchr(p, ',');
554 if (r) {
555 int i, j;
556 for (i = 0; backends[i].backend != NULL; i++) {
557 j = strlen(backends[i].name);
558 if (j == r - p &&
559 !memcmp(backends[i].name, p, j)) {
560 default_protocol = cfg.protocol =
561 backends[i].protocol;
562 portnumber =
563 backends[i].backend->default_port;
564 p = r + 1;
565 break;
566 }
567 }
568 }
569
570 /*
571 * Three cases. Either (a) there's a nonzero
572 * length string followed by an @, in which
573 * case that's user and the remainder is host.
574 * Or (b) there's only one string, not counting
575 * a potential initial @, and it exists in the
576 * saved-sessions database. Or (c) only one
577 * string and it _doesn't_ exist in the
578 * database.
579 */
580 r = strrchr(p, '@');
581 if (r == p)
582 p++, r = NULL; /* discount initial @ */
583 if (r == NULL) {
584 /*
585 * One string.
586 */
587 Config cfg2;
588 do_defaults(p, &cfg2);
589 if (cfg2.host[0] == '\0') {
590 /* No settings for this host; use defaults */
591 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
592 cfg.host[sizeof(cfg.host) - 1] = '\0';
593 cfg.port = default_port;
594 } else {
595 cfg = cfg2;
596 cfg.remote_cmd_ptr = cfg.remote_cmd;
597 }
598 } else {
599 *r++ = '\0';
600 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
601 cfg.username[sizeof(cfg.username) - 1] = '\0';
602 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
603 cfg.host[sizeof(cfg.host) - 1] = '\0';
604 cfg.port = default_port;
605 }
606 }
607 } else {
608 int len = sizeof(cfg.remote_cmd) - 1;
609 char *cp = cfg.remote_cmd;
610 int len2;
611
612 strncpy(cp, p, len);
613 cp[len] = '\0';
614 len2 = strlen(cp);
615 len -= len2;
616 cp += len2;
617 while (--argc) {
618 if (len > 0)
619 len--, *cp++ = ' ';
620 strncpy(cp, *++argv, len);
621 cp[len] = '\0';
622 len2 = strlen(cp);
623 len -= len2;
624 cp += len2;
625 }
626 cfg.nopty = TRUE; /* command => no terminal */
627 break; /* done with cmdline */
628 }
629 }
630 }
631
632 if (!*cfg.host) {
633 usage();
634 }
635
636 /*
637 * Trim leading whitespace off the hostname if it's there.
638 */
639 {
640 int space = strspn(cfg.host, " \t");
641 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
642 }
643
644 /* See if host is of the form user@host */
645 if (cfg.host[0] != '\0') {
646 char *atsign = strchr(cfg.host, '@');
647 /* Make sure we're not overflowing the user field */
648 if (atsign) {
649 if (atsign - cfg.host < sizeof cfg.username) {
650 strncpy(cfg.username, cfg.host, atsign - cfg.host);
651 cfg.username[atsign - cfg.host] = '\0';
652 }
653 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
654 }
655 }
656
657 /*
658 * Trim a colon suffix off the hostname if it's there.
659 */
660 cfg.host[strcspn(cfg.host, ":")] = '\0';
661
662 if (!*cfg.remote_cmd_ptr)
663 flags |= FLAG_INTERACTIVE;
664
665 /*
666 * Select protocol. This is farmed out into a table in a
667 * separate file to enable an ssh-free variant.
668 */
669 {
670 int i;
671 back = NULL;
672 for (i = 0; backends[i].backend != NULL; i++)
673 if (backends[i].protocol == cfg.protocol) {
674 back = backends[i].backend;
675 break;
676 }
677 if (back == NULL) {
678 fprintf(stderr,
679 "Internal fault: Unsupported protocol found\n");
680 return 1;
681 }
682 }
683
684 /*
685 * Add extra port forwardings (accumulated on command line) to
686 * cfg.
687 */
688 {
689 int i;
690 char *p;
691 p = extra_portfwd;
692 i = 0;
693 while (cfg.portfwd[i])
694 i += strlen(cfg.portfwd+i) + 1;
695 while (*p) {
696 if (strlen(p)+2 > sizeof(cfg.portfwd)-i) {
697 fprintf(stderr, "Internal fault: not enough space for all"
698 " port forwardings\n");
699 break;
700 }
701 strncpy(cfg.portfwd+i, p, sizeof(cfg.portfwd)-i-1);
702 i += strlen(cfg.portfwd+i) + 1;
703 cfg.portfwd[i] = '\0';
704 p += strlen(p)+1;
705 }
706 }
707
708 /*
709 * Select port.
710 */
711 if (portnumber != -1)
712 cfg.port = portnumber;
713
714 /*
715 * Initialise WinSock.
716 */
717 winsock_ver = MAKEWORD(2, 0);
718 if (WSAStartup(winsock_ver, &wsadata)) {
719 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
720 MB_OK | MB_ICONEXCLAMATION);
721 return 1;
722 }
723 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
724 MessageBox(NULL, "WinSock version is incompatible with 2.0",
725 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
726 WSACleanup();
727 return 1;
728 }
729 sk_init();
730
731 /*
732 * Start up the connection.
733 */
734 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
735 {
736 char *error;
737 char *realhost;
738 /* nodelay is only useful if stdin is a character device (console) */
739 int nodelay = cfg.tcp_nodelay &&
740 (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
741
742 error = back->init(cfg.host, cfg.port, &realhost, nodelay);
743 if (error) {
744 fprintf(stderr, "Unable to open connection:\n%s", error);
745 return 1;
746 }
747 sfree(realhost);
748 }
749 connopen = 1;
750
751 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
752 stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
753 stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
754
755 inhandle = GetStdHandle(STD_INPUT_HANDLE);
756 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
757 errhandle = GetStdHandle(STD_ERROR_HANDLE);
758 GetConsoleMode(inhandle, &orig_console_mode);
759 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
760
761 /*
762 * Turn off ECHO and LINE input modes. We don't care if this
763 * call fails, because we know we aren't necessarily running in
764 * a console.
765 */
766 handles[0] = netevent;
767 handles[1] = stdinevent;
768 handles[2] = stdoutevent;
769 handles[3] = stderrevent;
770 sending = FALSE;
771
772 /*
773 * Create spare threads to write to stdout and stderr, so we
774 * can arrange asynchronous writes.
775 */
776 odata.event = stdoutevent;
777 odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
778 odata.is_stderr = 0;
779 odata.busy = odata.done = 0;
780 if (!CreateThread(NULL, 0, stdout_write_thread,
781 &odata, 0, &out_threadid)) {
782 fprintf(stderr, "Unable to create output thread\n");
783 exit(1);
784 }
785 edata.event = stderrevent;
786 edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
787 edata.is_stderr = 1;
788 edata.busy = edata.done = 0;
789 if (!CreateThread(NULL, 0, stdout_write_thread,
790 &edata, 0, &err_threadid)) {
791 fprintf(stderr, "Unable to create error output thread\n");
792 exit(1);
793 }
794
795 while (1) {
796 int n;
797
798 if (!sending && back->sendok()) {
799 /*
800 * Create a separate thread to read from stdin. This is
801 * a total pain, but I can't find another way to do it:
802 *
803 * - an overlapped ReadFile or ReadFileEx just doesn't
804 * happen; we get failure from ReadFileEx, and
805 * ReadFile blocks despite being given an OVERLAPPED
806 * structure. Perhaps we can't do overlapped reads
807 * on consoles. WHY THE HELL NOT?
808 *
809 * - WaitForMultipleObjects(netevent, console) doesn't
810 * work, because it signals the console when
811 * _anything_ happens, including mouse motions and
812 * other things that don't cause data to be readable
813 * - so we're back to ReadFile blocking.
814 */
815 idata.event = stdinevent;
816 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
817 if (!CreateThread(NULL, 0, stdin_read_thread,
818 &idata, 0, &in_threadid)) {
819 fprintf(stderr, "Unable to create input thread\n");
820 exit(1);
821 }
822 sending = TRUE;
823 }
824
825 n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
826 if (n == 0) {
827 WSANETWORKEVENTS things;
828 SOCKET socket;
829 extern SOCKET first_socket(int *), next_socket(int *);
830 extern int select_result(WPARAM, LPARAM);
831 int i, socketstate;
832
833 /*
834 * We must not call select_result() for any socket
835 * until we have finished enumerating within the tree.
836 * This is because select_result() may close the socket
837 * and modify the tree.
838 */
839 /* Count the active sockets. */
840 i = 0;
841 for (socket = first_socket(&socketstate);
842 socket != INVALID_SOCKET;
843 socket = next_socket(&socketstate)) i++;
844
845 /* Expand the buffer if necessary. */
846 if (i > sksize) {
847 sksize = i + 16;
848 sklist = srealloc(sklist, sksize * sizeof(*sklist));
849 }
850
851 /* Retrieve the sockets into sklist. */
852 skcount = 0;
853 for (socket = first_socket(&socketstate);
854 socket != INVALID_SOCKET;
855 socket = next_socket(&socketstate)) {
856 sklist[skcount++] = socket;
857 }
858
859 /* Now we're done enumerating; go through the list. */
860 for (i = 0; i < skcount; i++) {
861 WPARAM wp;
862 socket = sklist[i];
863 wp = (WPARAM) socket;
864 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
865 noise_ultralight(socket);
866 noise_ultralight(things.lNetworkEvents);
867 if (things.lNetworkEvents & FD_CONNECT)
868 connopen &= select_result(wp, (LPARAM) FD_CONNECT);
869 if (things.lNetworkEvents & FD_READ)
870 connopen &= select_result(wp, (LPARAM) FD_READ);
871 if (things.lNetworkEvents & FD_CLOSE)
872 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
873 if (things.lNetworkEvents & FD_OOB)
874 connopen &= select_result(wp, (LPARAM) FD_OOB);
875 if (things.lNetworkEvents & FD_WRITE)
876 connopen &= select_result(wp, (LPARAM) FD_WRITE);
877 if (things.lNetworkEvents & FD_ACCEPT)
878 connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
879
880 }
881 }
882 } else if (n == 1) {
883 reading = 0;
884 noise_ultralight(idata.len);
885 if (connopen && back->socket() != NULL) {
886 if (idata.len > 0) {
887 back->send(idata.buffer, idata.len);
888 } else {
889 back->special(TS_EOF);
890 }
891 }
892 } else if (n == 2) {
893 odata.busy = 0;
894 if (!odata.writeret) {
895 fprintf(stderr, "Unable to write to standard output\n");
896 exit(0);
897 }
898 bufchain_consume(&stdout_data, odata.lenwritten);
899 if (bufchain_size(&stdout_data) > 0)
900 try_output(0);
901 if (connopen && back->socket() != NULL) {
902 back->unthrottle(bufchain_size(&stdout_data) +
903 bufchain_size(&stderr_data));
904 }
905 } else if (n == 3) {
906 edata.busy = 0;
907 if (!edata.writeret) {
908 fprintf(stderr, "Unable to write to standard output\n");
909 exit(0);
910 }
911 bufchain_consume(&stderr_data, edata.lenwritten);
912 if (bufchain_size(&stderr_data) > 0)
913 try_output(1);
914 if (connopen && back->socket() != NULL) {
915 back->unthrottle(bufchain_size(&stdout_data) +
916 bufchain_size(&stderr_data));
917 }
918 }
919 if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
920 SetEvent(idata.eventback);
921 reading = 1;
922 }
923 if ((!connopen || back->socket() == NULL) &&
924 bufchain_size(&stdout_data) == 0 &&
925 bufchain_size(&stderr_data) == 0)
926 break; /* we closed the connection */
927 }
928 WSACleanup();
929 return 0;
930 }