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