From 7cb3d36aedac87cdfa25b4c60e155d1e0e88a512 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Tue, 3 Dec 2019 08:52:12 -0800 Subject: A PtyModes helper for specifying terminal modes Closes: https://github.com/alexcrichton/ssh2-rs/issues/145 --- Cargo.toml | 2 +- src/channel.rs | 10 ++- src/lib.rs | 199 +++++++++++++++++++++++++++++++++++++++++ tests/all/channel.rs | 21 +++++ tests/run_integration_tests.sh | 2 +- 5 files changed, 228 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a61337f..8fa3b2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssh2" -version = "0.5.0" +version = "0.6.0" authors = ["Alex Crichton ", "Wez Furlong "] license = "MIT/Apache-2.0" keywords = ["ssh"] diff --git a/src/channel.rs b/src/channel.rs index 679a6c1..9156f57 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -5,7 +5,7 @@ use std::io::prelude::*; use std::slice; use std::sync::Arc; -use {raw, Error, ExtendedData, SessionInner}; +use {raw, Error, ExtendedData, PtyModes, SessionInner}; /// A channel represents a portion of an SSH connection on which data can be /// read and written. @@ -105,17 +105,19 @@ impl Channel { pub fn request_pty( &mut self, term: &str, - mode: Option<&str>, + mode: Option, dim: Option<(u32, u32, u32, u32)>, ) -> Result<(), Error> { + let mode = mode.map(PtyModes::finish); + let mode = mode.as_ref().map(Vec::as_slice).unwrap_or(&[]); self.sess.rc(unsafe { let (width, height, width_px, height_px) = dim.unwrap_or((80, 24, 0, 0)); raw::libssh2_channel_request_pty_ex( self.raw, term.as_ptr() as *const _, term.len() as c_uint, - mode.map(|s| s.as_ptr()).unwrap_or(0 as *const _) as *const _, - mode.map(|s| s.len()).unwrap_or(0) as c_uint, + mode.as_ptr() as *const _, + mode.len() as c_uint, width as c_int, height as c_int, width_px as c_int, diff --git a/src/lib.rs b/src/lib.rs index dd02a67..4c57256 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -301,3 +301,202 @@ pub enum ExtendedData { /// Discard all extended data as it arrives. Ignore = raw::LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE as isize, } + +/// The modes described in +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum PtyModeOpcode { + /// Indicates end of options. + TTY_OP_END = 0, + /// Interrupt character; 255 if none. Similarly for the other characters. Not all of these characters are supported on all systems. + VINTR = 1, + /// The quit character (sends SIGQUIT signal on POSIX systems). + VQUIT = 2, + /// Erase the character to left of the cursor. + VERASE = 3, + /// Kill the current input line. + VKILL = 4, + /// End-of-file character (sends EOF from the terminal). + VEOF = 5, + /// End-of-line character in addition to carriage return and/or linefeed. + VEOL = 6, + /// Additional end-of-line character. + VEOL2 = 7, + /// Continues paused output (normally control-Q). + VSTART = 8, + /// Pauses output (normally control-S). + VSTOP = 9, + /// Suspends the current program. + VSUSP = 10, + /// Another suspend character. + VDSUSP = 11, + /// Reprints the current input line. + VREPRINT = 12, + /// Erases a word left of cursor. + VWERASE = 13, + /// Enter the next character typed literally, even if it is a special character + VLNEXT = 14, + /// Character to flush output. + VFLUSH = 15, + /// Switch to a different shell layer. + VSWTCH = 16, + /// Prints system status line (load, command, pid, etc). + VSTATUS = 17, + /// Toggles the flushing of terminal output. + VDISCARD = 18, + /// The ignore parity flag. The parameter SHOULD be 0 if this flag is FALSE, and 1 if it is TRUE. + IGNPAR = 30, + /// Mark parity and framing errors. + PARMRK = 31, + /// Enable checking of parity errors. + INPCK = 32, + /// Strip 8th bit off characters. + ISTRIP = 33, + /// Map NL into CR on input. + INLCR = 34, + /// Ignore CR on input. + IGNCR = 35, + /// Map CR to NL on input. + ICRNL = 36, + /// Translate uppercase characters to lowercase. + IUCLC = 37, + /// Enable output flow control. + IXON = 38, + /// Any char will restart after stop. + IXANY = 39, + /// Enable input flow control. + IXOFF = 49, + /// Ring bell on input queue full. + IMAXBEL = 41, + /// Enable signals INTR, QUIT, [D]SUSP. + ISIG = 50, + /// Canonicalize input lines. + ICANON = 51, + + /// Enable input and output of uppercase characters by preceding their lowercase equivalents with "\". + XCASE = 52, + /// Enable echoing. + ECHO = 53, + /// Visually erase chars. + ECHOE = 54, + /// Kill character discards current line. + ECHOK = 55, + /// Echo NL even if ECHO is off. + ECHONL = 56, + /// Don't flush after interrupt. + NOFLSH = 57, + /// Stop background jobs from output. + TOSTOP = 58, + /// Enable extensions. + IEXTEN = 59, + /// Echo control characters as ^(Char). + ECHOCTL = 60, + /// Visual erase for line kill. + ECHOKE = 61, + /// Retype pending input. + PENDIN = 62, + /// Enable output processing. + OPOST = 70, + /// Convert lowercase to uppercase. + OLCUC = 71, + /// Map NL to CR-NL. + ONLCR = 72, + /// Translate carriage return to newline (output). + OCRNL = 73, + /// Translate newline to carriage return-newline (output). + ONOCR = 74, + /// Newline performs a carriage return (output). + ONLRET = 75, + /// 7 bit mode. + CS7 = 90, + /// 8 bit mode. + CS8 = 91, + /// Parity enable. + PARENB = 92, + /// Odd parity, else even. + PARODD = 93, + + /// Specifies the input baud rate in bits per second. + TTY_OP_ISPEED = 128, + /// Specifies the output baud rate in bits per second. + TTY_OP_OSPEED = 129, +} + +/// An opcode for setting a Pty terminal mode +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum ExtensiblePtyModeOpcode { + /// Use one of the modes specified by RFC 4250 + Mode(PtyModeOpcode), + /// Use a mode not reflected by RFC 4250 + Extended(u8), +} + +impl From for ExtensiblePtyModeOpcode { + fn from(op: PtyModeOpcode) -> ExtensiblePtyModeOpcode { + ExtensiblePtyModeOpcode::Mode(op) + } +} + +impl From for ExtensiblePtyModeOpcode { + fn from(op: u8) -> ExtensiblePtyModeOpcode { + ExtensiblePtyModeOpcode::Extended(op) + } +} + +impl ExtensiblePtyModeOpcode { + fn as_opcode(&self) -> u8 { + match self { + ExtensiblePtyModeOpcode::Mode(m) => *m as u8, + ExtensiblePtyModeOpcode::Extended(op) => *op, + } + } +} + +/// Encodes modes for Pty allocation requests. +/// The modes documented in +/// are supported. +pub struct PtyModes { + data: Vec, +} + +impl PtyModes { + /// Construct a PtyModes instance so that you can specify values for + /// various modes + pub fn new() -> Self { + Self { data: vec![] } + } + + /// Set a mode to an arbitrary u32 value + pub fn set_u32>(&mut self, option: O, value: u32) { + let data = [ + option.into().as_opcode(), + ((value >> 24) & 0xff) as u8, + ((value >> 16) & 0xff) as u8, + ((value >> 8) & 0xff) as u8, + (value & 0xff) as u8, + ]; + self.data.extend_from_slice(&data); + } + + /// Set a mode to a boolean value + pub fn set_boolean>(&mut self, option: O, value: bool) { + self.set_u32(option, if value { 1 } else { 0 }) + } + + /// Set a mode to a character value. + /// If the character is None it is set to 255 to indicate that it + /// is disabled. + /// While this interface and the protocol accept unicode characters + /// of up to 32 bits in width, these options likely only work for + /// characters in the 7-bit ascii range. + pub fn set_character>(&mut self, option: O, c: Option) { + self.set_u32(option, c.map(|c| c as u32).unwrap_or(255)) + } + + /// Finish accumulating modes and return the encoded + /// byte stream suitable for use in the ssh2 protocol + pub fn finish(mut self) -> Vec { + self.data.push(PtyModeOpcode::TTY_OP_END as u8); + self.data + } +} diff --git a/tests/all/channel.rs b/tests/all/channel.rs index 660c72d..8b624ac 100644 --- a/tests/all/channel.rs +++ b/tests/all/channel.rs @@ -220,3 +220,24 @@ fn exit_code_ignores_other_errors() { assert!(sess.disconnect(None, &longdescription, None).is_err()); // max len == 256 assert!(channel.exit_status().unwrap() == 0); } + +#[test] +fn pty_modes_are_propagated() { + let sess = ::authed_session(); + let mut channel = sess.channel_session().unwrap(); + eprintln!("requesting pty"); + + let mut mode = ssh2::PtyModes::new(); + // intr is typically CTRL-C; setting it to unmodified `y` + // should be very high signal that it took effect + mode.set_character(ssh2::PtyModeOpcode::VINTR, Some('y')); + + channel.request_pty("xterm", Some(mode), None).unwrap(); + channel.exec("stty -a").unwrap(); + + let (out, _err) = consume_stdio(&mut channel); + channel.close().unwrap(); + + // This may well be linux specific + assert!(out.contains("intr = y"), "mode was propagated"); +} diff --git a/tests/run_integration_tests.sh b/tests/run_integration_tests.sh index 451fbec..02d132d 100755 --- a/tests/run_integration_tests.sh +++ b/tests/run_integration_tests.sh @@ -31,7 +31,7 @@ cp $SSHDIR/id_rsa.pub $SSHDIR/authorized_keys ssh-keygen -f $SSHDIR/ssh_host_rsa_key -N '' -t rsa -for p in /usr/lib/openssh/sftp-server /usr/libexec/sftp-server ; do +for p in /usr/lib/openssh/sftp-server /usr/libexec/sftp-server /usr/libexec/openssh/sftp-server ; do if test -x $p ; then SFTP=$p fi -- cgit v1.2.3