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