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