]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - plink.c
Patch to enable >512-character command lines. Thanks to Thomas Halling.
[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                 char *command;
426                 int cmdlen, cmdsize;
427                 cmdlen = cmdsize = 0;
428                 command = NULL;
429
430                 while (argc) {
431                     while (*p) {
432                         if (cmdlen >= cmdsize) {
433                             cmdsize = cmdlen + 512;
434                             command = srealloc(command, cmdsize);
435                         }
436                         command[cmdlen++]=*p++;
437                     }
438                     if (cmdlen >= cmdsize) {
439                         cmdsize = cmdlen + 512;
440                         command = srealloc(command, cmdsize);
441                     }
442                     command[cmdlen++]=' '; /* always add trailing space */
443                     if (--argc) p = *++argv;
444                 }
445                 if (cmdlen) command[--cmdlen]='\0';
446                                        /* change trailing blank to NUL */
447                 cfg.remote_cmd_ptr = command;
448                 cfg.remote_cmd_ptr2 = NULL;
449                 cfg.nopty = TRUE;      /* command => no terminal */
450
451                 break;                 /* done with cmdline */
452             }
453         }
454     }
455
456     if (!*cfg.host) {
457         usage();
458     }
459
460     /*
461      * Trim leading whitespace off the hostname if it's there.
462      */
463     {
464         int space = strspn(cfg.host, " \t");
465         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
466     }
467
468     /* See if host is of the form user@host */
469     if (cfg.host[0] != '\0') {
470         char *atsign = strchr(cfg.host, '@');
471         /* Make sure we're not overflowing the user field */
472         if (atsign) {
473             if (atsign - cfg.host < sizeof cfg.username) {
474                 strncpy(cfg.username, cfg.host, atsign - cfg.host);
475                 cfg.username[atsign - cfg.host] = '\0';
476             }
477             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
478         }
479     }
480
481     /*
482      * Trim a colon suffix off the hostname if it's there.
483      */
484     cfg.host[strcspn(cfg.host, ":")] = '\0';
485
486     if (!*cfg.remote_cmd_ptr)
487         flags |= FLAG_INTERACTIVE;
488
489     /*
490      * Select protocol. This is farmed out into a table in a
491      * separate file to enable an ssh-free variant.
492      */
493     {
494         int i;
495         back = NULL;
496         for (i = 0; backends[i].backend != NULL; i++)
497             if (backends[i].protocol == cfg.protocol) {
498                 back = backends[i].backend;
499                 break;
500             }
501         if (back == NULL) {
502             fprintf(stderr,
503                     "Internal fault: Unsupported protocol found\n");
504             return 1;
505         }
506     }
507
508     /*
509      * Add extra port forwardings (accumulated on command line) to
510      * cfg.
511      */
512     {
513         int i;
514         char *p;
515         p = extra_portfwd;
516         i = 0;
517         while (cfg.portfwd[i])
518             i += strlen(cfg.portfwd+i) + 1;
519         while (*p) {
520             if (strlen(p)+2 > sizeof(cfg.portfwd)-i) {
521                 fprintf(stderr, "Internal fault: not enough space for all"
522                         " port forwardings\n");
523                 break;
524             }
525             strncpy(cfg.portfwd+i, p, sizeof(cfg.portfwd)-i-1);
526             i += strlen(cfg.portfwd+i) + 1;
527             cfg.portfwd[i] = '\0';
528             p += strlen(p)+1;
529         }
530     }
531
532     /*
533      * Select port.
534      */
535     if (portnumber != -1)
536         cfg.port = portnumber;
537
538     /*
539      * Initialise WinSock.
540      */
541     winsock_ver = MAKEWORD(2, 0);
542     if (WSAStartup(winsock_ver, &wsadata)) {
543         MessageBox(NULL, "Unable to initialise WinSock", "WinSock Error",
544                    MB_OK | MB_ICONEXCLAMATION);
545         return 1;
546     }
547     if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 0) {
548         MessageBox(NULL, "WinSock version is incompatible with 2.0",
549                    "WinSock Error", MB_OK | MB_ICONEXCLAMATION);
550         WSACleanup();
551         return 1;
552     }
553     sk_init();
554
555     /*
556      * Start up the connection.
557      */
558     netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
559     {
560         char *error;
561         char *realhost;
562         /* nodelay is only useful if stdin is a character device (console) */
563         int nodelay = cfg.tcp_nodelay &&
564             (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
565
566         error = back->init(cfg.host, cfg.port, &realhost, nodelay);
567         if (error) {
568             fprintf(stderr, "Unable to open connection:\n%s", error);
569             return 1;
570         }
571         sfree(realhost);
572     }
573     connopen = 1;
574
575     stdinevent = CreateEvent(NULL, FALSE, FALSE, NULL);
576     stdoutevent = CreateEvent(NULL, FALSE, FALSE, NULL);
577     stderrevent = CreateEvent(NULL, FALSE, FALSE, NULL);
578
579     inhandle = GetStdHandle(STD_INPUT_HANDLE);
580     outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
581     errhandle = GetStdHandle(STD_ERROR_HANDLE);
582     GetConsoleMode(inhandle, &orig_console_mode);
583     SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
584
585     /*
586      * Turn off ECHO and LINE input modes. We don't care if this
587      * call fails, because we know we aren't necessarily running in
588      * a console.
589      */
590     handles[0] = netevent;
591     handles[1] = stdinevent;
592     handles[2] = stdoutevent;
593     handles[3] = stderrevent;
594     sending = FALSE;
595
596     /*
597      * Create spare threads to write to stdout and stderr, so we
598      * can arrange asynchronous writes.
599      */
600     odata.event = stdoutevent;
601     odata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
602     odata.is_stderr = 0;
603     odata.busy = odata.done = 0;
604     if (!CreateThread(NULL, 0, stdout_write_thread,
605                       &odata, 0, &out_threadid)) {
606         fprintf(stderr, "Unable to create output thread\n");
607         exit(1);
608     }
609     edata.event = stderrevent;
610     edata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
611     edata.is_stderr = 1;
612     edata.busy = edata.done = 0;
613     if (!CreateThread(NULL, 0, stdout_write_thread,
614                       &edata, 0, &err_threadid)) {
615         fprintf(stderr, "Unable to create error output thread\n");
616         exit(1);
617     }
618
619     while (1) {
620         int n;
621
622         if (!sending && back->sendok()) {
623             /*
624              * Create a separate thread to read from stdin. This is
625              * a total pain, but I can't find another way to do it:
626              *
627              *  - an overlapped ReadFile or ReadFileEx just doesn't
628              *    happen; we get failure from ReadFileEx, and
629              *    ReadFile blocks despite being given an OVERLAPPED
630              *    structure. Perhaps we can't do overlapped reads
631              *    on consoles. WHY THE HELL NOT?
632              * 
633              *  - WaitForMultipleObjects(netevent, console) doesn't
634              *    work, because it signals the console when
635              *    _anything_ happens, including mouse motions and
636              *    other things that don't cause data to be readable
637              *    - so we're back to ReadFile blocking.
638              */
639             idata.event = stdinevent;
640             idata.eventback = CreateEvent(NULL, FALSE, FALSE, NULL);
641             if (!CreateThread(NULL, 0, stdin_read_thread,
642                               &idata, 0, &in_threadid)) {
643                 fprintf(stderr, "Unable to create input thread\n");
644                 exit(1);
645             }
646             sending = TRUE;
647         }
648
649         n = WaitForMultipleObjects(4, handles, FALSE, INFINITE);
650         if (n == 0) {
651             WSANETWORKEVENTS things;
652             SOCKET socket;
653             extern SOCKET first_socket(int *), next_socket(int *);
654             extern int select_result(WPARAM, LPARAM);
655             int i, socketstate;
656
657             /*
658              * We must not call select_result() for any socket
659              * until we have finished enumerating within the tree.
660              * This is because select_result() may close the socket
661              * and modify the tree.
662              */
663             /* Count the active sockets. */
664             i = 0;
665             for (socket = first_socket(&socketstate);
666                  socket != INVALID_SOCKET;
667                  socket = next_socket(&socketstate)) i++;
668
669             /* Expand the buffer if necessary. */
670             if (i > sksize) {
671                 sksize = i + 16;
672                 sklist = srealloc(sklist, sksize * sizeof(*sklist));
673             }
674
675             /* Retrieve the sockets into sklist. */
676             skcount = 0;
677             for (socket = first_socket(&socketstate);
678                  socket != INVALID_SOCKET;
679                  socket = next_socket(&socketstate)) {
680                 sklist[skcount++] = socket;
681             }
682
683             /* Now we're done enumerating; go through the list. */
684             for (i = 0; i < skcount; i++) {
685                 WPARAM wp;
686                 socket = sklist[i];
687                 wp = (WPARAM) socket;
688                 if (!WSAEnumNetworkEvents(socket, NULL, &things)) {
689                     static const struct { int bit, mask; } eventtypes[] = {
690                         {FD_CONNECT_BIT, FD_CONNECT},
691                         {FD_READ_BIT, FD_READ},
692                         {FD_CLOSE_BIT, FD_CLOSE},
693                         {FD_OOB_BIT, FD_OOB},
694                         {FD_WRITE_BIT, FD_WRITE},
695                         {FD_ACCEPT_BIT, FD_ACCEPT},
696                     };
697                     int e;
698
699                     noise_ultralight(socket);
700                     noise_ultralight(things.lNetworkEvents);
701
702                     for (e = 0; e < lenof(eventtypes); e++)
703                         if (things.lNetworkEvents & eventtypes[e].mask) {
704                             LPARAM lp;
705                             int err = things.iErrorCode[eventtypes[e].bit];
706                             lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
707                             connopen &= select_result(wp, lp);
708                         }
709                 }
710             }
711         } else if (n == 1) {
712             reading = 0;
713             noise_ultralight(idata.len);
714             if (connopen && back->socket() != NULL) {
715                 if (idata.len > 0) {
716                     back->send(idata.buffer, idata.len);
717                 } else {
718                     back->special(TS_EOF);
719                 }
720             }
721         } else if (n == 2) {
722             odata.busy = 0;
723             if (!odata.writeret) {
724                 fprintf(stderr, "Unable to write to standard output\n");
725                 exit(0);
726             }
727             bufchain_consume(&stdout_data, odata.lenwritten);
728             if (bufchain_size(&stdout_data) > 0)
729                 try_output(0);
730             if (connopen && back->socket() != NULL) {
731                 back->unthrottle(bufchain_size(&stdout_data) +
732                                  bufchain_size(&stderr_data));
733             }
734         } else if (n == 3) {
735             edata.busy = 0;
736             if (!edata.writeret) {
737                 fprintf(stderr, "Unable to write to standard output\n");
738                 exit(0);
739             }
740             bufchain_consume(&stderr_data, edata.lenwritten);
741             if (bufchain_size(&stderr_data) > 0)
742                 try_output(1);
743             if (connopen && back->socket() != NULL) {
744                 back->unthrottle(bufchain_size(&stdout_data) +
745                                  bufchain_size(&stderr_data));
746             }
747         }
748         if (!reading && back->sendbuffer() < MAX_STDIN_BACKLOG) {
749             SetEvent(idata.eventback);
750             reading = 1;
751         }
752         if ((!connopen || back->socket() == NULL) &&
753             bufchain_size(&stdout_data) == 0 &&
754             bufchain_size(&stderr_data) == 0)
755             break;                     /* we closed the connection */
756     }
757     WSACleanup();
758     exitcode = back->exitcode();
759     if (exitcode < 0) {
760         fprintf(stderr, "Remote process exit code unavailable\n");
761         exitcode = 1;                  /* this is an error condition */
762     }
763     return exitcode;
764 }