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