Wez Furlong's patch to tidy up full-screen mode: make it
[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
32874aea 13#define PUTTY_DO_GLOBALS /* actually _define_ globals */
12dc4ec0 14#include "putty.h"
a9422f39 15#include "storage.h"
8df7a775 16#include "tree234.h"
12dc4ec0 17
5471d09a 18#define MAX_STDIN_BACKLOG 4096
19
32874aea 20void fatalbox(char *p, ...)
21{
12dc4ec0 22 va_list ap;
49bad831 23 fprintf(stderr, "FATAL ERROR: ");
12dc4ec0 24 va_start(ap, p);
25 vfprintf(stderr, p, ap);
26 va_end(ap);
27 fputc('\n', stderr);
28 WSACleanup();
29 exit(1);
30}
32874aea 31void connection_fatal(char *p, ...)
32{
8d5de777 33 va_list ap;
49bad831 34 fprintf(stderr, "FATAL ERROR: ");
8d5de777 35 va_start(ap, p);
36 vfprintf(stderr, p, ap);
37 va_end(ap);
38 fputc('\n', stderr);
39 WSACleanup();
40 exit(1);
41}
12dc4ec0 42
d8426c54 43static char *password = NULL;
44
32874aea 45void logevent(char *string)
46{
47}
a9422f39 48
49void verify_ssh_host_key(char *host, int port, char *keytype,
32874aea 50 char *keystr, char *fingerprint)
51{
a9422f39 52 int ret;
fcbb94d3 53 HANDLE hin;
54 DWORD savemode, i;
a9422f39 55
56 static const char absentmsg[] =
32874aea 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"
d0718310 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) ";
a9422f39 69
70 static const char wrongmsg[] =
32874aea 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) ";
a9422f39 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
32874aea 97 if (ret == 0) /* success - key matched OK */
98 return;
fcbb94d3 99
b4453f49 100 if (ret == 2) { /* key was different */
32874aea 101 fprintf(stderr, wrongmsg, fingerprint);
b4453f49 102 fflush(stderr);
103 }
104 if (ret == 1) { /* key was absent */
32874aea 105 fprintf(stderr, absentmsg, fingerprint);
b4453f49 106 fflush(stderr);
107 }
fcbb94d3 108
109 hin = GetStdHandle(STD_INPUT_HANDLE);
110 GetConsoleMode(hin, &savemode);
111 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
32874aea 112 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
113 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
fcbb94d3 114 SetConsoleMode(hin, savemode);
115
d0718310 116 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
32874aea 117 if (line[0] == 'y' || line[0] == 'Y')
118 store_host_key(host, port, keytype, keystr);
d0718310 119 } else {
120 fprintf(stderr, abandoned);
121 exit(0);
a9422f39 122 }
123}
989b10e9 124
ca20bfcf 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 */
130void 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
0965bee0 165HANDLE inhandle, outhandle, errhandle;
6f34e365 166DWORD orig_console_mode;
167
8df7a775 168WSAEVENT netevent;
169
32874aea 170int term_ldisc(int mode)
171{
172 return FALSE;
173}
174void ldisc_update(int echo, int edit)
175{
0965bee0 176 /* Update stdin read mode to reflect changes in line discipline. */
177 DWORD mode;
178
179 mode = ENABLE_PROCESSED_INPUT;
180 if (echo)
32874aea 181 mode = mode | ENABLE_ECHO_INPUT;
0965bee0 182 else
32874aea 183 mode = mode & ~ENABLE_ECHO_INPUT;
0965bee0 184 if (edit)
32874aea 185 mode = mode | ENABLE_LINE_INPUT;
0965bee0 186 else
32874aea 187 mode = mode & ~ENABLE_LINE_INPUT;
0965bee0 188 SetConsoleMode(inhandle, mode);
189}
190
fa17a66e 191static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
67779be7 192{
193 HANDLE hin, hout;
fa17a66e 194 DWORD savemode, newmode, i;
67779be7 195
fa17a66e 196 if (is_pw && password) {
32874aea 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 }
67779be7 207 }
67779be7 208
209 hin = GetStdHandle(STD_INPUT_HANDLE);
210 hout = GetStdHandle(STD_OUTPUT_HANDLE);
211 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
32874aea 212 fprintf(stderr, "Cannot get standard input/output handles");
213 return 0;
67779be7 214 }
215
216 GetConsoleMode(hin, &savemode);
fa17a66e 217 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
218 if (is_pw)
32874aea 219 newmode &= ~ENABLE_ECHO_INPUT;
fa17a66e 220 else
32874aea 221 newmode |= ENABLE_ECHO_INPUT;
fa17a66e 222 SetConsoleMode(hin, newmode);
67779be7 223
224 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
32874aea 225 ReadFile(hin, str, maxlen - 1, &i, NULL);
67779be7 226
227 SetConsoleMode(hin, savemode);
228
32874aea 229 if ((int) i > maxlen)
230 i = maxlen - 1;
231 else
232 i = i - 2;
67779be7 233 str[i] = '\0';
234
fa17a66e 235 if (is_pw)
32874aea 236 WriteFile(hout, "\r\n", 2, &i, NULL);
67779be7 237
238 return 1;
239}
240
5471d09a 241struct input_data {
242 DWORD len;
243 char buffer[4096];
244 HANDLE event, eventback;
245};
246
32874aea 247static DWORD WINAPI stdin_read_thread(void *param)
248{
249 struct input_data *idata = (struct input_data *) param;
12dc4ec0 250 HANDLE inhandle;
251
252 inhandle = GetStdHandle(STD_INPUT_HANDLE);
253
254 while (ReadFile(inhandle, idata->buffer, sizeof(idata->buffer),
32874aea 255 &idata->len, NULL) && idata->len > 0) {
256 SetEvent(idata->event);
257 WaitForSingleObject(idata->eventback, INFINITE);
12dc4ec0 258 }
259
260 idata->len = 0;
261 SetEvent(idata->event);
262
263 return 0;
264}
265
5471d09a 266struct 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
275static 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
296bufchain stdout_data, stderr_data;
297struct output_data odata, edata;
298
299void 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
315int from_backend(int is_stderr, char *data, int len)
316{
5471d09a 317 HANDLE h = (is_stderr ? errhandle : outhandle);
5471d09a 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
d8426c54 334/*
335 * Short description of parameters.
336 */
337static 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");
e672967c 342 printf(" (\"host\" can also be a PuTTY saved session name)\n");
d8426c54 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");
96621a84 348 printf(" -m file read remote command(s) from file\n");
d8426c54 349 exit(1);
350}
351
32874aea 352char *do_select(SOCKET skt, int startup)
353{
8df7a775 354 int events;
355 if (startup) {
3ad9d396 356 events = (FD_CONNECT | FD_READ | FD_WRITE |
357 FD_OOB | FD_CLOSE | FD_ACCEPT);
8df7a775 358 } else {
359 events = 0;
360 }
32874aea 361 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
362 switch (WSAGetLastError()) {
363 case WSAENETDOWN:
364 return "Network is down";
365 default:
366 return "WSAAsyncSelect(): unknown error";
367 }
8df7a775 368 }
369 return NULL;
370}
371
32874aea 372int main(int argc, char **argv)
373{
12dc4ec0 374 WSADATA wsadata;
375 WORD winsock_ver;
5471d09a 376 WSAEVENT stdinevent, stdoutevent, stderrevent;
377 HANDLE handles[4];
378 DWORD in_threadid, out_threadid, err_threadid;
12dc4ec0 379 struct input_data idata;
5471d09a 380 int reading;
12dc4ec0 381 int sending;
d8426c54 382 int portnumber = -1;
8df7a775 383 SOCKET *sklist;
384 int skcount, sksize;
385 int connopen;
12dc4ec0 386
fa17a66e 387 ssh_get_line = get_line;
67779be7 388
32874aea 389 sklist = NULL;
390 skcount = sksize = 0;
c9bdcd96 391 /*
392 * Initialise port and protocol to sensible defaults. (These
393 * will be overridden by more or less anything.)
394 */
395 default_protocol = PROT_SSH;
396 default_port = 22;
8df7a775 397
67779be7 398 flags = FLAG_STDERR;
12dc4ec0 399 /*
400 * Process the command line.
401 */
a9422f39 402 do_defaults(NULL, &cfg);
e7a7383f 403 default_protocol = cfg.protocol;
404 default_port = cfg.port;
8cb9c947 405 {
32874aea 406 /*
407 * Override the default protocol if PLINK_PROTOCOL is set.
408 */
409 char *p = getenv("PLINK_PROTOCOL");
410 int i;
411 if (p) {
412 for (i = 0; backends[i].backend != NULL; i++) {
413 if (!strcmp(backends[i].name, p)) {
414 default_protocol = cfg.protocol = backends[i].protocol;
415 default_port = cfg.port =
416 backends[i].backend->default_port;
417 break;
418 }
419 }
420 }
8cb9c947 421 }
12dc4ec0 422 while (--argc) {
32874aea 423 char *p = *++argv;
424 if (*p == '-') {
425 if (!strcmp(p, "-ssh")) {
12dc4ec0 426 default_protocol = cfg.protocol = PROT_SSH;
427 default_port = cfg.port = 22;
32874aea 428 } else if (!strcmp(p, "-telnet")) {
9d33ebdd 429 default_protocol = cfg.protocol = PROT_TELNET;
430 default_port = cfg.port = 23;
32874aea 431 } else if (!strcmp(p, "-raw")) {
9d33ebdd 432 default_protocol = cfg.protocol = PROT_RAW;
67779be7 433 } else if (!strcmp(p, "-v")) {
32874aea 434 flags |= FLAG_VERBOSE;
12dc4ec0 435 } else if (!strcmp(p, "-log")) {
32874aea 436 logfile = "putty.log";
437 } else if (!strcmp(p, "-pw") && argc > 1) {
438 --argc, password = *++argv;
439 } else if (!strcmp(p, "-l") && argc > 1) {
440 char *username;
441 --argc, username = *++argv;
442 strncpy(cfg.username, username, sizeof(cfg.username));
443 cfg.username[sizeof(cfg.username) - 1] = '\0';
444 } else if (!strcmp(p, "-m") && argc > 1) {
445 char *filename, *command;
446 int cmdlen, cmdsize;
447 FILE *fp;
448 int c, d;
449
450 --argc, filename = *++argv;
451
452 cmdlen = cmdsize = 0;
453 command = NULL;
454 fp = fopen(filename, "r");
455 if (!fp) {
456 fprintf(stderr, "plink: unable to open command "
457 "file \"%s\"\n", filename);
458 return 1;
459 }
460 do {
461 c = fgetc(fp);
462 d = c;
463 if (c == EOF)
464 d = 0;
465 if (cmdlen >= cmdsize) {
466 cmdsize = cmdlen + 512;
467 command = srealloc(command, cmdsize);
468 }
469 command[cmdlen++] = d;
470 } while (c != EOF);
471 cfg.remote_cmd_ptr = command;
fd5e5847 472 cfg.remote_cmd_ptr2 = NULL;
32874aea 473 cfg.nopty = TRUE; /* command => no terminal */
474 } else if (!strcmp(p, "-P") && argc > 1) {
475 --argc, portnumber = atoi(*++argv);
476 }
12dc4ec0 477 } else if (*p) {
32874aea 478 if (!*cfg.host) {
479 char *q = p;
480 /*
481 * If the hostname starts with "telnet:", set the
482 * protocol to Telnet and process the string as a
483 * Telnet URL.
484 */
485 if (!strncmp(q, "telnet:", 7)) {
486 char c;
487
488 q += 7;
489 if (q[0] == '/' && q[1] == '/')
490 q += 2;
491 cfg.protocol = PROT_TELNET;
492 p = q;
493 while (*p && *p != ':' && *p != '/')
494 p++;
495 c = *p;
496 if (*p)
497 *p++ = '\0';
498 if (c == ':')
499 cfg.port = atoi(p);
500 else
501 cfg.port = -1;
502 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
503 cfg.host[sizeof(cfg.host) - 1] = '\0';
504 } else {
505 char *r;
506 /*
507 * Before we process the [user@]host string, we
508 * first check for the presence of a protocol
509 * prefix (a protocol name followed by ",").
510 */
511 r = strchr(p, ',');
512 if (r) {
513 int i, j;
514 for (i = 0; backends[i].backend != NULL; i++) {
515 j = strlen(backends[i].name);
516 if (j == r - p &&
517 !memcmp(backends[i].name, p, j)) {
518 default_protocol = cfg.protocol =
519 backends[i].protocol;
520 portnumber =
521 backends[i].backend->default_port;
522 p = r + 1;
523 break;
524 }
525 }
526 }
527
528 /*
529 * Three cases. Either (a) there's a nonzero
530 * length string followed by an @, in which
531 * case that's user and the remainder is host.
532 * Or (b) there's only one string, not counting
533 * a potential initial @, and it exists in the
534 * saved-sessions database. Or (c) only one
535 * string and it _doesn't_ exist in the
536 * database.
537 */
538 r = strrchr(p, '@');
539 if (r == p)
540 p++, r = NULL; /* discount initial @ */
541 if (r == NULL) {
542 /*
543 * One string.
544 */
545 Config cfg2;
546 do_defaults(p, &cfg2);
547 if (cfg2.host[0] == '\0') {
548 /* No settings for this host; use defaults */
549 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
550 cfg.host[sizeof(cfg.host) - 1] = '\0';
551 cfg.port = default_port;
552 } else {
553 cfg = cfg2;
554 cfg.remote_cmd_ptr = cfg.remote_cmd;
555 }
556 } else {
557 *r++ = '\0';
558 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
559 cfg.username[sizeof(cfg.username) - 1] = '\0';
560 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
561 cfg.host[sizeof(cfg.host) - 1] = '\0';
562 cfg.port = default_port;
563 }
564 }
565 } else {
566 int len = sizeof(cfg.remote_cmd) - 1;
567 char *cp = cfg.remote_cmd;
568 int len2;
569
570 strncpy(cp, p, len);
571 cp[len] = '\0';
572 len2 = strlen(cp);
573 len -= len2;
574 cp += len2;
575 while (--argc) {
576 if (len > 0)
577 len--, *cp++ = ' ';
578 strncpy(cp, *++argv, len);
579 cp[len] = '\0';
580 len2 = strlen(cp);
581 len -= len2;
582 cp += len2;
583 }
584 cfg.nopty = TRUE; /* command => no terminal */
585 break; /* done with cmdline */
586 }
12dc4ec0 587 }
588 }
589
d8426c54 590 if (!*cfg.host) {
32874aea 591 usage();
d8426c54 592 }
d8426c54 593
96621a84 594 if (!*cfg.remote_cmd_ptr)
32874aea 595 flags |= FLAG_INTERACTIVE;
67779be7 596
12dc4ec0 597 /*
598 * Select protocol. This is farmed out into a table in a
599 * separate file to enable an ssh-free variant.
600 */
601 {
32874aea 602 int i;
603 back = NULL;
604 for (i = 0; backends[i].backend != NULL; i++)
605 if (backends[i].protocol == cfg.protocol) {
606 back = backends[i].backend;
607 break;
608 }
609 if (back == NULL) {
610 fprintf(stderr,
611 "Internal fault: Unsupported protocol found\n");
612 return 1;
613 }
12dc4ec0 614 }
615
616 /*
8cb9c947 617 * Select port.
618 */
619 if (portnumber != -1)
32874aea 620 cfg.port = portnumber;
8cb9c947 621
622 /*
12dc4ec0 623 * Initialise WinSock.
624 */
625 winsock_ver = MAKEWORD(2, 0);
626 if (WSAStartup(winsock_ver, &wsadata)) {
627 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
628 MB_OK | MB_ICONEXCLAMATION);
629 return 1;
630 }
631 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
632 MessageBox(NULL, "WinSock version is incompatible with 2.0",
633 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
634 WSACleanup();
635 return 1;
636 }
8df7a775 637 sk_init();
12dc4ec0 638
639 /*
640 * Start up the connection.
641 */
8df7a775 642 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 643 {
644 char *error;
645 char *realhost;
646
32874aea 647 error = back->init(cfg.host, cfg.port, &realhost);
12dc4ec0 648 if (error) {
649 fprintf(stderr, "Unable to open connection:\n%s", error);
650 return 1;
651 }
6e1ebb76 652 sfree(realhost);
12dc4ec0 653 }
8df7a775 654 connopen = 1;
12dc4ec0 655
12dc4ec0 656 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
5471d09a 657 stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
658 stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 659
0965bee0 660 inhandle = GetStdHandle(STD_INPUT_HANDLE);
12dc4ec0 661 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
fe50e814 662 errhandle = GetStdHandle(STD_ERROR_HANDLE);
0965bee0 663 GetConsoleMode(inhandle, &orig_console_mode);
664 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
12dc4ec0 665
666 /*
12dc4ec0 667 * Turn off ECHO and LINE input modes. We don't care if this
668 * call fails, because we know we aren't necessarily running in
669 * a console.
670 */
12dc4ec0 671 handles[0] = netevent;
672 handles[1] = stdinevent;
5471d09a 673 handles[2] = stdoutevent;
674 handles[3] = stderrevent;
12dc4ec0 675 sending = FALSE;
5471d09a 676
677 /*
678 * Create spare threads to write to stdout and stderr, so we
679 * can arrange asynchronous writes.
680 */
681 odata.event = stdoutevent;
682 odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
683 odata.is_stderr = 0;
684 odata.busy = odata.done = 0;
685 if (!CreateThread(NULL, 0, stdout_write_thread,
686 &odata, 0, &out_threadid)) {
687 fprintf(stderr, "Unable to create output thread\n");
688 exit(1);
689 }
690 edata.event = stderrevent;
691 edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
692 edata.is_stderr = 1;
693 edata.busy = edata.done = 0;
694 if (!CreateThread(NULL, 0, stdout_write_thread,
695 &edata, 0, &err_threadid)) {
696 fprintf(stderr, "Unable to create error output thread\n");
697 exit(1);
698 }
699
12dc4ec0 700 while (1) {
32874aea 701 int n;
702
703 if (!sending && back->sendok()) {
704 /*
705 * Create a separate thread to read from stdin. This is
706 * a total pain, but I can't find another way to do it:
707 *
708 * - an overlapped ReadFile or ReadFileEx just doesn't
709 * happen; we get failure from ReadFileEx, and
710 * ReadFile blocks despite being given an OVERLAPPED
711 * structure. Perhaps we can't do overlapped reads
712 * on consoles. WHY THE HELL NOT?
713 *
714 * - WaitForMultipleObjects(netevent, console) doesn't
715 * work, because it signals the console when
716 * _anything_ happens, including mouse motions and
717 * other things that don't cause data to be readable
718 * - so we're back to ReadFile blocking.
719 */
720 idata.event = stdinevent;
721 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
722 if (!CreateThread(NULL, 0, stdin_read_thread,
5471d09a 723 &idata, 0, &in_threadid)) {
724 fprintf(stderr, "Unable to create input thread\n");
32874aea 725 exit(1);
726 }
727 sending = TRUE;
728 }
729
5471d09a 730 n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
32874aea 731 if (n == 0) {
732 WSANETWORKEVENTS things;
8df7a775 733 SOCKET socket;
d2371c81 734 extern SOCKET first_socket(int *), next_socket(int *);
8df7a775 735 extern int select_result(WPARAM, LPARAM);
32874aea 736 int i, socketstate;
737
738 /*
739 * We must not call select_result() for any socket
740 * until we have finished enumerating within the tree.
741 * This is because select_result() may close the socket
742 * and modify the tree.
743 */
744 /* Count the active sockets. */
745 i = 0;
746 for (socket = first_socket(&socketstate);
747 socket != INVALID_SOCKET;
748 socket = next_socket(&socketstate)) i++;
749
750 /* Expand the buffer if necessary. */
751 if (i > sksize) {
752 sksize = i + 16;
753 sklist = srealloc(sklist, sksize * sizeof(*sklist));
754 }
755
756 /* Retrieve the sockets into sklist. */
757 skcount = 0;
758 for (socket = first_socket(&socketstate);
759 socket != INVALID_SOCKET;
d2371c81 760 socket = next_socket(&socketstate)) {
32874aea 761 sklist[skcount++] = socket;
762 }
763
764 /* Now we're done enumerating; go through the list. */
765 for (i = 0; i < skcount; i++) {
766 WPARAM wp;
767 socket = sklist[i];
768 wp = (WPARAM) socket;
ffb959c7 769 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
32874aea 770 noise_ultralight(socket);
771 noise_ultralight(things.lNetworkEvents);
3ad9d396 772 if (things.lNetworkEvents & FD_CONNECT)
773 connopen &= select_result(wp, (LPARAM) FD_CONNECT);
8df7a775 774 if (things.lNetworkEvents & FD_READ)
32874aea 775 connopen &= select_result(wp, (LPARAM) FD_READ);
8df7a775 776 if (things.lNetworkEvents & FD_CLOSE)
32874aea 777 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
8df7a775 778 if (things.lNetworkEvents & FD_OOB)
32874aea 779 connopen &= select_result(wp, (LPARAM) FD_OOB);
8df7a775 780 if (things.lNetworkEvents & FD_WRITE)
32874aea 781 connopen &= select_result(wp, (LPARAM) FD_WRITE);
d74d141c 782 if (things.lNetworkEvents & FD_ACCEPT)
783 connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
784
8df7a775 785 }
786 }
32874aea 787 } else if (n == 1) {
5471d09a 788 reading = 0;
32874aea 789 noise_ultralight(idata.len);
790 if (idata.len > 0) {
791 back->send(idata.buffer, idata.len);
792 } else {
793 back->special(TS_EOF);
794 }
5471d09a 795 } else if (n == 2) {
796 odata.busy = 0;
797 if (!odata.writeret) {
798 fprintf(stderr, "Unable to write to standard output\n");
799 exit(0);
800 }
801 bufchain_consume(&stdout_data, odata.lenwritten);
802 if (bufchain_size(&stdout_data) > 0)
803 try_output(0);
804 back->unthrottle(bufchain_size(&stdout_data) +
805 bufchain_size(&stderr_data));
806 } else if (n == 3) {
807 edata.busy = 0;
808 if (!edata.writeret) {
809 fprintf(stderr, "Unable to write to standard output\n");
810 exit(0);
811 }
812 bufchain_consume(&stderr_data, edata.lenwritten);
813 if (bufchain_size(&stderr_data) > 0)
814 try_output(1);
815 back->unthrottle(bufchain_size(&stdout_data) +
816 bufchain_size(&stderr_data));
817 }
818 if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
32874aea 819 SetEvent(idata.eventback);
5471d09a 820 reading = 1;
32874aea 821 }
822 if (!connopen || back->socket() == NULL)
823 break; /* we closed the connection */
12dc4ec0 824 }
825 WSACleanup();
826 return 0;
827}