The other utilities should do the same processing of the hostname
[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{
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
449925a6 594 /*
595 * Trim leading whitespace off the hostname if it's there.
596 */
597 {
598 int space = strspn(cfg.host, " \t");
599 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
600 }
601
602 /* See if host is of the form user@host */
603 if (cfg.host[0] != '\0') {
604 char *atsign = strchr(cfg.host, '@');
605 /* Make sure we're not overflowing the user field */
606 if (atsign) {
607 if (atsign - cfg.host < sizeof cfg.username) {
608 strncpy(cfg.username, cfg.host, atsign - cfg.host);
609 cfg.username[atsign - cfg.host] = '\0';
610 }
611 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
612 }
613 }
614
615 /*
616 * Trim a colon suffix off the hostname if it's there.
617 */
618 cfg.host[strcspn(cfg.host, ":")] = '\0';
619
96621a84 620 if (!*cfg.remote_cmd_ptr)
32874aea 621 flags |= FLAG_INTERACTIVE;
67779be7 622
12dc4ec0 623 /*
624 * Select protocol. This is farmed out into a table in a
625 * separate file to enable an ssh-free variant.
626 */
627 {
32874aea 628 int i;
629 back = NULL;
630 for (i = 0; backends[i].backend != NULL; i++)
631 if (backends[i].protocol == cfg.protocol) {
632 back = backends[i].backend;
633 break;
634 }
635 if (back == NULL) {
636 fprintf(stderr,
637 "Internal fault: Unsupported protocol found\n");
638 return 1;
639 }
12dc4ec0 640 }
641
642 /*
8cb9c947 643 * Select port.
644 */
645 if (portnumber != -1)
32874aea 646 cfg.port = portnumber;
8cb9c947 647
648 /*
12dc4ec0 649 * Initialise WinSock.
650 */
651 winsock_ver = MAKEWORD(2, 0);
652 if (WSAStartup(winsock_ver, &wsadata)) {
653 MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
654 MB_OK | MB_ICONEXCLAMATION);
655 return 1;
656 }
657 if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
658 MessageBox(NULL, "WinSock version is incompatible with 2.0",
659 "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
660 WSACleanup();
661 return 1;
662 }
8df7a775 663 sk_init();
12dc4ec0 664
665 /*
666 * Start up the connection.
667 */
8df7a775 668 netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 669 {
670 char *error;
671 char *realhost;
672
32874aea 673 error = back->init(cfg.host, cfg.port, &realhost);
12dc4ec0 674 if (error) {
675 fprintf(stderr, "Unable to open connection:\n%s", error);
676 return 1;
677 }
6e1ebb76 678 sfree(realhost);
12dc4ec0 679 }
8df7a775 680 connopen = 1;
12dc4ec0 681
12dc4ec0 682 stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
5471d09a 683 stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
684 stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
12dc4ec0 685
0965bee0 686 inhandle = GetStdHandle(STD_INPUT_HANDLE);
12dc4ec0 687 outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
fe50e814 688 errhandle = GetStdHandle(STD_ERROR_HANDLE);
0965bee0 689 GetConsoleMode(inhandle, &orig_console_mode);
690 SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
12dc4ec0 691
692 /*
12dc4ec0 693 * Turn off ECHO and LINE input modes. We don't care if this
694 * call fails, because we know we aren't necessarily running in
695 * a console.
696 */
12dc4ec0 697 handles[0] = netevent;
698 handles[1] = stdinevent;
5471d09a 699 handles[2] = stdoutevent;
700 handles[3] = stderrevent;
12dc4ec0 701 sending = FALSE;
5471d09a 702
703 /*
704 * Create spare threads to write to stdout and stderr, so we
705 * can arrange asynchronous writes.
706 */
707 odata.event = stdoutevent;
708 odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
709 odata.is_stderr = 0;
710 odata.busy = odata.done = 0;
711 if (!CreateThread(NULL, 0, stdout_write_thread,
712 &odata, 0, &out_threadid)) {
713 fprintf(stderr, "Unable to create output thread\n");
714 exit(1);
715 }
716 edata.event = stderrevent;
717 edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
718 edata.is_stderr = 1;
719 edata.busy = edata.done = 0;
720 if (!CreateThread(NULL, 0, stdout_write_thread,
721 &edata, 0, &err_threadid)) {
722 fprintf(stderr, "Unable to create error output thread\n");
723 exit(1);
724 }
725
12dc4ec0 726 while (1) {
32874aea 727 int n;
728
729 if (!sending && back->sendok()) {
730 /*
731 * Create a separate thread to read from stdin. This is
732 * a total pain, but I can't find another way to do it:
733 *
734 * - an overlapped ReadFile or ReadFileEx just doesn't
735 * happen; we get failure from ReadFileEx, and
736 * ReadFile blocks despite being given an OVERLAPPED
737 * structure. Perhaps we can't do overlapped reads
738 * on consoles. WHY THE HELL NOT?
739 *
740 * - WaitForMultipleObjects(netevent, console) doesn't
741 * work, because it signals the console when
742 * _anything_ happens, including mouse motions and
743 * other things that don't cause data to be readable
744 * - so we're back to ReadFile blocking.
745 */
746 idata.event = stdinevent;
747 idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
748 if (!CreateThread(NULL, 0, stdin_read_thread,
5471d09a 749 &idata, 0, &in_threadid)) {
750 fprintf(stderr, "Unable to create input thread\n");
32874aea 751 exit(1);
752 }
753 sending = TRUE;
754 }
755
5471d09a 756 n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
32874aea 757 if (n == 0) {
758 WSANETWORKEVENTS things;
8df7a775 759 SOCKET socket;
d2371c81 760 extern SOCKET first_socket(int *), next_socket(int *);
8df7a775 761 extern int select_result(WPARAM, LPARAM);
32874aea 762 int i, socketstate;
763
764 /*
765 * We must not call select_result() for any socket
766 * until we have finished enumerating within the tree.
767 * This is because select_result() may close the socket
768 * and modify the tree.
769 */
770 /* Count the active sockets. */
771 i = 0;
772 for (socket = first_socket(&socketstate);
773 socket != INVALID_SOCKET;
774 socket = next_socket(&socketstate)) i++;
775
776 /* Expand the buffer if necessary. */
777 if (i > sksize) {
778 sksize = i + 16;
779 sklist = srealloc(sklist, sksize * sizeof(*sklist));
780 }
781
782 /* Retrieve the sockets into sklist. */
783 skcount = 0;
784 for (socket = first_socket(&socketstate);
785 socket != INVALID_SOCKET;
d2371c81 786 socket = next_socket(&socketstate)) {
32874aea 787 sklist[skcount++] = socket;
788 }
789
790 /* Now we're done enumerating; go through the list. */
791 for (i = 0; i < skcount; i++) {
792 WPARAM wp;
793 socket = sklist[i];
794 wp = (WPARAM) socket;
ffb959c7 795 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
32874aea 796 noise_ultralight(socket);
797 noise_ultralight(things.lNetworkEvents);
3ad9d396 798 if (things.lNetworkEvents & FD_CONNECT)
799 connopen &= select_result(wp, (LPARAM) FD_CONNECT);
8df7a775 800 if (things.lNetworkEvents & FD_READ)
32874aea 801 connopen &= select_result(wp, (LPARAM) FD_READ);
8df7a775 802 if (things.lNetworkEvents & FD_CLOSE)
32874aea 803 connopen &= select_result(wp, (LPARAM) FD_CLOSE);
8df7a775 804 if (things.lNetworkEvents & FD_OOB)
32874aea 805 connopen &= select_result(wp, (LPARAM) FD_OOB);
8df7a775 806 if (things.lNetworkEvents & FD_WRITE)
32874aea 807 connopen &= select_result(wp, (LPARAM) FD_WRITE);
d74d141c 808 if (things.lNetworkEvents & FD_ACCEPT)
809 connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
810
8df7a775 811 }
812 }
32874aea 813 } else if (n == 1) {
5471d09a 814 reading = 0;
32874aea 815 noise_ultralight(idata.len);
42856df4 816 if (connopen && back->socket() != NULL) {
817 if (idata.len > 0) {
818 back->send(idata.buffer, idata.len);
819 } else {
820 back->special(TS_EOF);
821 }
32874aea 822 }
5471d09a 823 } else if (n == 2) {
824 odata.busy = 0;
825 if (!odata.writeret) {
826 fprintf(stderr, "Unable to write to standard output\n");
827 exit(0);
828 }
829 bufchain_consume(&stdout_data, odata.lenwritten);
830 if (bufchain_size(&stdout_data) > 0)
831 try_output(0);
42856df4 832 if (connopen && back->socket() != NULL) {
833 back->unthrottle(bufchain_size(&stdout_data) +
834 bufchain_size(&stderr_data));
835 }
5471d09a 836 } else if (n == 3) {
837 edata.busy = 0;
838 if (!edata.writeret) {
839 fprintf(stderr, "Unable to write to standard output\n");
840 exit(0);
841 }
842 bufchain_consume(&stderr_data, edata.lenwritten);
843 if (bufchain_size(&stderr_data) > 0)
844 try_output(1);
42856df4 845 if (connopen && back->socket() != NULL) {
846 back->unthrottle(bufchain_size(&stdout_data) +
847 bufchain_size(&stderr_data));
848 }
5471d09a 849 }
850 if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
32874aea 851 SetEvent(idata.eventback);
5471d09a 852 reading = 1;
32874aea 853 }
42856df4 854 if ((!connopen || back->socket() == NULL) &&
855 bufchain_size(&stdout_data) == 0 &&
856 bufchain_size(&stderr_data) == 0)
32874aea 857 break; /* we closed the connection */
12dc4ec0 858 }
859 WSACleanup();
860 return 0;
861}