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 * Ask whether to wipe a session log file before writing to it.
167 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
169 int askappend(char *filename
)
174 static const char msgtemplate
[] =
175 "The session log file \"%.*s\" already exists.\n"
176 "You can overwrite it with a new session log,\n"
177 "append your session log to the end of it,\n"
178 "or disable session logging for this session.\n"
179 "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
180 "or just press Return to disable logging.\n"
181 "Wipe the log file? (y/n, Return cancels logging) ";
185 fprintf(stderr
, msgtemplate
, FILENAME_MAX
, filename
);
188 hin
= GetStdHandle(STD_INPUT_HANDLE
);
189 GetConsoleMode(hin
, &savemode
);
190 SetConsoleMode(hin
, (savemode
| ENABLE_ECHO_INPUT
|
191 ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
));
192 ReadFile(hin
, line
, sizeof(line
) - 1, &i
, NULL
);
193 SetConsoleMode(hin
, savemode
);
195 if (line
[0] == 'y' || line
[0] == 'Y')
197 else if (line
[0] == 'n' || line
[0] == 'N')
204 * Warn about the obsolescent key file format.
206 void old_keyfile_warning(void)
208 static const char message
[] =
209 "You are loading an SSH 2 private key which has an\n"
210 "old version of the file format. This means your key\n"
211 "file is not fully tamperproof. Future versions of\n"
212 "PuTTY may stop supporting this private key format,\n"
213 "so we recommend you convert your key to the new\n"
216 "Once the key is loaded into PuTTYgen, you can perform\n"
217 "this conversion simply by saving it again.\n";
219 fputs(message
, stderr
);
222 HANDLE inhandle
, outhandle
, errhandle
;
223 DWORD orig_console_mode
;
227 int term_ldisc(int mode
)
231 void ldisc_update(int echo
, int edit
)
233 /* Update stdin read mode to reflect changes in line discipline. */
236 mode
= ENABLE_PROCESSED_INPUT
;
238 mode
= mode
| ENABLE_ECHO_INPUT
;
240 mode
= mode
& ~ENABLE_ECHO_INPUT
;
242 mode
= mode
| ENABLE_LINE_INPUT
;
244 mode
= mode
& ~ENABLE_LINE_INPUT
;
245 SetConsoleMode(inhandle
, mode
);
248 static int get_line(const char *prompt
, char *str
, int maxlen
, int is_pw
)
251 DWORD savemode
, newmode
, i
;
253 if (is_pw
&& password
) {
254 static int tried_once
= 0;
259 strncpy(str
, password
, maxlen
);
260 str
[maxlen
- 1] = '\0';
266 hin
= GetStdHandle(STD_INPUT_HANDLE
);
267 hout
= GetStdHandle(STD_OUTPUT_HANDLE
);
268 if (hin
== INVALID_HANDLE_VALUE
|| hout
== INVALID_HANDLE_VALUE
) {
269 fprintf(stderr
, "Cannot get standard input/output handles");
273 GetConsoleMode(hin
, &savemode
);
274 newmode
= savemode
| ENABLE_PROCESSED_INPUT
| ENABLE_LINE_INPUT
;
276 newmode
&= ~ENABLE_ECHO_INPUT
;
278 newmode
|= ENABLE_ECHO_INPUT
;
279 SetConsoleMode(hin
, newmode
);
281 WriteFile(hout
, prompt
, strlen(prompt
), &i
, NULL
);
282 ReadFile(hin
, str
, maxlen
- 1, &i
, NULL
);
284 SetConsoleMode(hin
, savemode
);
286 if ((int) i
> maxlen
)
293 WriteFile(hout
, "\r\n", 2, &i
, NULL
);
301 HANDLE event
, eventback
;
304 static DWORD WINAPI
stdin_read_thread(void *param
)
306 struct input_data
*idata
= (struct input_data
*) param
;
309 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
311 while (ReadFile(inhandle
, idata
->buffer
, sizeof(idata
->buffer
),
312 &idata
->len
, NULL
) && idata
->len
> 0) {
313 SetEvent(idata
->event
);
314 WaitForSingleObject(idata
->eventback
, INFINITE
);
318 SetEvent(idata
->event
);
324 DWORD len
, lenwritten
;
328 HANDLE event
, eventback
;
332 static DWORD WINAPI
stdout_write_thread(void *param
)
334 struct output_data
*odata
= (struct output_data
*) param
;
335 HANDLE outhandle
, errhandle
;
337 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
338 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
341 WaitForSingleObject(odata
->eventback
, INFINITE
);
345 WriteFile(odata
->is_stderr ? errhandle
: outhandle
,
346 odata
->buffer
, odata
->len
, &odata
->lenwritten
, NULL
);
347 SetEvent(odata
->event
);
353 bufchain stdout_data
, stderr_data
;
354 struct output_data odata
, edata
;
356 void try_output(int is_stderr
)
358 struct output_data
*data
= (is_stderr ?
&edata
: &odata
);
363 bufchain_prefix(is_stderr ?
&stderr_data
: &stdout_data
,
364 &senddata
, &sendlen
);
365 data
->buffer
= senddata
;
367 SetEvent(data
->eventback
);
372 int from_backend(int is_stderr
, char *data
, int len
)
374 HANDLE h
= (is_stderr ? errhandle
: outhandle
);
378 bufchain_add(&stderr_data
, data
, len
);
381 bufchain_add(&stdout_data
, data
, len
);
385 osize
= bufchain_size(&stdout_data
);
386 esize
= bufchain_size(&stderr_data
);
388 return osize
+ esize
;
392 * Short description of parameters.
394 static void usage(void)
396 printf("PuTTY Link: command-line connection utility\n");
398 printf("Usage: plink [options] [user@]host [command]\n");
399 printf(" (\"host\" can also be a PuTTY saved session name)\n");
400 printf("Options:\n");
401 printf(" -v show verbose messages\n");
402 printf(" -ssh force use of ssh protocol\n");
403 printf(" -P port connect to specified port\n");
404 printf(" -pw passw login with specified password\n");
405 printf(" -m file read remote command(s) from file\n");
406 printf(" -L listen-port:host:port Forward local port to "
408 printf(" -R listen-port:host:port Forward remote port to"
413 char *do_select(SOCKET skt
, int startup
)
417 events
= (FD_CONNECT
| FD_READ
| FD_WRITE
|
418 FD_OOB
| FD_CLOSE
| FD_ACCEPT
);
422 if (WSAEventSelect(skt
, netevent
, events
) == SOCKET_ERROR
) {
423 switch (WSAGetLastError()) {
425 return "Network is down";
427 return "WSAAsyncSelect(): unknown error";
433 int main(int argc
, char **argv
)
437 WSAEVENT stdinevent
, stdoutevent
, stderrevent
;
439 DWORD in_threadid
, out_threadid
, err_threadid
;
440 struct input_data idata
;
448 char extra_portfwd
[sizeof(cfg
.portfwd
)];
450 ssh_get_line
= get_line
;
453 skcount
= sksize
= 0;
455 * Initialise port and protocol to sensible defaults. (These
456 * will be overridden by more or less anything.)
458 default_protocol
= PROT_SSH
;
463 * Process the command line.
465 do_defaults(NULL
, &cfg
);
466 default_protocol
= cfg
.protocol
;
467 default_port
= cfg
.port
;
470 * Override the default protocol if PLINK_PROTOCOL is set.
472 char *p
= getenv("PLINK_PROTOCOL");
475 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
476 if (!strcmp(backends
[i
].name
, p
)) {
477 default_protocol
= cfg
.protocol
= backends
[i
].protocol
;
478 default_port
= cfg
.port
=
479 backends
[i
].backend
->default_port
;
488 if (!strcmp(p
, "-ssh")) {
489 default_protocol
= cfg
.protocol
= PROT_SSH
;
490 default_port
= cfg
.port
= 22;
491 } else if (!strcmp(p
, "-telnet")) {
492 default_protocol
= cfg
.protocol
= PROT_TELNET
;
493 default_port
= cfg
.port
= 23;
494 } else if (!strcmp(p
, "-rlogin")) {
495 default_protocol
= cfg
.protocol
= PROT_RLOGIN
;
496 default_port
= cfg
.port
= 513;
497 } else if (!strcmp(p
, "-raw")) {
498 default_protocol
= cfg
.protocol
= PROT_RAW
;
499 } else if (!strcmp(p
, "-v")) {
500 flags
|= FLAG_VERBOSE
;
501 } else if (!strcmp(p
, "-log")) {
502 logfile
= "putty.log";
503 } else if (!strcmp(p
, "-pw") && argc
> 1) {
504 --argc
, password
= *++argv
;
505 } else if (!strcmp(p
, "-l") && argc
> 1) {
507 --argc
, username
= *++argv
;
508 strncpy(cfg
.username
, username
, sizeof(cfg
.username
));
509 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
510 } else if ((!strcmp(p
, "-L") || !strcmp(p
, "-R")) && argc
> 1) {
513 --argc
, fwd
= *++argv
;
515 /* if multiple forwards, find end of list */
516 if (ptr
[0]=='R' || ptr
[0]=='L') {
517 for (i
= 0; i
< sizeof(extra_portfwd
) - 2; i
++)
518 if (ptr
[i
]=='\000' && ptr
[i
+1]=='\000')
520 ptr
= ptr
+ i
+ 1; /* point to next forward slot */
522 ptr
[0] = p
[1]; /* insert a 'L' or 'R' at the start */
523 strncpy(ptr
+1, fwd
, sizeof(extra_portfwd
) - i
);
524 q
= strchr(ptr
, ':');
525 if (q
) *q
= '\t'; /* replace first : with \t */
526 ptr
[strlen(ptr
)+1] = '\000'; /* append two '\000' */
527 extra_portfwd
[sizeof(extra_portfwd
) - 1] = '\0';
528 } else if (!strcmp(p
, "-m") && argc
> 1) {
529 char *filename
, *command
;
534 --argc
, filename
= *++argv
;
536 cmdlen
= cmdsize
= 0;
538 fp
= fopen(filename
, "r");
540 fprintf(stderr
, "plink: unable to open command "
541 "file \"%s\"\n", filename
);
549 if (cmdlen
>= cmdsize
) {
550 cmdsize
= cmdlen
+ 512;
551 command
= srealloc(command
, cmdsize
);
553 command
[cmdlen
++] = d
;
555 cfg
.remote_cmd_ptr
= command
;
556 cfg
.remote_cmd_ptr2
= NULL
;
557 cfg
.nopty
= TRUE
; /* command => no terminal */
558 } else if (!strcmp(p
, "-P") && argc
> 1) {
559 --argc
, portnumber
= atoi(*++argv
);
565 * If the hostname starts with "telnet:", set the
566 * protocol to Telnet and process the string as a
569 if (!strncmp(q
, "telnet:", 7)) {
573 if (q
[0] == '/' && q
[1] == '/')
575 cfg
.protocol
= PROT_TELNET
;
577 while (*p
&& *p
!= ':' && *p
!= '/')
586 strncpy(cfg
.host
, q
, sizeof(cfg
.host
) - 1);
587 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
591 * Before we process the [user@]host string, we
592 * first check for the presence of a protocol
593 * prefix (a protocol name followed by ",").
598 for (i
= 0; backends
[i
].backend
!= NULL
; i
++) {
599 j
= strlen(backends
[i
].name
);
601 !memcmp(backends
[i
].name
, p
, j
)) {
602 default_protocol
= cfg
.protocol
=
603 backends
[i
].protocol
;
605 backends
[i
].backend
->default_port
;
613 * Three cases. Either (a) there's a nonzero
614 * length string followed by an @, in which
615 * case that's user and the remainder is host.
616 * Or (b) there's only one string, not counting
617 * a potential initial @, and it exists in the
618 * saved-sessions database. Or (c) only one
619 * string and it _doesn't_ exist in the
624 p
++, r
= NULL
; /* discount initial @ */
630 do_defaults(p
, &cfg2
);
631 if (cfg2
.host
[0] == '\0') {
632 /* No settings for this host; use defaults */
633 strncpy(cfg
.host
, p
, sizeof(cfg
.host
) - 1);
634 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
635 cfg
.port
= default_port
;
638 cfg
.remote_cmd_ptr
= cfg
.remote_cmd
;
642 strncpy(cfg
.username
, p
, sizeof(cfg
.username
) - 1);
643 cfg
.username
[sizeof(cfg
.username
) - 1] = '\0';
644 strncpy(cfg
.host
, r
, sizeof(cfg
.host
) - 1);
645 cfg
.host
[sizeof(cfg
.host
) - 1] = '\0';
646 cfg
.port
= default_port
;
650 int len
= sizeof(cfg
.remote_cmd
) - 1;
651 char *cp
= cfg
.remote_cmd
;
662 strncpy(cp
, *++argv
, len
);
668 cfg
.nopty
= TRUE
; /* command => no terminal */
669 break; /* done with cmdline */
679 * Trim leading whitespace off the hostname if it's there.
682 int space
= strspn(cfg
.host
, " \t");
683 memmove(cfg
.host
, cfg
.host
+space
, 1+strlen(cfg
.host
)-space
);
686 /* See if host is of the form user@host */
687 if (cfg
.host
[0] != '\0') {
688 char *atsign
= strchr(cfg
.host
, '@');
689 /* Make sure we're not overflowing the user field */
691 if (atsign
- cfg
.host
< sizeof cfg
.username
) {
692 strncpy(cfg
.username
, cfg
.host
, atsign
- cfg
.host
);
693 cfg
.username
[atsign
- cfg
.host
] = '\0';
695 memmove(cfg
.host
, atsign
+ 1, 1 + strlen(atsign
+ 1));
700 * Trim a colon suffix off the hostname if it's there.
702 cfg
.host
[strcspn(cfg
.host
, ":")] = '\0';
704 if (!*cfg
.remote_cmd_ptr
)
705 flags
|= FLAG_INTERACTIVE
;
708 * Select protocol. This is farmed out into a table in a
709 * separate file to enable an ssh-free variant.
714 for (i
= 0; backends
[i
].backend
!= NULL
; i
++)
715 if (backends
[i
].protocol
== cfg
.protocol
) {
716 back
= backends
[i
].backend
;
721 "Internal fault: Unsupported protocol found\n");
727 * Add extra port forwardings (accumulated on command line) to
735 while (cfg
.portfwd
[i
])
736 i
+= strlen(cfg
.portfwd
+i
) + 1;
738 if (strlen(p
)+2 > sizeof(cfg
.portfwd
)-i
) {
739 fprintf(stderr
, "Internal fault: not enough space for all"
740 " port forwardings\n");
743 strncpy(cfg
.portfwd
+i
, p
, sizeof(cfg
.portfwd
)-i
-1);
744 i
+= strlen(cfg
.portfwd
+i
) + 1;
745 cfg
.portfwd
[i
] = '\0';
753 if (portnumber
!= -1)
754 cfg
.port
= portnumber
;
757 * Initialise WinSock.
759 winsock_ver
= MAKEWORD(2, 0);
760 if (WSAStartup(winsock_ver
, &wsadata
)) {
761 MessageBox(NULL
, "Unable to initialise WinSock", "WinSock Error",
762 MB_OK
| MB_ICONEXCLAMATION
);
765 if (LOBYTE(wsadata
.wVersion
) != 2 || HIBYTE(wsadata
.wVersion
) != 0) {
766 MessageBox(NULL
, "WinSock version is incompatible with 2.0",
767 "WinSock Error", MB_OK
| MB_ICONEXCLAMATION
);
774 * Start up the connection.
776 netevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
780 /* nodelay is only useful if stdin is a character device (console) */
781 int nodelay
= cfg
.tcp_nodelay
&&
782 (GetFileType(GetStdHandle(STD_INPUT_HANDLE
)) == FILE_TYPE_CHAR
);
784 error
= back
->init(cfg
.host
, cfg
.port
, &realhost
, nodelay
);
786 fprintf(stderr
, "Unable to open connection:\n%s", error
);
793 stdinevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
794 stdoutevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
795 stderrevent
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
797 inhandle
= GetStdHandle(STD_INPUT_HANDLE
);
798 outhandle
= GetStdHandle(STD_OUTPUT_HANDLE
);
799 errhandle
= GetStdHandle(STD_ERROR_HANDLE
);
800 GetConsoleMode(inhandle
, &orig_console_mode
);
801 SetConsoleMode(inhandle
, ENABLE_PROCESSED_INPUT
);
804 * Turn off ECHO and LINE input modes. We don't care if this
805 * call fails, because we know we aren't necessarily running in
808 handles
[0] = netevent
;
809 handles
[1] = stdinevent
;
810 handles
[2] = stdoutevent
;
811 handles
[3] = stderrevent
;
815 * Create spare threads to write to stdout and stderr, so we
816 * can arrange asynchronous writes.
818 odata
.event
= stdoutevent
;
819 odata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
821 odata
.busy
= odata
.done
= 0;
822 if (!CreateThread(NULL
, 0, stdout_write_thread
,
823 &odata
, 0, &out_threadid
)) {
824 fprintf(stderr
, "Unable to create output thread\n");
827 edata
.event
= stderrevent
;
828 edata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
830 edata
.busy
= edata
.done
= 0;
831 if (!CreateThread(NULL
, 0, stdout_write_thread
,
832 &edata
, 0, &err_threadid
)) {
833 fprintf(stderr
, "Unable to create error output thread\n");
840 if (!sending
&& back
->sendok()) {
842 * Create a separate thread to read from stdin. This is
843 * a total pain, but I can't find another way to do it:
845 * - an overlapped ReadFile or ReadFileEx just doesn't
846 * happen; we get failure from ReadFileEx, and
847 * ReadFile blocks despite being given an OVERLAPPED
848 * structure. Perhaps we can't do overlapped reads
849 * on consoles. WHY THE HELL NOT?
851 * - WaitForMultipleObjects(netevent, console) doesn't
852 * work, because it signals the console when
853 * _anything_ happens, including mouse motions and
854 * other things that don't cause data to be readable
855 * - so we're back to ReadFile blocking.
857 idata
.event
= stdinevent
;
858 idata
.eventback
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
859 if (!CreateThread(NULL
, 0, stdin_read_thread
,
860 &idata
, 0, &in_threadid
)) {
861 fprintf(stderr
, "Unable to create input thread\n");
867 n
= WaitForMultipleObjects(4, handles
, FALSE
, INFINITE
);
869 WSANETWORKEVENTS things
;
871 extern SOCKET
first_socket(int *), next_socket(int *);
872 extern int select_result(WPARAM
, LPARAM
);
876 * We must not call select_result() for any socket
877 * until we have finished enumerating within the tree.
878 * This is because select_result() may close the socket
879 * and modify the tree.
881 /* Count the active sockets. */
883 for (socket
= first_socket(&socketstate
);
884 socket
!= INVALID_SOCKET
;
885 socket
= next_socket(&socketstate
)) i
++;
887 /* Expand the buffer if necessary. */
890 sklist
= srealloc(sklist
, sksize
* sizeof(*sklist
));
893 /* Retrieve the sockets into sklist. */
895 for (socket
= first_socket(&socketstate
);
896 socket
!= INVALID_SOCKET
;
897 socket
= next_socket(&socketstate
)) {
898 sklist
[skcount
++] = socket
;
901 /* Now we're done enumerating; go through the list. */
902 for (i
= 0; i
< skcount
; i
++) {
905 wp
= (WPARAM
) socket
;
906 if (!WSAEnumNetworkEvents(socket
, NULL
, &things
)) {
907 noise_ultralight(socket
);
908 noise_ultralight(things
.lNetworkEvents
);
909 if (things
.lNetworkEvents
& FD_CONNECT
)
910 connopen
&= select_result(wp
, (LPARAM
) FD_CONNECT
);
911 if (things
.lNetworkEvents
& FD_READ
)
912 connopen
&= select_result(wp
, (LPARAM
) FD_READ
);
913 if (things
.lNetworkEvents
& FD_CLOSE
)
914 connopen
&= select_result(wp
, (LPARAM
) FD_CLOSE
);
915 if (things
.lNetworkEvents
& FD_OOB
)
916 connopen
&= select_result(wp
, (LPARAM
) FD_OOB
);
917 if (things
.lNetworkEvents
& FD_WRITE
)
918 connopen
&= select_result(wp
, (LPARAM
) FD_WRITE
);
919 if (things
.lNetworkEvents
& FD_ACCEPT
)
920 connopen
&= select_result(wp
, (LPARAM
) FD_ACCEPT
);
926 noise_ultralight(idata
.len
);
927 if (connopen
&& back
->socket() != NULL
) {
929 back
->send(idata
.buffer
, idata
.len
);
931 back
->special(TS_EOF
);
936 if (!odata
.writeret
) {
937 fprintf(stderr
, "Unable to write to standard output\n");
940 bufchain_consume(&stdout_data
, odata
.lenwritten
);
941 if (bufchain_size(&stdout_data
) > 0)
943 if (connopen
&& back
->socket() != NULL
) {
944 back
->unthrottle(bufchain_size(&stdout_data
) +
945 bufchain_size(&stderr_data
));
949 if (!edata
.writeret
) {
950 fprintf(stderr
, "Unable to write to standard output\n");
953 bufchain_consume(&stderr_data
, edata
.lenwritten
);
954 if (bufchain_size(&stderr_data
) > 0)
956 if (connopen
&& back
->socket() != NULL
) {
957 back
->unthrottle(bufchain_size(&stdout_data
) +
958 bufchain_size(&stderr_data
));
961 if (!reading
&& back
->sendbuffer() < MAX_STDIN_BACKLOG
) {
962 SetEvent(idata
.eventback
);
965 if ((!connopen
|| back
->socket() == NULL
) &&
966 bufchain_size(&stdout_data
) == 0 &&
967 bufchain_size(&stderr_data
) == 0)
968 break; /* we closed the connection */
971 exitcode
= back
->exitcode();
973 fprintf(stderr
, "Remote process exit code unavailable\n");
974 exitcode
= 1; /* this is an error condition */