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
);
165 HANDLE inhandle
, outhandle
, errhandle
;
166 DWORD orig_console_mode
;
170 int term_ldisc(int mode
)
174 void ldisc_update(int echo
, int edit
)
176 /* Update stdin read mode to reflect changes in line discipline. */
179 mode
= ENABLE_PROCESSED_INPUT
;
181 mode
= mode
| ENABLE_ECHO_INPUT
;
183 mode
= mode
& ~ENABLE_ECHO_INPUT
;
185 mode
= mode
| ENABLE_LINE_INPUT
;
187 mode
= mode
& ~ENABLE_LINE_INPUT
;
188 SetConsoleMode(inhandle
, mode
);
191 static int get_line(const char *prompt
, char *str
, int maxlen
, int is_pw
)
194 DWORD savemode
, newmode
, i
;
196 if (is_pw
&& password
) {
197 static int tried_once
= 0;
202 strncpy(str
, password
, maxlen
);
203 str
[maxlen
- 1] = '\0';
209 hin
= GetStdHandle(STD_INPUT_HANDLE
);
210 hout
= GetStdHandle(STD_OUTPUT_HANDLE
);
211 if (hin
== INVALID_HANDLE_VALUE
|| hout
== INVALID_HANDLE_VALUE
) {
212 fprintf(stderr
, "Cannot get standard input/output handles");
216 GetConsoleMode(hin
, &savemode
);
217 newmode
= savemode
| ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
;
219 newmode
&= ~ENABLE_ECHO_INPUT
;
221 newmode
|= ENABLE_ECHO_INPUT
;
222 SetConsoleMode(hin
, newmode
);
224 WriteFile(hout
, prompt
, strlen(prompt
), &i
, NULL
);
225 ReadFile(hin
, str
, maxlen
- 1, &i
, NULL
);
227 SetConsoleMode(hin
, savemode
);
229 if ((int) i
> maxlen
)
236 WriteFile(hout
, "\r\n", 2, &i
, NULL
);
244 HANDLE event
, eventback
;
247 static DWORD WINAPI
stdin_read_thread(void *param
)
249 struct input_data
*idata
= (struct input_data
*) param
;
252 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
254 while (ReadFile(inhandle
, idata
->buffer
, sizeof(idata
->buffer
),
255 &idata
->len
, NULL
) && idata
->len
> 0) {
256 SetEvent(idata
->event
);
257 WaitForSingleObject(idata
->eventback
, INFINITE
);
261 SetEvent(idata
->event
);
267 DWORD len
, lenwritten
;
271 HANDLE event
, eventback
;
275 static DWORD WINAPI
stdout_write_thread(void *param
)
277 struct output_data
*odata
= (struct output_data
*) param
;
278 HANDLE outhandle
, errhandle
;
280 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
281 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
284 WaitForSingleObject(odata
->eventback
, INFINITE
);
288 WriteFile(odata
->is_stderr ? errhandle
: outhandle
,
289 odata
->buffer
, odata
->len
, &odata
->lenwritten
, NULL
);
290 SetEvent(odata
->event
);
296 bufchain stdout_data
, stderr_data
;
297 struct output_data odata
, edata
;
299 void try_output(int is_stderr
)
301 struct output_data
*data
= (is_stderr ?
&edata
: &odata
);
306 bufchain_prefix(is_stderr ?
&stderr_data
: &stdout_data
,
307 &senddata
, &sendlen
);
308 data
->buffer
= senddata
;
310 SetEvent(data
->eventback
);
315 int from_backend(int is_stderr
, char *data
, int len
)
317 HANDLE h
= (is_stderr ? errhandle
: outhandle
);
321 bufchain_add(&stderr_data
, data
, len
);
324 bufchain_add(&stdout_data
, data
, len
);
328 osize
= bufchain_size(&stdout_data
);
329 esize
= bufchain_size(&stderr_data
);
331 return osize
+ esize
;
335 * Short description of parameters.
337 static void usage(void)
339 printf("PuTTY Link: command-line connection utility\n");
341 printf("Usage: plink [options] [user@]host [command]\n");
342 printf(" (\"host\" can also be a PuTTY saved session name)\n");
343 printf("Options:\n");
344 printf(" -v show verbose messages\n");
345 printf(" -ssh force use of ssh protocol\n");
346 printf(" -P port connect to specified port\n");
347 printf(" -pw passw login with specified password\n");
348 printf(" -m file read remote command(s) from file\n");
349 printf(" -L listen-port:host:port Forward local port to "
351 printf(" -R listen-port:host:port Forward remote port to"
356 char *do_select(SOCKET skt
, int startup
)
360 events
= (FD_CONNECT
| FD_READ
| FD_WRITE
|
361 FD_OOB
| FD_CLOSE
| FD_ACCEPT
);
365 if (WSAEventSelect(skt
, netevent
, events
) == SOCKET_ERROR
) {
366 switch (WSAGetLastError()) {
368 return "Network is down";
370 return "WSAAsyncSelect(): unknown error";
376 int main(int argc
, char **argv
)
380 WSAEVENT stdinevent
, stdoutevent
, stderrevent
;
382 DWORD in_threadid
, out_threadid
, err_threadid
;
383 struct input_data idata
;
390 char extra_portfwd
[sizeof(cfg
.portfwd
)];
392 ssh_get_line
= get_line
;
395 skcount
= sksize
= 0;
397 * Initialise port and protocol to sensible defaults. (These
398 * will be overridden by more or less anything.)
400 default_protocol
= PROT_SSH
;
405 * Process the command line.
407 do_defaults(NULL
, &cfg
);
408 default_protocol
= cfg
.protocol
;
409 default_port
= cfg
.port
;
412 * Override the default protocol if PLINK_PROTOCOL is set.
414 char *p
= getenv("PLINK_PROTOCOL");
417 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
418 if (!strcmp(backends
[i
].name
, p
)) {
419 default_protocol
= cfg
.protocol
= backends
[i
].protocol
;
420 default_port
= cfg
.port
=
421 backends
[i
].backend
->default_port
;
430 if (!strcmp(p
, "-ssh")) {
431 default_protocol
= cfg
.protocol
= PROT_SSH
;
432 default_port
= cfg
.port
= 22;
433 } else if (!strcmp(p
, "-telnet")) {
434 default_protocol
= cfg
.protocol
= PROT_TELNET
;
435 default_port
= cfg
.port
= 23;
436 } else if (!strcmp(p
, "-raw")) {
437 default_protocol
= cfg
.protocol
= PROT_RAW
;
438 } else if (!strcmp(p
, "-v")) {
439 flags
|= FLAG_VERBOSE
;
440 } else if (!strcmp(p
, "-log")) {
441 logfile
= "putty.log";
442 } else if (!strcmp(p
, "-pw") && argc
> 1) {
443 --argc
, password
= *++argv
;
444 } else if (!strcmp(p
, "-l") && argc
> 1) {
446 --argc
, username
= *++argv
;
447 strncpy(cfg
.username
, username
, sizeof(cfg
.username
));
448 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
449 } else if ((!strcmp(p
, "-L") || !strcmp(p
, "-R")) && argc
> 1) {
452 --argc
, fwd
= *++argv
;
454 /* if multiple forwards, find end of list */
455 if (ptr
[0]=='R' || ptr
[0]=='L') {
456 for (i
= 0; i
< sizeof(extra_portfwd
) - 2; i
++)
457 if (ptr
[i
]=='\000' && ptr
[i
+1]=='\000')
459 ptr
= ptr
+ i
+ 1; /* point to next forward slot */
461 ptr
[0] = p
[1]; /* insert a 'L' or 'R' at the start */
462 strncpy(ptr
+1, fwd
, sizeof(extra_portfwd
) - i
);
463 q
= strchr(ptr
, ':');
464 if (q
) *q
= '\t'; /* replace first : with \t */
465 ptr
[strlen(ptr
)+1] = '\000'; /* append two '\000' */
466 extra_portfwd
[sizeof(extra_portfwd
) - 1] = '\0';
467 } else if (!strcmp(p
, "-m") && argc
> 1) {
468 char *filename
, *command
;
473 --argc
, filename
= *++argv
;
475 cmdlen
= cmdsize
= 0;
477 fp
= fopen(filename
, "r");
479 fprintf(stderr
, "plink: unable to open command "
480 "file \"%s\"\n", filename
);
488 if (cmdlen
>= cmdsize
) {
489 cmdsize
= cmdlen
+ 512;
490 command
= srealloc(command
, cmdsize
);
492 command
[cmdlen
++] = d
;
494 cfg
.remote_cmd_ptr
= command
;
495 cfg
.remote_cmd_ptr2
= NULL
;
496 cfg
.nopty
= TRUE
; /* command => no terminal */
497 } else if (!strcmp(p
, "-P") && argc
> 1) {
498 --argc
, portnumber
= atoi(*++argv
);
504 * If the hostname starts with "telnet:", set the
505 * protocol to Telnet and process the string as a
508 if (!strncmp(q
, "telnet:", 7)) {
512 if (q
[0] == '/' && q
[1] == '/')
514 cfg
.protocol
= PROT_TELNET
;
516 while (*p
&& *p
!= ':' && *p
!= '/')
525 strncpy(cfg
.host
, q
, sizeof(cfg
.host
) - 1);
526 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
530 * Before we process the [user@]host string, we
531 * first check for the presence of a protocol
532 * prefix (a protocol name followed by ",").
537 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
538 j
= strlen(backends
[i
].name
);
540 !memcmp(backends
[i
].name
, p
, j
)) {
541 default_protocol
= cfg
.protocol
=
542 backends
[i
].protocol
;
544 backends
[i
].backend
->default_port
;
552 * Three cases. Either (a) there's a nonzero
553 * length string followed by an @, in which
554 * case that's user and the remainder is host.
555 * Or (b) there's only one string, not counting
556 * a potential initial @, and it exists in the
557 * saved-sessions database. Or (c) only one
558 * string and it _doesn't_ exist in the
563 p
++, r
= NULL
; /* discount initial @ */
569 do_defaults(p
, &cfg2
);
570 if (cfg2
.host
[0] == '\0') {
571 /* No settings for this host; use defaults */
572 strncpy(cfg
.host
, p
, sizeof(cfg
.host
) - 1);
573 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
574 cfg
.port
= default_port
;
577 cfg
.remote_cmd_ptr
= cfg
.remote_cmd
;
581 strncpy(cfg
.username
, p
, sizeof(cfg
.username
) - 1);
582 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
583 strncpy(cfg
.host
, r
, sizeof(cfg
.host
) - 1);
584 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
585 cfg
.port
= default_port
;
589 int len
= sizeof(cfg
.remote_cmd
) - 1;
590 char *cp
= cfg
.remote_cmd
;
601 strncpy(cp
, *++argv
, len
);
607 cfg
.nopty
= TRUE
; /* command => no terminal */
608 break; /* done with cmdline */
618 * Trim leading whitespace off the hostname if it's there.
621 int space
= strspn(cfg
.host
, " \t");
622 memmove(cfg
.host
, cfg
.host
+space
, 1+strlen(cfg
.host
)-space
);
625 /* See if host is of the form user@host */
626 if (cfg
.host
[0] != '\0') {
627 char *atsign
= strchr(cfg
.host
, '@');
628 /* Make sure we're not overflowing the user field */
630 if (atsign
- cfg
.host
< sizeof cfg
.username
) {
631 strncpy(cfg
.username
, cfg
.host
, atsign
- cfg
.host
);
632 cfg
.username
[atsign
- cfg
.host
] = '\0';
634 memmove(cfg
.host
, atsign
+ 1, 1 + strlen(atsign
+ 1));
639 * Trim a colon suffix off the hostname if it's there.
641 cfg
.host
[strcspn(cfg
.host
, ":")] = '\0';
643 if (!*cfg
.remote_cmd_ptr
)
644 flags
|= FLAG_INTERACTIVE
;
647 * Select protocol. This is farmed out into a table in a
648 * separate file to enable an ssh-free variant.
653 for (i
= 0; backends
[i
].backend
!= NULL
; i
++)
654 if (backends
[i
].protocol
== cfg
.protocol
) {
655 back
= backends
[i
].backend
;
660 "Internal fault: Unsupported protocol found\n");
666 * Add extra port forwardings (accumulated on command line) to
674 while (cfg
.portfwd
[i
])
675 i
+= strlen(cfg
.portfwd
+i
) + 1;
677 if (strlen(p
)+2 > sizeof(cfg
.portfwd
)-i
) {
678 fprintf(stderr
, "Internal fault: not enough space for all"
679 " port forwardings\n");
682 strncpy(cfg
.portfwd
+i
, p
, sizeof(cfg
.portfwd
)-i
-1);
683 i
+= strlen(cfg
.portfwd
+i
) + 1;
684 cfg
.portfwd
[i
] = '\0';
692 if (portnumber
!= -1)
693 cfg
.port
= portnumber
;
696 * Initialise WinSock.
698 winsock_ver
= MAKEWORD(2, 0);
699 if (WSAStartup(winsock_ver
, &wsadata
)) {
700 MessageBox(NULL
, "Unable to initialise WinSock", "WinSock Error",
701 MB_OK
| MB_ICONEXCLAMATION
);
704 if (LOBYTE(wsadata
.wVersion
) != 2 || HIBYTE(wsadata
.wVersion
) != 0) {
705 MessageBox(NULL
, "WinSock version is incompatible with 2.0",
706 "WinSock Error", MB_OK
| MB_ICONEXCLAMATION
);
713 * Start up the connection.
715 netevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
720 error
= back
->init(cfg
.host
, cfg
.port
, &realhost
);
722 fprintf(stderr
, "Unable to open connection:\n%s", error
);
729 stdinevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
730 stdoutevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
731 stderrevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
733 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
734 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
735 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
736 GetConsoleMode(inhandle
, &orig_console_mode
);
737 SetConsoleMode(inhandle
, ENABLE_PROCESSED_INPUT
);
740 * Turn off ECHO and LINE input modes. We don't care if this
741 * call fails, because we know we aren't necessarily running in
744 handles
[0] = netevent
;
745 handles
[1] = stdinevent
;
746 handles
[2] = stdoutevent
;
747 handles
[3] = stderrevent
;
751 * Create spare threads to write to stdout and stderr, so we
752 * can arrange asynchronous writes.
754 odata
.event
= stdoutevent
;
755 odata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
757 odata
.busy
= odata
.done
= 0;
758 if (!CreateThread(NULL
, 0, stdout_write_thread
,
759 &odata
, 0, &out_threadid
)) {
760 fprintf(stderr
, "Unable to create output thread\n");
763 edata
.event
= stderrevent
;
764 edata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
766 edata
.busy
= edata
.done
= 0;
767 if (!CreateThread(NULL
, 0, stdout_write_thread
,
768 &edata
, 0, &err_threadid
)) {
769 fprintf(stderr
, "Unable to create error output thread\n");
776 if (!sending
&& back
->sendok()) {
778 * Create a separate thread to read from stdin. This is
779 * a total pain, but I can't find another way to do it:
781 * - an overlapped ReadFile or ReadFileEx just doesn't
782 * happen; we get failure from ReadFileEx, and
783 * ReadFile blocks despite being given an OVERLAPPED
784 * structure. Perhaps we can't do overlapped reads
785 * on consoles. WHY THE HELL NOT?
787 * - WaitForMultipleObjects(netevent, console) doesn't
788 * work, because it signals the console when
789 * _anything_ happens, including mouse motions and
790 * other things that don't cause data to be readable
791 * - so we're back to ReadFile blocking.
793 idata
.event
= stdinevent
;
794 idata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
795 if (!CreateThread(NULL
, 0, stdin_read_thread
,
796 &idata
, 0, &in_threadid
)) {
797 fprintf(stderr
, "Unable to create input thread\n");
803 n
= WaitForMultipleObjects(4, handles
, FALSE
, INFINITE
);
805 WSANETWORKEVENTS things
;
807 extern SOCKET
first_socket(int *), next_socket(int *);
808 extern int select_result(WPARAM
, LPARAM
);
812 * We must not call select_result() for any socket
813 * until we have finished enumerating within the tree.
814 * This is because select_result() may close the socket
815 * and modify the tree.
817 /* Count the active sockets. */
819 for (socket
= first_socket(&socketstate
);
820 socket
!= INVALID_SOCKET
;
821 socket
= next_socket(&socketstate
)) i
++;
823 /* Expand the buffer if necessary. */
826 sklist
= srealloc(sklist
, sksize
* sizeof(*sklist
));
829 /* Retrieve the sockets into sklist. */
831 for (socket
= first_socket(&socketstate
);
832 socket
!= INVALID_SOCKET
;
833 socket
= next_socket(&socketstate
)) {
834 sklist
[skcount
++] = socket
;
837 /* Now we're done enumerating; go through the list. */
838 for (i
= 0; i
< skcount
; i
++) {
841 wp
= (WPARAM
) socket
;
842 if (!WSAEnumNetworkEvents(socket
, NULL
, &things
)) {
843 noise_ultralight(socket
);
844 noise_ultralight(things
.lNetworkEvents
);
845 if (things
.lNetworkEvents
& FD_CONNECT
)
846 connopen
&= select_result(wp
, (LPARAM
) FD_CONNECT
);
847 if (things
.lNetworkEvents
& FD_READ
)
848 connopen
&= select_result(wp
, (LPARAM
) FD_READ
);
849 if (things
.lNetworkEvents
& FD_CLOSE
)
850 connopen
&= select_result(wp
, (LPARAM
) FD_CLOSE
);
851 if (things
.lNetworkEvents
& FD_OOB
)
852 connopen
&= select_result(wp
, (LPARAM
) FD_OOB
);
853 if (things
.lNetworkEvents
& FD_WRITE
)
854 connopen
&= select_result(wp
, (LPARAM
) FD_WRITE
);
855 if (things
.lNetworkEvents
& FD_ACCEPT
)
856 connopen
&= select_result(wp
, (LPARAM
) FD_ACCEPT
);
862 noise_ultralight(idata
.len
);
863 if (connopen
&& back
->socket() != NULL
) {
865 back
->send(idata
.buffer
, idata
.len
);
867 back
->special(TS_EOF
);
872 if (!odata
.writeret
) {
873 fprintf(stderr
, "Unable to write to standard output\n");
876 bufchain_consume(&stdout_data
, odata
.lenwritten
);
877 if (bufchain_size(&stdout_data
) > 0)
879 if (connopen
&& back
->socket() != NULL
) {
880 back
->unthrottle(bufchain_size(&stdout_data
) +
881 bufchain_size(&stderr_data
));
885 if (!edata
.writeret
) {
886 fprintf(stderr
, "Unable to write to standard output\n");
889 bufchain_consume(&stderr_data
, edata
.lenwritten
);
890 if (bufchain_size(&stderr_data
) > 0)
892 if (connopen
&& back
->socket() != NULL
) {
893 back
->unthrottle(bufchain_size(&stdout_data
) +
894 bufchain_size(&stderr_data
));
897 if (!reading
&& back
->sendbuffer() < MAX_STDIN_BACKLOG
) {
898 SetEvent(idata
.eventback
);
901 if ((!connopen
|| back
->socket() == NULL
) &&
902 bufchain_size(&stdout_data
) == 0 &&
903 bufchain_size(&stderr_data
) == 0)
904 break; /* we closed the connection */