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