Extensive changes that _should_ fix the socket buffering problems,
[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 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 }