mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
Plink: default to sanitising non-tty console output.
If Plink's standard output and/or standard error points at a Windows console or a Unix tty device, and if Plink was not configured to request a remote pty (and hence to send a terminal-type string), then we apply the new control-character stripping facility. The idea is to be a mild defence against malicious remote processes sending confusing escape sequences through the standard error channel when Plink is being used as a transport for something like git: it's OK to have actual sensible error messages come back from the server, but when you run a git command, you didn't really intend to give the remote server the implicit licence to write _all over_ your local terminal display. At the same time, in that scenario, the standard _output_ of Plink is left completely alone, on the grounds that git will be expecting it to be 8-bit clean. (And Plink can tell that because it's redirected away from the console.) For interactive login sessions using Plink, this behaviour is disabled, on the grounds that once you've sent a terminal-type string it's assumed that you were _expecting_ the server to use it to know what escape sequences to send to you. So it should be transparent for all the use cases I've so far thought of. But in case it's not, there's a family of new command-line options like -no-sanitise-stdout and -sanitise-stderr that you can use to forcibly override the autodetection of whether to do it. This all applies the same way to both Unix and Windows Plink.
This commit is contained in:
parent
462b8c7b84
commit
91cf47dd0d
4
Recipe
4
Recipe
@ -333,7 +333,7 @@ U_BE_NOSSH = be_nos_s uxser nocproxy
|
||||
putty : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS
|
||||
puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss LIBS
|
||||
plink : [C] winplink wincons NONSSH WINSSH W_BE_ALL logging WINMISC
|
||||
+ winx11 plink.res winnojmp sessprep noterm LIBS
|
||||
+ winx11 plink.res winnojmp sessprep stripctrl noterm LIBS
|
||||
pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
|
||||
+ pscp.res winnojmp LIBS
|
||||
psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
|
||||
@ -361,7 +361,7 @@ puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH
|
||||
+ nogss utils memory GTKMAIN
|
||||
|
||||
plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal
|
||||
+ ux_x11 noterm uxnogtk sessprep cmdline
|
||||
+ ux_x11 noterm uxnogtk sessprep cmdline stripctrl
|
||||
|
||||
PUTTYGEN_UNIX = sshrsag sshdssg sshprime sshdes ARITH sshmd5 version sshprng
|
||||
+ sshrand uxnoise sshsha MISC sshrsa sshdss uxcons uxstore uxmisc
|
||||
|
@ -79,6 +79,8 @@ use Plink:
|
||||
\c -share enable use of connection sharing
|
||||
\c -hostkey aa:bb:cc:...
|
||||
\c manually specify a host key (may be repeated)
|
||||
\c -sanitise-stderr, -sanitise-stdout, -no-sanitise-stderr, -no-sanitise-stdout
|
||||
\c do/don't strip control chars from standard output/error
|
||||
\c -m file read remote command(s) from file
|
||||
\c -s remote command is an SSH subsystem (SSH-2 only)
|
||||
\c -N don't start a shell/command (SSH-2 only)
|
||||
@ -282,6 +284,50 @@ zero exit status if a usable \q{upstream} exists, nonzero otherwise.
|
||||
|
||||
(This option is only meaningful with the SSH-2 protocol.)
|
||||
|
||||
\S2{plink-option-sanitise} \I{-sanitise-stderr}\I{-sanitise-stdout}\I{-no-sanitise-stderr}\I{-no-sanitise-stdout}\c{-sanitise-}\e{stream}: control output sanitisation
|
||||
|
||||
In some situations, Plink applies a sanitisation pass to the output
|
||||
received from the server, to strip out control characters such as
|
||||
backspace and the escape character.
|
||||
|
||||
The idea of this is to prevent remote processes from sending confusing
|
||||
escape sequences through the standard error channel when Plink is
|
||||
being used as a transport for something like \cw{git} or CVS. If the
|
||||
server actually wants to send an error message, it will probably be
|
||||
plain text; if the server abuses that channel to try to write over
|
||||
unexpected parts of your terminal display, Plink will try to stop it.
|
||||
|
||||
By default, this only happens for output channels which are sent to a
|
||||
Windows console device, or a Unix terminal device. (Any output stream
|
||||
going somewhere else is likely to be needed by an 8-bit protocol and
|
||||
must not be tampered with at all.) It also stops happening if you tell
|
||||
Plink to allocate a remote pseudo-terminal (see \k{using-cmdline-pty}
|
||||
and \k{config-ssh-pty}), on the basis that in that situation you often
|
||||
\e{want} escape sequences from the server to go to your terminal.
|
||||
|
||||
But in case Plink guesses wrong about whether you want this
|
||||
sanitisation, you can override it in either direction, using one of
|
||||
these options:
|
||||
|
||||
\dd \c{-sanitise-stderr}
|
||||
|
||||
\dt Sanitise server data written to Plink's standard error channel,
|
||||
regardless of terminals and consoles and remote ptys.
|
||||
|
||||
\dd \c{-no-sanitise-stderr}
|
||||
|
||||
\dt Do not sanitise server data written to Plink's standard error
|
||||
channel.
|
||||
|
||||
\dd \c{-sanitise-stdout}
|
||||
|
||||
\dt Sanitise server data written to Plink's standard output channel.
|
||||
|
||||
\dd \c{-no-sanitise-stdout}
|
||||
|
||||
\dt Do not sanitise server data written to Plink's standard output
|
||||
channel.
|
||||
|
||||
\H{plink-batch} Using Plink in \i{batch files} and \i{scripts}
|
||||
|
||||
Once you have set up Plink to be able to log in to a remote server
|
||||
|
@ -323,6 +323,10 @@ void cleanup_termios(void)
|
||||
}
|
||||
|
||||
bufchain stdout_data, stderr_data;
|
||||
bufchain_sink stdout_bcs, stderr_bcs;
|
||||
StripCtrlChars *stdout_scc, *stderr_scc;
|
||||
BinarySink *stdout_bs, *stderr_bs;
|
||||
|
||||
enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
|
||||
|
||||
size_t try_output(bool is_stderr)
|
||||
@ -357,14 +361,12 @@ size_t try_output(bool is_stderr)
|
||||
static size_t plink_output(
|
||||
Seat *seat, bool is_stderr, const void *data, size_t len)
|
||||
{
|
||||
if (is_stderr) {
|
||||
bufchain_add(&stderr_data, data, len);
|
||||
return try_output(true);
|
||||
} else {
|
||||
assert(outgoingeof == EOF_NO);
|
||||
bufchain_add(&stdout_data, data, len);
|
||||
return try_output(false);
|
||||
}
|
||||
assert(is_stderr || outgoingeof == EOF_NO);
|
||||
|
||||
BinarySink *bs = is_stderr ? stderr_bs : stdout_bs;
|
||||
put_data(bs, data, len);
|
||||
|
||||
return try_output(is_stderr);
|
||||
}
|
||||
|
||||
static bool plink_eof(Seat *seat)
|
||||
@ -529,6 +531,10 @@ static void usage(void)
|
||||
printf(" -share enable use of connection sharing\n");
|
||||
printf(" -hostkey aa:bb:cc:...\n");
|
||||
printf(" manually specify a host key (may be repeated)\n");
|
||||
printf(" -sanitise-stderr, -sanitise-stdout, "
|
||||
"-no-sanitise-stderr, -no-sanitise-stdout\n");
|
||||
printf(" do/don't strip control chars from standard "
|
||||
"output/error\n");
|
||||
printf(" -m file read remote command(s) from file\n");
|
||||
printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
|
||||
printf(" -N don't start a shell/command (SSH-2 only)\n");
|
||||
@ -565,6 +571,7 @@ int main(int argc, char **argv)
|
||||
int i, fdsize, fdstate;
|
||||
int exitcode;
|
||||
bool errors;
|
||||
enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO;
|
||||
bool use_subsystem = false;
|
||||
bool just_test_share_exists = false;
|
||||
unsigned long now;
|
||||
@ -582,6 +589,10 @@ int main(int argc, char **argv)
|
||||
|
||||
bufchain_init(&stdout_data);
|
||||
bufchain_init(&stderr_data);
|
||||
bufchain_sink_init(&stdout_bcs, &stdout_data);
|
||||
bufchain_sink_init(&stderr_bcs, &stderr_data);
|
||||
stdout_bs = BinarySink_UPCAST(&stdout_bcs);
|
||||
stderr_bs = BinarySink_UPCAST(&stderr_bcs);
|
||||
outgoingeof = EOF_NO;
|
||||
|
||||
flags = FLAG_STDERR_TTY;
|
||||
@ -655,6 +666,18 @@ int main(int argc, char **argv)
|
||||
} else if (!strcmp(p, "-fuzznet")) {
|
||||
conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ);
|
||||
conf_set_str(conf, CONF_proxy_telnet_command, "%host");
|
||||
} else if (!strcmp(p, "-sanitise-stdout") ||
|
||||
!strcmp(p, "-sanitize-stdout")) {
|
||||
sanitise_stdout = FORCE_ON;
|
||||
} else if (!strcmp(p, "-no-sanitise-stdout") ||
|
||||
!strcmp(p, "-no-sanitize-stdout")) {
|
||||
sanitise_stdout = FORCE_OFF;
|
||||
} else if (!strcmp(p, "-sanitise-stderr") ||
|
||||
!strcmp(p, "-sanitize-stderr")) {
|
||||
sanitise_stderr = FORCE_ON;
|
||||
} else if (!strcmp(p, "-no-sanitise-stderr") ||
|
||||
!strcmp(p, "-no-sanitize-stderr")) {
|
||||
sanitise_stderr = FORCE_OFF;
|
||||
} else if (*p != '-') {
|
||||
char *command;
|
||||
int cmdlen, cmdsize;
|
||||
@ -766,6 +789,33 @@ int main(int argc, char **argv)
|
||||
conf_set_int(conf, CONF_height, size.ws_row);
|
||||
}
|
||||
|
||||
/*
|
||||
* Decide whether to sanitise control sequences out of standard
|
||||
* output and standard error.
|
||||
*
|
||||
* If we weren't given a command-line override, we do this if (a)
|
||||
* the fd in question is pointing at a terminal, and (b) we aren't
|
||||
* trying to allocate a terminal as part of the session.
|
||||
*
|
||||
* (Rationale: the risk of control sequences is that they cause
|
||||
* confusion when sent to a local terminal, so if there isn't one,
|
||||
* no problem. Also, if we allocate a remote terminal, then we
|
||||
* sent a terminal type, i.e. we told it what kind of escape
|
||||
* sequences we _like_, i.e. we were expecting to receive some.)
|
||||
*/
|
||||
if (sanitise_stdout == FORCE_ON ||
|
||||
(sanitise_stdout == AUTO && isatty(STDOUT_FILENO) &&
|
||||
conf_get_bool(conf, CONF_nopty))) {
|
||||
stdout_scc = stripctrl_new(stdout_bs, true, L'\0');
|
||||
stdout_bs = BinarySink_UPCAST(stdout_scc);
|
||||
}
|
||||
if (sanitise_stderr == FORCE_ON ||
|
||||
(sanitise_stderr == AUTO && isatty(STDERR_FILENO) &&
|
||||
conf_get_bool(conf, CONF_nopty))) {
|
||||
stderr_scc = stripctrl_new(stderr_bs, true, L'\0');
|
||||
stderr_bs = BinarySink_UPCAST(stderr_scc);
|
||||
}
|
||||
|
||||
sk_init();
|
||||
uxsel_init();
|
||||
|
||||
|
@ -33,6 +33,9 @@ void cmdline_error(const char *fmt, ...)
|
||||
|
||||
HANDLE inhandle, outhandle, errhandle;
|
||||
struct handle *stdin_handle, *stdout_handle, *stderr_handle;
|
||||
handle_sink stdout_hs, stderr_hs;
|
||||
StripCtrlChars *stdout_scc, *stderr_scc;
|
||||
BinarySink *stdout_bs, *stderr_bs;
|
||||
DWORD orig_console_mode;
|
||||
|
||||
WSAEVENT netevent;
|
||||
@ -64,11 +67,8 @@ static void plink_echoedit_update(Seat *seat, bool echo, bool edit)
|
||||
static size_t plink_output(
|
||||
Seat *seat, bool is_stderr, const void *data, size_t len)
|
||||
{
|
||||
if (is_stderr) {
|
||||
handle_write(stderr_handle, data, len);
|
||||
} else {
|
||||
handle_write(stdout_handle, data, len);
|
||||
}
|
||||
BinarySink *bs = is_stderr ? stderr_bs : stdout_bs;
|
||||
put_data(bs, data, len);
|
||||
|
||||
return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);
|
||||
}
|
||||
@ -165,6 +165,10 @@ static void usage(void)
|
||||
printf(" -share enable use of connection sharing\n");
|
||||
printf(" -hostkey aa:bb:cc:...\n");
|
||||
printf(" manually specify a host key (may be repeated)\n");
|
||||
printf(" -sanitise-stderr, -sanitise-stdout, "
|
||||
"-no-sanitise-stderr, -no-sanitise-stdout\n");
|
||||
printf(" do/don't strip control chars from standard "
|
||||
"output/error\n");
|
||||
printf(" -m file read remote command(s) from file\n");
|
||||
printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
|
||||
printf(" -N don't start a shell/command (SSH-2 only)\n");
|
||||
@ -263,6 +267,7 @@ int main(int argc, char **argv)
|
||||
bool errors;
|
||||
bool use_subsystem = false;
|
||||
bool just_test_share_exists = false;
|
||||
enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO;
|
||||
unsigned long now, next, then;
|
||||
const struct BackendVtable *vt;
|
||||
|
||||
@ -334,6 +339,18 @@ int main(int argc, char **argv)
|
||||
exit(1);
|
||||
} else if (!strcmp(p, "-shareexists")) {
|
||||
just_test_share_exists = true;
|
||||
} else if (!strcmp(p, "-sanitise-stdout") ||
|
||||
!strcmp(p, "-sanitize-stdout")) {
|
||||
sanitise_stdout = FORCE_ON;
|
||||
} else if (!strcmp(p, "-no-sanitise-stdout") ||
|
||||
!strcmp(p, "-no-sanitize-stdout")) {
|
||||
sanitise_stdout = FORCE_OFF;
|
||||
} else if (!strcmp(p, "-sanitise-stderr") ||
|
||||
!strcmp(p, "-sanitize-stderr")) {
|
||||
sanitise_stderr = FORCE_ON;
|
||||
} else if (!strcmp(p, "-no-sanitise-stderr") ||
|
||||
!strcmp(p, "-no-sanitize-stderr")) {
|
||||
sanitise_stderr = FORCE_OFF;
|
||||
} else if (*p != '-') {
|
||||
char *command;
|
||||
int cmdlen, cmdsize;
|
||||
@ -482,6 +499,37 @@ int main(int argc, char **argv)
|
||||
*/
|
||||
stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
|
||||
stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
|
||||
handle_sink_init(&stdout_hs, stdout_handle);
|
||||
handle_sink_init(&stderr_hs, stderr_handle);
|
||||
stdout_bs = BinarySink_UPCAST(&stdout_hs);
|
||||
stderr_bs = BinarySink_UPCAST(&stderr_hs);
|
||||
|
||||
/*
|
||||
* Decide whether to sanitise control sequences out of standard
|
||||
* output and standard error.
|
||||
*
|
||||
* If we weren't given a command-line override, we do this if (a)
|
||||
* the fd in question is pointing at a console, and (b) we aren't
|
||||
* trying to allocate a terminal as part of the session.
|
||||
*
|
||||
* (Rationale: the risk of control sequences is that they cause
|
||||
* confusion when sent to a local console, so if there isn't one,
|
||||
* no problem. Also, if we allocate a remote terminal, then we
|
||||
* sent a terminal type, i.e. we told it what kind of escape
|
||||
* sequences we _like_, i.e. we were expecting to receive some.)
|
||||
*/
|
||||
if (sanitise_stdout == FORCE_ON ||
|
||||
(sanitise_stdout == AUTO && is_console_handle(outhandle) &&
|
||||
conf_get_bool(conf, CONF_nopty))) {
|
||||
stdout_scc = stripctrl_new(stdout_bs, true, L'\0');
|
||||
stdout_bs = BinarySink_UPCAST(stdout_scc);
|
||||
}
|
||||
if (sanitise_stderr == FORCE_ON ||
|
||||
(sanitise_stderr == AUTO && is_console_handle(errhandle) &&
|
||||
conf_get_bool(conf, CONF_nopty))) {
|
||||
stderr_scc = stripctrl_new(stderr_bs, true, L'\0');
|
||||
stderr_bs = BinarySink_UPCAST(stderr_scc);
|
||||
}
|
||||
|
||||
main_thread_id = GetCurrentThreadId();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user