PSCP now uses the modern SFTP protocol if it can, and falls back to
[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>
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{
317 int pos;
318 DWORD ret;
319 HANDLE h = (is_stderr ? errhandle : outhandle);
320 void *writedata;
321 int writelen;
322 int osize, esize;
323
324 if (is_stderr) {
325 bufchain_add(&stderr_data, data, len);
326 try_output(1);
327 } else {
328 bufchain_add(&stdout_data, data, len);
329 try_output(0);
330 }
331
332 osize = bufchain_size(&stdout_data);
333 esize = bufchain_size(&stderr_data);
334
335 return osize + esize;
336}
337
d8426c54 338/*
339 * Short description of parameters.
340 */
341static void usage(void)
342{
343 printf("PuTTY Link: command-line connection utility\n");
344 printf("%s\n", ver);
345 printf("Usage: plink [options] [user@]host [command]\n");
346 printf("Options:\n");
347 printf(" -v show verbose messages\n");
348 printf(" -ssh force use of ssh protocol\n");
349 printf(" -P port connect to specified port\n");
350 printf(" -pw passw login with specified password\n");
96621a84 351 printf(" -m file read remote command(s) from file\n");
d8426c54 352 exit(1);
353}
354
32874aea 355char *do_select(SOCKET skt, int startup)
356{
8df7a775 357 int events;
358 if (startup) {
d74d141c 359 events = FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT;
8df7a775 360 } else {
361 events = 0;
362 }
32874aea 363 if (WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
364 switch (WSAGetLastError()) {
365 case WSAENETDOWN:
366 return "Network is down";
367 default:
368 return "WSAAsyncSelect(): unknown error";
369 }
8df7a775 370 }
371 return NULL;
372}
373
32874aea 374int main(int argc, char **argv)
375{
12dc4ec0 376 WSADATA wsadata;
377 WORD winsock_ver;
5471d09a 378 WSAEVENT stdinevent, stdoutevent, stderrevent;
379 HANDLE handles[4];
380 DWORD in_threadid, out_threadid, err_threadid;
12dc4ec0 381 struct input_data idata;
5471d09a 382 int reading;
12dc4ec0 383 int sending;
d8426c54 384 int portnumber = -1;
8df7a775 385 SOCKET *sklist;
386 int skcount, sksize;
387 int connopen;
12dc4ec0 388
fa17a66e 389 ssh_get_line = get_line;
67779be7 390
32874aea 391 sklist = NULL;
392 skcount = sksize = 0;
c9bdcd96 393 /*
394 * Initialise port and protocol to sensible defaults. (These
395 * will be overridden by more or less anything.)
396 */
397 default_protocol = PROT_SSH;
398 default_port = 22;
8df7a775 399
67779be7 400 flags = FLAG_STDERR;
12dc4ec0 401 /*
402 * Process the command line.
403 */
a9422f39 404 do_defaults(NULL, &cfg);
e7a7383f 405 default_protocol = cfg.protocol;
406 default_port = cfg.port;
8cb9c947 407 {
32874aea 408 /*
409 * Override the default protocol if PLINK_PROTOCOL is set.
410 */
411 char *p = getenv("PLINK_PROTOCOL");
412 int i;
413 if (p) {
414 for (i = 0; backends[i].backend != NULL; i++) {
415 if (!strcmp(backends[i].name, p)) {
416 default_protocol = cfg.protocol = backends[i].protocol;
417 default_port = cfg.port =
418 backends[i].backend->default_port;
419 break;
420 }
421 }
422 }
8cb9c947 423 }
12dc4ec0 424 while (--argc) {
32874aea 425 char *p = *++argv;
426 if (*p == '-') {
427 if (!strcmp(p, "-ssh")) {
12dc4ec0 428 default_protocol = cfg.protocol = PROT_SSH;
429 default_port = cfg.port = 22;
32874aea 430 } else if (!strcmp(p, "-telnet")) {
9d33ebdd 431 default_protocol = cfg.protocol = PROT_TELNET;
432 default_port = cfg.port = 23;
32874aea 433 } else if (!strcmp(p, "-raw")) {
9d33ebdd 434 default_protocol = cfg.protocol = PROT_RAW;
67779be7 435 } else if (!strcmp(p, "-v")) {
32874aea 436 flags |= FLAG_VERBOSE;
12dc4ec0 437 } else if (!strcmp(p, "-log")) {
32874aea 438 logfile = "putty.log";
439 } else if (!strcmp(p, "-pw") && argc > 1) {
440 --argc, password = *++argv;
441 } else if (!strcmp(p, "-l") && argc > 1) {
442 char *username;
443 --argc, username = *++argv;
444 strncpy(cfg.username, username, sizeof(cfg.username));
445 cfg.username[sizeof(cfg.username) - 1] = '\0';
446 } else if (!strcmp(p, "-m") && argc > 1) {
447 char *filename, *command;
448 int cmdlen, cmdsize;
449 FILE *fp;
450 int c, d;
451
452 --argc, filename = *++argv;
453
454 cmdlen = cmdsize = 0;
455 command = NULL;
456 fp = fopen(filename, "r");
457 if (!fp) {
458 fprintf(stderr, "plink: unable to open command "
459 "file \"%s\"\n", filename);
460 return 1;
461 }
462 do {
463 c = fgetc(fp);
464 d = c;
465 if (c == EOF)
466 d = 0;
467 if (cmdlen >= cmdsize) {
468 cmdsize = cmdlen + 512;
469 command = srealloc(command, cmdsize);
470 }
471 command[cmdlen++] = d;
472 } while (c != EOF);
473 cfg.remote_cmd_ptr = command;
fd5e5847 474 cfg.remote_cmd_ptr2 = NULL;
32874aea 475 cfg.nopty = TRUE; /* command => no terminal */
476 } else if (!strcmp(p, "-P") && argc > 1) {
477 --argc, portnumber = atoi(*++argv);
478 }
12dc4ec0 479 } else if (*p) {
32874aea 480 if (!*cfg.host) {
481 char *q = p;
482 /*
483 * If the hostname starts with "telnet:", set the
484 * protocol to Telnet and process the string as a
485 * Telnet URL.
486 */
487 if (!strncmp(q, "telnet:", 7)) {
488 char c;
489
490 q += 7;
491 if (q[0] == '/' && q[1] == '/')
492 q += 2;
493 cfg.protocol = PROT_TELNET;
494 p = q;
495 while (*p && *p != ':' && *p != '/')
496 p++;
497 c = *p;
498 if (*p)
499 *p++ = '\0';
500 if (c == ':')
501 cfg.port = atoi(p);
502 else
503 cfg.port = -1;
504 strncpy(cfg.host, q, sizeof(cfg.host) - 1);
505 cfg.host[sizeof(cfg.host) - 1] = '\0';
506 } else {
507 char *r;
508 /*
509 * Before we process the [user@]host string, we
510 * first check for the presence of a protocol
511 * prefix (a protocol name followed by ",").
512 */
513 r = strchr(p, ',');
514 if (r) {
515 int i, j;
516 for (i = 0; backends[i].backend != NULL; i++) {
517 j = strlen(backends[i].name);
518 if (j == r - p &&
519 !memcmp(backends[i].name, p, j)) {
520 default_protocol = cfg.protocol =
521 backends[i].protocol;
522 portnumber =
523 backends[i].backend->default_port;
524 p = r + 1;
525 break;
526 }
527 }
528 }
529
530 /*
531 * Three cases. Either (a) there's a nonzero
532 * length string followed by an @, in which
533 * case that's user and the remainder is host.
534 * Or (b) there's only one string, not counting
535 * a potential initial @, and it exists in the
536 * saved-sessions database. Or (c) only one
537 * string and it _doesn't_ exist in the
538 * database.
539 */
540 r = strrchr(p, '@');
541 if (r == p)
542 p++, r = NULL; /* discount initial @ */
543 if (r == NULL) {
544 /*
545 * One string.
546 */
547 Config cfg2;
548 do_defaults(p, &cfg2);
549 if (cfg2.host[0] == '\0') {
550 /* No settings for this host; use defaults */
551 strncpy(cfg.host, p, sizeof(cfg.host) - 1);
552 cfg.host[sizeof(cfg.host) - 1] = '\0';
553 cfg.port = default_port;
554 } else {
555 cfg = cfg2;
556 cfg.remote_cmd_ptr = cfg.remote_cmd;
557 }
558 } else {
559 *r++ = '\0';
560 strncpy(cfg.username, p, sizeof(cfg.username) - 1);
561 cfg.username[sizeof(cfg.username) - 1] = '\0';
562 strncpy(cfg.host, r, sizeof(cfg.host) - 1);
563 cfg.host[sizeof(cfg.host) - 1] = '\0';
564 cfg.port = default_port;
565 }
566 }
567 } else {
568 int len = sizeof(cfg.remote_cmd) - 1;
569 char *cp = cfg.remote_cmd;
570 int len2;
571
572 strncpy(cp, p, len);
573 cp[len] = '\0';
574 len2 = strlen(cp);
575 len -= len2;
576 cp += len2;
577 while (--argc) {
578 if (len > 0)
579 len--, *cp++ = ' ';
580 strncpy(cp, *++argv, len);
581 cp[len] = '\0';
582 len2 = strlen(cp);
583 len -= len2;
584 cp += len2;
585 }
586 cfg.nopty = TRUE; /* command => no terminal */
587 break; /* done with cmdline */
588 }
12dc4ec0 589 }
590 }
591
d8426c54 592 if (!*cfg.host) {
32874aea 593 usage();
d8426c54 594 }
d8426c54 595
96621a84 596 if (!*cfg.remote_cmd_ptr)
32874aea 597 flags |= FLAG_INTERACTIVE;
67779be7 598
12dc4ec0 599 /*
600 * Select protocol. This is farmed out into a table in a
601 * separate file to enable an ssh-free variant.
602 */
603 {
32874aea 604 int i;
605 back = NULL;
606 for (i = 0; backends[i].backend != NULL; i++)
607 if (backends[i].protocol == cfg.protocol) {
608 back = backends[i].backend;
609 break;
610 }
611 if (back == NULL) {
612 fprintf(stderr,
613 "Internal fault: Unsupported protocol found\n");
614 return 1;
615 }
12dc4ec0 616 }
617
618 /*
8cb9c947 619 * Select port.
620 */
621 if (portnumber != -1)
32874aea 622 cfg.port = portnumber;
8cb9c947 623
624 /*
12dc4ec0 625 * Initialise WinSock.
626 */
627 winsock_ver = MAKEWORD(2, 0);
628 if (WSAStartup(winsock_ver, &wsadata)) {
629 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
630 MB_OK | MB_ICONEXCLAMATION);
631 return 1;
632 }
633 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
634 MessageBox(NULL, "WinSock version is incompatible with 2.0",
635 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
636 WSACleanup();
637 return 1;
638 }
8df7a775 639 sk_init();
12dc4ec0 640
641 /*
642 * Start up the connection.
643 */
8df7a775 644 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 645 {
646 char *error;
647 char *realhost;
648
32874aea 649 error = back->init(cfg.host, cfg.port, &realhost);
12dc4ec0 650 if (error) {
651 fprintf(stderr, "Unable to open connection:\n%s", error);
652 return 1;
653 }
6e1ebb76 654 sfree(realhost);
12dc4ec0 655 }
8df7a775 656 connopen = 1;
12dc4ec0 657
12dc4ec0 658 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
5471d09a 659 stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
660 stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 661
0965bee0 662 inhandle = GetStdHandle(STD_INPUT_HANDLE);
12dc4ec0 663 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
fe50e814 664 errhandle = GetStdHandle(STD_ERROR_HANDLE);
0965bee0 665 GetConsoleMode(inhandle, &orig_console_mode);
666 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
12dc4ec0 667
668 /*
12dc4ec0 669 * Turn off ECHO and LINE input modes. We don't care if this
670 * call fails, because we know we aren't necessarily running in
671 * a console.
672 */
12dc4ec0 673 handles[0] = netevent;
674 handles[1] = stdinevent;
5471d09a 675 handles[2] = stdoutevent;
676 handles[3] = stderrevent;
12dc4ec0 677 sending = FALSE;
5471d09a 678
679 /*
680 * Create spare threads to write to stdout and stderr, so we
681 * can arrange asynchronous writes.
682 */
683 odata.event = stdoutevent;
684 odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
685 odata.is_stderr = 0;
686 odata.busy = odata.done = 0;
687 if (!CreateThread(NULL, 0, stdout_write_thread,
688 &odata, 0, &out_threadid)) {
689 fprintf(stderr, "Unable to create output thread\n");
690 exit(1);
691 }
692 edata.event = stderrevent;
693 edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
694 edata.is_stderr = 1;
695 edata.busy = edata.done = 0;
696 if (!CreateThread(NULL, 0, stdout_write_thread,
697 &edata, 0, &err_threadid)) {
698 fprintf(stderr, "Unable to create error output thread\n");
699 exit(1);
700 }
701
12dc4ec0 702 while (1) {
32874aea 703 int n;
704
705 if (!sending && back->sendok()) {
706 /*
707 * Create a separate thread to read from stdin. This is
708 * a total pain, but I can't find another way to do it:
709 *
710 * - an overlapped ReadFile or ReadFileEx just doesn't
711 * happen; we get failure from ReadFileEx, and
712 * ReadFile blocks despite being given an OVERLAPPED
713 * structure. Perhaps we can't do overlapped reads
714 * on consoles. WHY THE HELL NOT?
715 *
716 * - WaitForMultipleObjects(netevent, console) doesn't
717 * work, because it signals the console when
718 * _anything_ happens, including mouse motions and
719 * other things that don't cause data to be readable
720 * - so we're back to ReadFile blocking.
721 */
722 idata.event = stdinevent;
723 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
724 if (!CreateThread(NULL, 0, stdin_read_thread,
5471d09a 725 &idata, 0, &in_threadid)) {
726 fprintf(stderr, "Unable to create input thread\n");
32874aea 727 exit(1);
728 }
729 sending = TRUE;
730 }
731
5471d09a 732 n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
32874aea 733 if (n == 0) {
734 WSANETWORKEVENTS things;
8df7a775 735 SOCKET socket;
d2371c81 736 extern SOCKET first_socket(int *), next_socket(int *);
8df7a775 737 extern int select_result(WPARAM, LPARAM);
32874aea 738 int i, socketstate;
739
740 /*
741 * We must not call select_result() for any socket
742 * until we have finished enumerating within the tree.
743 * This is because select_result() may close the socket
744 * and modify the tree.
745 */
746 /* Count the active sockets. */
747 i = 0;
748 for (socket = first_socket(&socketstate);
749 socket != INVALID_SOCKET;
750 socket = next_socket(&socketstate)) i++;
751
752 /* Expand the buffer if necessary. */
753 if (i > sksize) {
754 sksize = i + 16;
755 sklist = srealloc(sklist, sksize * sizeof(*sklist));
756 }
757
758 /* Retrieve the sockets into sklist. */
759 skcount = 0;
760 for (socket = first_socket(&socketstate);
761 socket != INVALID_SOCKET;
d2371c81 762 socket = next_socket(&socketstate)) {
32874aea 763 sklist[skcount++] = socket;
764 }
765
766 /* Now we're done enumerating; go through the list. */
767 for (i = 0; i < skcount; i++) {
768 WPARAM wp;
769 socket = sklist[i];
770 wp = (WPARAM) socket;
ffb959c7 771 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
32874aea 772 noise_ultralight(socket);
773 noise_ultralight(things.lNetworkEvents);
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}