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