8d4701a9217708b76f33588c1d305969d9a2bd9d
2 * PLink - a command-line (stdin/stdout) variant of PuTTY.
13 #define PUTTY_DO_GLOBALS /* actually _define_ globals */
18 #define MAX_STDIN_BACKLOG 4096
20 void fatalbox(char *p
, ...)
23 fprintf(stderr
, "FATAL ERROR: ");
25 vfprintf(stderr
, p
, ap
);
31 void connection_fatal(char *p
, ...)
34 fprintf(stderr
, "FATAL ERROR: ");
36 vfprintf(stderr
, p
, ap
);
43 static char *password
= NULL
;
45 void logevent(char *string
)
49 void verify_ssh_host_key(char *host
, int port
, char *keytype
,
50 char *keystr
, char *fingerprint
)
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"
60 "The server's key fingerprint is:\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"
68 "Store key in cache? (y/n) ";
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"
77 "The new key fingerprint is:\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"
86 "Update cached key? (y/n, Return cancels connection) ";
88 static const char abandoned
[] = "Connection abandoned.\n";
93 * Verify the key against the registry.
95 ret
= verify_host_key(host
, port
, keytype
, keystr
);
97 if (ret
== 0) /* success - key matched OK */
100 if (ret
== 2) { /* key was different */
101 fprintf(stderr
, wrongmsg
, fingerprint
);
104 if (ret
== 1) { /* key was absent */
105 fprintf(stderr
, absentmsg
, fingerprint
);
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
);
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
);
120 fprintf(stderr
, abandoned
);
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
130 void askcipher(char *ciphername
, int cs
)
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";
145 (cs
== 1) ?
"client-to-server " :
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
);
157 if (line
[0] == 'y' || line
[0] == 'Y') {
160 fprintf(stderr
, abandoned
);
166 * Warn about the obsolescent key file format.
168 void old_keyfile_warning(void)
170 static const char message
[] =
171 "You are loading an SSH 2 private key which has an\n"
172 "old version of the file format. This means your key\n"
173 "file is not fully tamperproof. Future versions of\n"
174 "PuTTY may stop supporting this private key format,\n"
175 "so we recommend you convert your key to the new\n"
178 "Once the key is loaded into PuTTYgen, you can perform\n"
179 "this conversion simply by saving it again.\n";
181 fputs(message
, stderr
);
184 HANDLE inhandle
, outhandle
, errhandle
;
185 DWORD orig_console_mode
;
189 int term_ldisc(int mode
)
193 void ldisc_update(int echo
, int edit
)
195 /* Update stdin read mode to reflect changes in line discipline. */
198 mode
= ENABLE_PROCESSED_INPUT
;
200 mode
= mode
| ENABLE_ECHO_INPUT
;
202 mode
= mode
& ~ENABLE_ECHO_INPUT
;
204 mode
= mode
| ENABLE_LINE_INPUT
;
206 mode
= mode
& ~ENABLE_LINE_INPUT
;
207 SetConsoleMode(inhandle
, mode
);
210 static int get_line(const char *prompt
, char *str
, int maxlen
, int is_pw
)
213 DWORD savemode
, newmode
, i
;
215 if (is_pw
&& password
) {
216 static int tried_once
= 0;
221 strncpy(str
, password
, maxlen
);
222 str
[maxlen
- 1] = '\0';
228 hin
= GetStdHandle(STD_INPUT_HANDLE
);
229 hout
= GetStdHandle(STD_OUTPUT_HANDLE
);
230 if (hin
== INVALID_HANDLE_VALUE
|| hout
== INVALID_HANDLE_VALUE
) {
231 fprintf(stderr
, "Cannot get standard input/output handles");
235 GetConsoleMode(hin
, &savemode
);
236 newmode
= savemode
| ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
;
238 newmode
&= ~ENABLE_ECHO_INPUT
;
240 newmode
|= ENABLE_ECHO_INPUT
;
241 SetConsoleMode(hin
, newmode
);
243 WriteFile(hout
, prompt
, strlen(prompt
), &i
, NULL
);
244 ReadFile(hin
, str
, maxlen
- 1, &i
, NULL
);
246 SetConsoleMode(hin
, savemode
);
248 if ((int) i
> maxlen
)
255 WriteFile(hout
, "\r\n", 2, &i
, NULL
);
263 HANDLE event
, eventback
;
266 static DWORD WINAPI
stdin_read_thread(void *param
)
268 struct input_data
*idata
= (struct input_data
*) param
;
271 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
273 while (ReadFile(inhandle
, idata
->buffer
, sizeof(idata
->buffer
),
274 &idata
->len
, NULL
) && idata
->len
> 0) {
275 SetEvent(idata
->event
);
276 WaitForSingleObject(idata
->eventback
, INFINITE
);
280 SetEvent(idata
->event
);
286 DWORD len
, lenwritten
;
290 HANDLE event
, eventback
;
294 static DWORD WINAPI
stdout_write_thread(void *param
)
296 struct output_data
*odata
= (struct output_data
*) param
;
297 HANDLE outhandle
, errhandle
;
299 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
300 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
303 WaitForSingleObject(odata
->eventback
, INFINITE
);
307 WriteFile(odata
->is_stderr ? errhandle
: outhandle
,
308 odata
->buffer
, odata
->len
, &odata
->lenwritten
, NULL
);
309 SetEvent(odata
->event
);
315 bufchain stdout_data
, stderr_data
;
316 struct output_data odata
, edata
;
318 void try_output(int is_stderr
)
320 struct output_data
*data
= (is_stderr ?
&edata
: &odata
);
325 bufchain_prefix(is_stderr ?
&stderr_data
: &stdout_data
,
326 &senddata
, &sendlen
);
327 data
->buffer
= senddata
;
329 SetEvent(data
->eventback
);
334 int from_backend(int is_stderr
, char *data
, int len
)
336 HANDLE h
= (is_stderr ? errhandle
: outhandle
);
340 bufchain_add(&stderr_data
, data
, len
);
343 bufchain_add(&stdout_data
, data
, len
);
347 osize
= bufchain_size(&stdout_data
);
348 esize
= bufchain_size(&stderr_data
);
350 return osize
+ esize
;
354 * Short description of parameters.
356 static void usage(void)
358 printf("PuTTY Link: command-line connection utility\n");
360 printf("Usage: plink [options] [user@]host [command]\n");
361 printf(" (\"host\" can also be a PuTTY saved session name)\n");
362 printf("Options:\n");
363 printf(" -v show verbose messages\n");
364 printf(" -ssh force use of ssh protocol\n");
365 printf(" -P port connect to specified port\n");
366 printf(" -pw passw login with specified password\n");
367 printf(" -m file read remote command(s) from file\n");
368 printf(" -L listen-port:host:port Forward local port to "
370 printf(" -R listen-port:host:port Forward remote port to"
375 char *do_select(SOCKET skt
, int startup
)
379 events
= (FD_CONNECT
| FD_READ
| FD_WRITE
|
380 FD_OOB
| FD_CLOSE
| FD_ACCEPT
);
384 if (WSAEventSelect(skt
, netevent
, events
) == SOCKET_ERROR
) {
385 switch (WSAGetLastError()) {
387 return "Network is down";
389 return "WSAAsyncSelect(): unknown error";
395 int main(int argc
, char **argv
)
399 WSAEVENT stdinevent
, stdoutevent
, stderrevent
;
401 DWORD in_threadid
, out_threadid
, err_threadid
;
402 struct input_data idata
;
409 char extra_portfwd
[sizeof(cfg
.portfwd
)];
411 ssh_get_line
= get_line
;
414 skcount
= sksize
= 0;
416 * Initialise port and protocol to sensible defaults. (These
417 * will be overridden by more or less anything.)
419 default_protocol
= PROT_SSH
;
424 * Process the command line.
426 do_defaults(NULL
, &cfg
);
427 default_protocol
= cfg
.protocol
;
428 default_port
= cfg
.port
;
431 * Override the default protocol if PLINK_PROTOCOL is set.
433 char *p
= getenv("PLINK_PROTOCOL");
436 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
437 if (!strcmp(backends
[i
].name
, p
)) {
438 default_protocol
= cfg
.protocol
= backends
[i
].protocol
;
439 default_port
= cfg
.port
=
440 backends
[i
].backend
->default_port
;
449 if (!strcmp(p
, "-ssh")) {
450 default_protocol
= cfg
.protocol
= PROT_SSH
;
451 default_port
= cfg
.port
= 22;
452 } else if (!strcmp(p
, "-telnet")) {
453 default_protocol
= cfg
.protocol
= PROT_TELNET
;
454 default_port
= cfg
.port
= 23;
455 } else if (!strcmp(p
, "-raw")) {
456 default_protocol
= cfg
.protocol
= PROT_RAW
;
457 } else if (!strcmp(p
, "-v")) {
458 flags
|= FLAG_VERBOSE
;
459 } else if (!strcmp(p
, "-log")) {
460 logfile
= "putty.log";
461 } else if (!strcmp(p
, "-pw") && argc
> 1) {
462 --argc
, password
= *++argv
;
463 } else if (!strcmp(p
, "-l") && argc
> 1) {
465 --argc
, username
= *++argv
;
466 strncpy(cfg
.username
, username
, sizeof(cfg
.username
));
467 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
468 } else if ((!strcmp(p
, "-L") || !strcmp(p
, "-R")) && argc
> 1) {
471 --argc
, fwd
= *++argv
;
473 /* if multiple forwards, find end of list */
474 if (ptr
[0]=='R' || ptr
[0]=='L') {
475 for (i
= 0; i
< sizeof(extra_portfwd
) - 2; i
++)
476 if (ptr
[i
]=='\000' && ptr
[i
+1]=='\000')
478 ptr
= ptr
+ i
+ 1; /* point to next forward slot */
480 ptr
[0] = p
[1]; /* insert a 'L' or 'R' at the start */
481 strncpy(ptr
+1, fwd
, sizeof(extra_portfwd
) - i
);
482 q
= strchr(ptr
, ':');
483 if (q
) *q
= '\t'; /* replace first : with \t */
484 ptr
[strlen(ptr
)+1] = '\000'; /* append two '\000' */
485 extra_portfwd
[sizeof(extra_portfwd
) - 1] = '\0';
486 } else if (!strcmp(p
, "-m") && argc
> 1) {
487 char *filename
, *command
;
492 --argc
, filename
= *++argv
;
494 cmdlen
= cmdsize
= 0;
496 fp
= fopen(filename
, "r");
498 fprintf(stderr
, "plink: unable to open command "
499 "file \"%s\"\n", filename
);
507 if (cmdlen
>= cmdsize
) {
508 cmdsize
= cmdlen
+ 512;
509 command
= srealloc(command
, cmdsize
);
511 command
[cmdlen
++] = d
;
513 cfg
.remote_cmd_ptr
= command
;
514 cfg
.remote_cmd_ptr2
= NULL
;
515 cfg
.nopty
= TRUE
; /* command => no terminal */
516 } else if (!strcmp(p
, "-P") && argc
> 1) {
517 --argc
, portnumber
= atoi(*++argv
);
523 * If the hostname starts with "telnet:", set the
524 * protocol to Telnet and process the string as a
527 if (!strncmp(q
, "telnet:", 7)) {
531 if (q
[0] == '/' && q
[1] == '/')
533 cfg
.protocol
= PROT_TELNET
;
535 while (*p
&& *p
!= ':' && *p
!= '/')
544 strncpy(cfg
.host
, q
, sizeof(cfg
.host
) - 1);
545 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
549 * Before we process the [user@]host string, we
550 * first check for the presence of a protocol
551 * prefix (a protocol name followed by ",").
556 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
557 j
= strlen(backends
[i
].name
);
559 !memcmp(backends
[i
].name
, p
, j
)) {
560 default_protocol
= cfg
.protocol
=
561 backends
[i
].protocol
;
563 backends
[i
].backend
->default_port
;
571 * Three cases. Either (a) there's a nonzero
572 * length string followed by an @, in which
573 * case that's user and the remainder is host.
574 * Or (b) there's only one string, not counting
575 * a potential initial @, and it exists in the
576 * saved-sessions database. Or (c) only one
577 * string and it _doesn't_ exist in the
582 p
++, r
= NULL
; /* discount initial @ */
588 do_defaults(p
, &cfg2
);
589 if (cfg2
.host
[0] == '\0') {
590 /* No settings for this host; use defaults */
591 strncpy(cfg
.host
, p
, sizeof(cfg
.host
) - 1);
592 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
593 cfg
.port
= default_port
;
596 cfg
.remote_cmd_ptr
= cfg
.remote_cmd
;
600 strncpy(cfg
.username
, p
, sizeof(cfg
.username
) - 1);
601 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
602 strncpy(cfg
.host
, r
, sizeof(cfg
.host
) - 1);
603 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
604 cfg
.port
= default_port
;
608 int len
= sizeof(cfg
.remote_cmd
) - 1;
609 char *cp
= cfg
.remote_cmd
;
620 strncpy(cp
, *++argv
, len
);
626 cfg
.nopty
= TRUE
; /* command => no terminal */
627 break; /* done with cmdline */
637 * Trim leading whitespace off the hostname if it's there.
640 int space
= strspn(cfg
.host
, " \t");
641 memmove(cfg
.host
, cfg
.host
+space
, 1+strlen(cfg
.host
)-space
);
644 /* See if host is of the form user@host */
645 if (cfg
.host
[0] != '\0') {
646 char *atsign
= strchr(cfg
.host
, '@');
647 /* Make sure we're not overflowing the user field */
649 if (atsign
- cfg
.host
< sizeof cfg
.username
) {
650 strncpy(cfg
.username
, cfg
.host
, atsign
- cfg
.host
);
651 cfg
.username
[atsign
- cfg
.host
] = '\0';
653 memmove(cfg
.host
, atsign
+ 1, 1 + strlen(atsign
+ 1));
658 * Trim a colon suffix off the hostname if it's there.
660 cfg
.host
[strcspn(cfg
.host
, ":")] = '\0';
662 if (!*cfg
.remote_cmd_ptr
)
663 flags
|= FLAG_INTERACTIVE
;
666 * Select protocol. This is farmed out into a table in a
667 * separate file to enable an ssh-free variant.
672 for (i
= 0; backends
[i
].backend
!= NULL
; i
++)
673 if (backends
[i
].protocol
== cfg
.protocol
) {
674 back
= backends
[i
].backend
;
679 "Internal fault: Unsupported protocol found\n");
685 * Add extra port forwardings (accumulated on command line) to
693 while (cfg
.portfwd
[i
])
694 i
+= strlen(cfg
.portfwd
+i
) + 1;
696 if (strlen(p
)+2 > sizeof(cfg
.portfwd
)-i
) {
697 fprintf(stderr
, "Internal fault: not enough space for all"
698 " port forwardings\n");
701 strncpy(cfg
.portfwd
+i
, p
, sizeof(cfg
.portfwd
)-i
-1);
702 i
+= strlen(cfg
.portfwd
+i
) + 1;
703 cfg
.portfwd
[i
] = '\0';
711 if (portnumber
!= -1)
712 cfg
.port
= portnumber
;
715 * Initialise WinSock.
717 winsock_ver
= MAKEWORD(2, 0);
718 if (WSAStartup(winsock_ver
, &wsadata
)) {
719 MessageBox(NULL
, "Unable to initialise WinSock", "WinSock Error",
720 MB_OK
| MB_ICONEXCLAMATION
);
723 if (LOBYTE(wsadata
.wVersion
) != 2 || HIBYTE(wsadata
.wVersion
) != 0) {
724 MessageBox(NULL
, "WinSock version is incompatible with 2.0",
725 "WinSock Error", MB_OK
| MB_ICONEXCLAMATION
);
732 * Start up the connection.
734 netevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
739 error
= back
->init(cfg
.host
, cfg
.port
, &realhost
);
741 fprintf(stderr
, "Unable to open connection:\n%s", error
);
748 stdinevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
749 stdoutevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
750 stderrevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
752 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
753 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
754 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
755 GetConsoleMode(inhandle
, &orig_console_mode
);
756 SetConsoleMode(inhandle
, ENABLE_PROCESSED_INPUT
);
759 * Turn off ECHO and LINE input modes. We don't care if this
760 * call fails, because we know we aren't necessarily running in
763 handles
[0] = netevent
;
764 handles
[1] = stdinevent
;
765 handles
[2] = stdoutevent
;
766 handles
[3] = stderrevent
;
770 * Create spare threads to write to stdout and stderr, so we
771 * can arrange asynchronous writes.
773 odata
.event
= stdoutevent
;
774 odata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
776 odata
.busy
= odata
.done
= 0;
777 if (!CreateThread(NULL
, 0, stdout_write_thread
,
778 &odata
, 0, &out_threadid
)) {
779 fprintf(stderr
, "Unable to create output thread\n");
782 edata
.event
= stderrevent
;
783 edata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
785 edata
.busy
= edata
.done
= 0;
786 if (!CreateThread(NULL
, 0, stdout_write_thread
,
787 &edata
, 0, &err_threadid
)) {
788 fprintf(stderr
, "Unable to create error output thread\n");
795 if (!sending
&& back
->sendok()) {
797 * Create a separate thread to read from stdin. This is
798 * a total pain, but I can't find another way to do it:
800 * - an overlapped ReadFile or ReadFileEx just doesn't
801 * happen; we get failure from ReadFileEx, and
802 * ReadFile blocks despite being given an OVERLAPPED
803 * structure. Perhaps we can't do overlapped reads
804 * on consoles. WHY THE HELL NOT?
806 * - WaitForMultipleObjects(netevent, console) doesn't
807 * work, because it signals the console when
808 * _anything_ happens, including mouse motions and
809 * other things that don't cause data to be readable
810 * - so we're back to ReadFile blocking.
812 idata
.event
= stdinevent
;
813 idata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
814 if (!CreateThread(NULL
, 0, stdin_read_thread
,
815 &idata
, 0, &in_threadid
)) {
816 fprintf(stderr
, "Unable to create input thread\n");
822 n
= WaitForMultipleObjects(4, handles
, FALSE
, INFINITE
);
824 WSANETWORKEVENTS things
;
826 extern SOCKET
first_socket(int *), next_socket(int *);
827 extern int select_result(WPARAM
, LPARAM
);
831 * We must not call select_result() for any socket
832 * until we have finished enumerating within the tree.
833 * This is because select_result() may close the socket
834 * and modify the tree.
836 /* Count the active sockets. */
838 for (socket
= first_socket(&socketstate
);
839 socket
!= INVALID_SOCKET
;
840 socket
= next_socket(&socketstate
)) i
++;
842 /* Expand the buffer if necessary. */
845 sklist
= srealloc(sklist
, sksize
* sizeof(*sklist
));
848 /* Retrieve the sockets into sklist. */
850 for (socket
= first_socket(&socketstate
);
851 socket
!= INVALID_SOCKET
;
852 socket
= next_socket(&socketstate
)) {
853 sklist
[skcount
++] = socket
;
856 /* Now we're done enumerating; go through the list. */
857 for (i
= 0; i
< skcount
; i
++) {
860 wp
= (WPARAM
) socket
;
861 if (!WSAEnumNetworkEvents(socket
, NULL
, &things
)) {
862 noise_ultralight(socket
);
863 noise_ultralight(things
.lNetworkEvents
);
864 if (things
.lNetworkEvents
& FD_CONNECT
)
865 connopen
&= select_result(wp
, (LPARAM
) FD_CONNECT
);
866 if (things
.lNetworkEvents
& FD_READ
)
867 connopen
&= select_result(wp
, (LPARAM
) FD_READ
);
868 if (things
.lNetworkEvents
& FD_CLOSE
)
869 connopen
&= select_result(wp
, (LPARAM
) FD_CLOSE
);
870 if (things
.lNetworkEvents
& FD_OOB
)
871 connopen
&= select_result(wp
, (LPARAM
) FD_OOB
);
872 if (things
.lNetworkEvents
& FD_WRITE
)
873 connopen
&= select_result(wp
, (LPARAM
) FD_WRITE
);
874 if (things
.lNetworkEvents
& FD_ACCEPT
)
875 connopen
&= select_result(wp
, (LPARAM
) FD_ACCEPT
);
881 noise_ultralight(idata
.len
);
882 if (connopen
&& back
->socket() != NULL
) {
884 back
->send(idata
.buffer
, idata
.len
);
886 back
->special(TS_EOF
);
891 if (!odata
.writeret
) {
892 fprintf(stderr
, "Unable to write to standard output\n");
895 bufchain_consume(&stdout_data
, odata
.lenwritten
);
896 if (bufchain_size(&stdout_data
) > 0)
898 if (connopen
&& back
->socket() != NULL
) {
899 back
->unthrottle(bufchain_size(&stdout_data
) +
900 bufchain_size(&stderr_data
));
904 if (!edata
.writeret
) {
905 fprintf(stderr
, "Unable to write to standard output\n");
908 bufchain_consume(&stderr_data
, edata
.lenwritten
);
909 if (bufchain_size(&stderr_data
) > 0)
911 if (connopen
&& back
->socket() != NULL
) {
912 back
->unthrottle(bufchain_size(&stdout_data
) +
913 bufchain_size(&stderr_data
));
916 if (!reading
&& back
->sendbuffer() < MAX_STDIN_BACKLOG
) {
917 SetEvent(idata
.eventback
);
920 if ((!connopen
|| back
->socket() == NULL
) &&
921 bufchain_size(&stdout_data
) == 0 &&
922 bufchain_size(&stderr_data
) == 0)
923 break; /* we closed the connection */