]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
The other utilities should do the same processing of the hostname
[PuTTY.git] / 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     exit(1);
350 }
351
352 char *do_select(SOCKET skt, int startup)
353 {
354     int events;
355     if (startup) {
356         events = (FD_CONNECT | FD_READ | FD_WRITE |
357                   FD_OOB | FD_CLOSE | FD_ACCEPT);
358     } else {
359         events = 0;
360     }
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         }
368     }
369     return NULL;
370 }
371
372 int main(int argc, char **argv)
373 {
374     WSADATA wsadata;
375     WORD winsock_ver;
376     WSAEVENT stdinevent, stdoutevent, stderrevent;
377     HANDLE handles[4];
378     DWORD in_threadid, out_threadid, err_threadid;
379     struct input_data idata;
380     int reading;
381     int sending;
382     int portnumber = -1;
383     SOCKET *sklist;
384     int skcount, sksize;
385     int connopen;
386
387     ssh_get_line = get_line;
388
389     sklist = NULL;
390     skcount = sksize = 0;
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;
397
398     flags = FLAG_STDERR;
399     /*
400      * Process the command line.
401      */
402     do_defaults(NULL, &cfg);
403     default_protocol = cfg.protocol;
404     default_port = cfg.port;
405     {
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         }
421     }
422     while (--argc) {
423         char *p = *++argv;
424         if (*p == '-') {
425             if (!strcmp(p, "-ssh")) {
426                 default_protocol = cfg.protocol = PROT_SSH;
427                 default_port = cfg.port = 22;
428             } else if (!strcmp(p, "-telnet")) {
429                 default_protocol = cfg.protocol = PROT_TELNET;
430                 default_port = cfg.port = 23;
431             } else if (!strcmp(p, "-raw")) {
432                 default_protocol = cfg.protocol = PROT_RAW;
433             } else if (!strcmp(p, "-v")) {
434                 flags |= FLAG_VERBOSE;
435             } else if (!strcmp(p, "-log")) {
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;
472                 cfg.remote_cmd_ptr2 = NULL;
473                 cfg.nopty = TRUE;      /* command => no terminal */
474             } else if (!strcmp(p, "-P") && argc > 1) {
475                 --argc, portnumber = atoi(*++argv);
476             }
477         } else if (*p) {
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             }
587         }
588     }
589
590     if (!*cfg.host) {
591         usage();
592     }
593
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
620     if (!*cfg.remote_cmd_ptr)
621         flags |= FLAG_INTERACTIVE;
622
623     /*
624      * Select protocol. This is farmed out into a table in a
625      * separate file to enable an ssh-free variant.
626      */
627     {
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         }
640     }
641
642     /*
643      * Select port.
644      */
645     if (portnumber != -1)
646         cfg.port = portnumber;
647
648     /*
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     }
663     sk_init();
664
665     /*
666      * Start up the connection.
667      */
668     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
669     {
670         char *error;
671         char *realhost;
672
673         error = back->init(cfg.host, cfg.port, &realhost);
674         if (error) {
675             fprintf(stderr, "Unable to open connection:\n%s", error);
676             return 1;
677         }
678         sfree(realhost);
679     }
680     connopen = 1;
681
682     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
683     stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
684     stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
685
686     inhandle = GetStdHandle(STD_INPUT_HANDLE);
687     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
688     errhandle = GetStdHandle(STD_ERROR_HANDLE);
689     GetConsoleMode(inhandle, &orig_console_mode);
690     SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
691
692     /*
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      */
697     handles[0] = netevent;
698     handles[1] = stdinevent;
699     handles[2] = stdoutevent;
700     handles[3] = stderrevent;
701     sending = FALSE;
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
726     while (1) {
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,
749                               &idata, 0, &in_threadid)) {
750                 fprintf(stderr, "Unable to create input thread\n");
751                 exit(1);
752             }
753             sending = TRUE;
754         }
755
756         n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
757         if (n == 0) {
758             WSANETWORKEVENTS things;
759             SOCKET socket;
760             extern SOCKET first_socket(int *), next_socket(int *);
761             extern int select_result(WPARAM, LPARAM);
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;
786                  socket = next_socket(&socketstate)) {
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;
795                 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
796                     noise_ultralight(socket);
797                     noise_ultralight(things.lNetworkEvents);
798                     if (things.lNetworkEvents & FD_CONNECT)
799                         connopen &= select_result(wp, (LPARAM) FD_CONNECT);
800                     if (things.lNetworkEvents & FD_READ)
801                         connopen &= select_result(wp, (LPARAM) FD_READ);
802                     if (things.lNetworkEvents & FD_CLOSE)
803                         connopen &= select_result(wp, (LPARAM) FD_CLOSE);
804                     if (things.lNetworkEvents & FD_OOB)
805                         connopen &= select_result(wp, (LPARAM) FD_OOB);
806                     if (things.lNetworkEvents & FD_WRITE)
807                         connopen &= select_result(wp, (LPARAM) FD_WRITE);
808                     if (things.lNetworkEvents & FD_ACCEPT)
809                         connopen &= select_result(wp, (LPARAM) FD_ACCEPT);
810
811                 }
812             }
813         } else if (n == 1) {
814             reading = 0;
815             noise_ultralight(idata.len);
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                 }
822             }
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);
832             if (connopen && back->socket() != NULL) {
833                 back->unthrottle(bufchain_size(&stdout_data) +
834                                  bufchain_size(&stderr_data));
835             }
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);
845             if (connopen && back->socket() != NULL) {
846                 back->unthrottle(bufchain_size(&stdout_data) +
847                                  bufchain_size(&stderr_data));
848             }
849         }
850         if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
851             SetEvent(idata.eventback);
852             reading = 1;
853         }
854         if ((!connopen || back->socket() == NULL) &&
855             bufchain_size(&stdout_data) == 0 &&
856             bufchain_size(&stderr_data) == 0)
857             break;                     /* we closed the connection */
858     }
859     WSACleanup();
860     return 0;
861 }