diff options
-rw-r--r-- | src/agent.rs | 49 | ||||
-rw-r--r-- | src/channel.rs | 277 | ||||
-rw-r--r-- | src/error.rs | 8 | ||||
-rw-r--r-- | src/knownhosts.rs | 54 | ||||
-rw-r--r-- | src/lib.rs | 76 | ||||
-rw-r--r-- | src/listener.rs | 36 | ||||
-rw-r--r-- | src/session.rs | 660 | ||||
-rw-r--r-- | src/sftp.rs | 31 | ||||
-rw-r--r-- | src/util.rs | 26 | ||||
-rw-r--r-- | tests/all.rs | 36 | ||||
-rw-r--r-- | tests/session.rs | 8 |
11 files changed, 666 insertions, 595 deletions
diff --git a/src/agent.rs b/src/agent.rs index f7fa943..e6943f2 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -4,6 +4,7 @@ use std::slice; use std::str; use {raw, Session, Error}; +use util::{Binding, SessionBinding}; /// A structure representing a connection to an SSH agent. /// @@ -26,18 +27,6 @@ pub struct PublicKey<'agent> { } impl<'sess> Agent<'sess> { - /// Wraps a raw pointer in a new Agent structure tied to the lifetime of the - /// given session. - /// - /// This consumes ownership of `raw`. - pub unsafe fn from_raw(sess: &Session, - raw: *mut raw::LIBSSH2_AGENT) -> Agent { - Agent { - raw: raw, - sess: sess, - } - } - /// Connect to an ssh-agent running on the system. pub fn connect(&mut self) -> Result<(), Error> { unsafe { self.sess.rc(raw::libssh2_agent_connect(self.raw)) } @@ -74,6 +63,16 @@ impl<'sess> Agent<'sess> { } } +impl<'sess> SessionBinding<'sess> for Agent<'sess> { + type Raw = raw::LIBSSH2_AGENT; + + unsafe fn from_raw(sess: &'sess Session, + raw: *mut raw::LIBSSH2_AGENT) -> Agent<'sess> { + Agent { raw: raw, sess: sess } + } + fn raw(&self) -> *mut raw::LIBSSH2_AGENT { self.raw } +} + #[unsafe_destructor] impl<'a> Drop for Agent<'a> { fn drop(&mut self) { @@ -89,7 +88,7 @@ impl<'agent> Iterator for Identities<'agent> { match raw::libssh2_agent_get_identity(self.agent.raw, &mut next, self.prev) { - 0 => { self.prev = next; Some(Ok(PublicKey::from_raw(next))) } + 0 => { self.prev = next; Some(Ok(Binding::from_raw(next))) } 1 => None, rc => Some(Err(self.agent.sess.rc(rc).err().unwrap())), } @@ -98,18 +97,6 @@ impl<'agent> Iterator for Identities<'agent> { } impl<'agent> PublicKey<'agent> { - /// Creates a new public key from its raw counterpart. - /// - /// Unsafe because the validity of `raw` cannot be guaranteed and there are - /// no restrictions on the lifetime returned. - pub unsafe fn from_raw(raw: *mut raw::libssh2_agent_publickey) - -> PublicKey<'agent> { - PublicKey { - raw: raw, - marker: marker::ContravariantLifetime, - } - } - /// Return the data of this public key. pub fn blob(&self) -> &[u8] { unsafe { @@ -125,7 +112,15 @@ impl<'agent> PublicKey<'agent> { .unwrap() } } +} + +impl<'agent> Binding for PublicKey<'agent> { + type Raw = *mut raw::libssh2_agent_publickey; + + unsafe fn from_raw(raw: *mut raw::libssh2_agent_publickey) + -> PublicKey<'agent> { + PublicKey { raw: raw, marker: marker::ContravariantLifetime } + } - /// Gain access to the underlying raw pointer - pub fn raw(&self) -> *mut raw::libssh2_agent_publickey { self.raw } + fn raw(&self) -> *mut raw::libssh2_agent_publickey { self.raw } } diff --git a/src/channel.rs b/src/channel.rs index 7db0719..149c1fb 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -3,12 +3,16 @@ use std::io; use libc::{c_uint, c_int, size_t, c_char, c_void, c_uchar}; use {raw, Session, Error}; +use util::{Binding, SessionBinding}; /// A channel represents a portion of an SSH connection on which data can be /// read and written. /// /// Channels denote all of SCP uploads and downloads, shell sessions, remote -/// process executions, and other general-purpose sessions. +/// process executions, and other general-purpose sessions. Each channel +/// implements the `Reader` and `Writer` traits to send and receive data. +/// Whether or not I/O operations are blocking is mandated by the `blocking` +/// flag on a channel's corresponding `Session`. pub struct Channel<'sess> { raw: *mut raw::LIBSSH2_CHANNEL, sess: &'sess Session, @@ -49,66 +53,17 @@ pub struct WriteWindow { } impl<'sess> Channel<'sess> { - /// Wraps a raw pointer in a new Channel structure tied to the lifetime of the - /// given session. - /// - /// This consumes ownership of `raw`. - pub unsafe fn from_raw(sess: &Session, - raw: *mut raw::LIBSSH2_CHANNEL) -> Channel { - Channel { - raw: raw, - sess: sess, - read_limit: None, - } - } - - /// Close an active data channel. - /// - /// In practice this means sending an SSH_MSG_CLOSE packet to the remote - /// host which serves as instruction that no further data will be sent to - /// it. The remote host may still send data back until it sends its own - /// close message in response. - /// - /// To wait for the remote end to close its connection as well, follow this - /// command with `wait_closed` - pub fn close(&mut self) -> Result<(), Error> { - unsafe { - self.sess.rc(raw::libssh2_channel_close(self.raw)) - } - } - - /// Enter a temporary blocking state until the remote host closes the named - /// channel. - /// - /// Typically sent after `close` in order to examine the exit status. - pub fn wait_close(&mut self) -> Result<(), Error> { - unsafe { self.sess.rc(raw::libssh2_channel_wait_closed(self.raw)) } - } - - /// Wait for the remote end to acknowledge an EOF request. - pub fn wait_eof(&mut self) -> Result<(), Error> { - unsafe { self.sess.rc(raw::libssh2_channel_wait_eof(self.raw)) } - } - - /// Check if the remote host has sent an EOF status for the selected stream. - pub fn eof(&self) -> bool { - self.read_limit == Some(0) || - unsafe { raw::libssh2_channel_eof(self.raw) != 0 } - } - - /// Initiate a request on a session type channel. + /// Set an environment variable in the remote channel's process space. /// - /// The SSH2 protocol currently defines shell, exec, and subsystem as - /// standard process services. - pub fn process_startup(&mut self, request: &str, message: Option<&str>) - -> Result<(), Error> { - let message_len = message.map(|s| s.len()).unwrap_or(0); - let message = message.map(|s| s.as_ptr()).unwrap_or(0 as *const _); + /// Note that this does not make sense for all channel types and may be + /// ignored by the server despite returning success. + pub fn setenv(&mut self, var: &str, val: &str) -> Result<(), Error> { unsafe { - let rc = raw::libssh2_channel_process_startup(self.raw, - request.as_ptr() as *const _, request.len() as c_uint, - message as *const _, message_len as c_uint); - self.sess.rc(rc) + self.sess.rc(raw::libssh2_channel_setenv_ex(self.raw, + var.as_ptr() as *const _, + var.len() as c_uint, + val.as_ptr() as *const _, + val.len() as c_uint)) } } @@ -158,10 +113,12 @@ impl<'sess> Channel<'sess> { /// Execute a command /// + /// An execution is one of the standard process services defined by the SSH2 + /// protocol. + /// /// # Example /// /// ```no_run - /// # #![allow(unstable)] /// # use ssh2::Session; /// # let session: Session = panic!(); /// let mut channel = session.channel_session().unwrap(); @@ -173,15 +130,42 @@ impl<'sess> Channel<'sess> { } /// Start a shell + /// + /// A shell is one of the standard process services defined by the SSH2 + /// protocol. pub fn shell(&mut self) -> Result<(), Error> { self.process_startup("shell", None) } - /// Request a subsystem be started + /// Request a subsystem be started. + /// + /// A subsystem is one of the standard process services defined by the SSH2 + /// protocol. pub fn subsystem(&mut self, system: &str) -> Result<(), Error> { self.process_startup("subsystem", Some(system)) } + /// Initiate a request on a session type channel. + /// + /// The SSH2 protocol currently defines shell, exec, and subsystem as + /// standard process services. + pub fn process_startup(&mut self, request: &str, message: Option<&str>) + -> Result<(), Error> { + let message_len = message.map(|s| s.len()).unwrap_or(0); + let message = message.map(|s| s.as_ptr()).unwrap_or(0 as *const _); + unsafe { + let rc = raw::libssh2_channel_process_startup(self.raw, + request.as_ptr() as *const _, request.len() as c_uint, + message as *const _, message_len as c_uint); + self.sess.rc(rc) + } + } + + /// Flush the stderr buffers. + pub fn flush_stderr(&mut self) -> Result<(), Error> { + self.flush_stream(::EXTENDED_DATA_STDERR) + } + /// Flush the read buffer for a given channel instance. /// /// Groups of substreams may be flushed by passing on of the following @@ -196,9 +180,9 @@ impl<'sess> Channel<'sess> { } } - /// Flush the stderr buffers. - pub fn flush_stderr(&mut self) -> Result<(), Error> { - self.flush_stream(::EXTENDED_DATA_STDERR) + /// Write data to the channel stderr stream. + pub fn write_stderr(&mut self, data: &[u8]) -> Result<(), Error> { + self.write_stream(::EXTENDED_DATA_STDERR, data) } /// Write data to a channel stream. @@ -218,53 +202,9 @@ impl<'sess> Channel<'sess> { } } - /// Write data to the channel stderr stream. - pub fn write_stderr(&mut self, data: &[u8]) -> Result<(), Error> { - self.write_stream(::EXTENDED_DATA_STDERR, data) - } - - /// Get the remote exit signal. - pub fn exit_signal(&self) -> Result<ExitSignal, Error> { - unsafe { - let mut sig = 0 as *mut _; - let mut siglen = 0; - let mut msg = 0 as *mut _; - let mut msglen = 0; - let mut lang = 0 as *mut _; - let mut langlen = 0; - let rc = raw::libssh2_channel_get_exit_signal(self.raw, - &mut sig, &mut siglen, - &mut msg, &mut msglen, - &mut lang, - &mut langlen); - try!(self.sess.rc(rc)); - return Ok(ExitSignal { - exit_signal: convert(self, sig, siglen), - error_message: convert(self, msg, msglen), - lang_tag: convert(self, lang, langlen), - }) - } - - unsafe fn convert(chan: &Channel, ptr: *mut c_char, - len: size_t) -> Option<String> { - if ptr.is_null() { return None } - let ret = Vec::from_raw_buf(ptr as *const u8, len as usize); - raw::libssh2_free(chan.sess.raw(), ptr as *mut c_void); - String::from_utf8(ret).ok() - } - } - - /// Returns the exit code raised by the process running on the remote host - /// at the other end of the named channel. - /// - /// Note that the exit status may not be available if the remote end has not - /// yet set its status to closed. - pub fn exit_status(&self) -> Result<i32, Error> { - let ret = unsafe { raw::libssh2_channel_get_exit_status(self.raw) }; - match Error::last_error(self.sess) { - Some(err) => Err(err), - None => Ok(ret as i32) - } + /// Read from the stderr stream . + pub fn read_stderr(&mut self, data: &mut [u8]) -> Result<usize, Error> { + self.read_stream(::EXTENDED_DATA_STDERR, data) } /// Attempt to read data from an active channel stream. @@ -299,32 +239,47 @@ impl<'sess> Channel<'sess> { } } - /// Read from the stderr stream . - pub fn read_stderr(&mut self, data: &mut [u8]) -> Result<usize, Error> { - self.read_stream(::EXTENDED_DATA_STDERR, data) - } - - /// Set an environment variable in the remote channel's process space. + /// Returns the exit code raised by the process running on the remote host + /// at the other end of the named channel. /// - /// Note that this does not make sense for all channel types and may be - /// ignored by the server despite returning success. - pub fn setenv(&mut self, var: &str, val: &str) -> Result<(), Error> { - unsafe { - self.sess.rc(raw::libssh2_channel_setenv_ex(self.raw, - var.as_ptr() as *const _, - var.len() as c_uint, - val.as_ptr() as *const _, - val.len() as c_uint)) + /// Note that the exit status may not be available if the remote end has not + /// yet set its status to closed. + pub fn exit_status(&self) -> Result<i32, Error> { + let ret = unsafe { raw::libssh2_channel_get_exit_status(self.raw) }; + match Error::last_error(self.sess) { + Some(err) => Err(err), + None => Ok(ret as i32) } } - /// Tell the remote host that no further data will be sent on the specified - /// channel. - /// - /// Processes typically interpret this as a closed stdin descriptor. - pub fn send_eof(&mut self) -> Result<(), Error> { + /// Get the remote exit signal. + pub fn exit_signal(&self) -> Result<ExitSignal, Error> { unsafe { - self.sess.rc(raw::libssh2_channel_send_eof(self.raw)) + let mut sig = 0 as *mut _; + let mut siglen = 0; + let mut msg = 0 as *mut _; + let mut msglen = 0; + let mut lang = 0 as *mut _; + let mut langlen = 0; + let rc = raw::libssh2_channel_get_exit_signal(self.raw, + &mut sig, &mut siglen, + &mut msg, &mut msglen, + &mut lang, + &mut langlen); + try!(self.sess.rc(rc)); + return Ok(ExitSignal { + exit_signal: convert(self, sig, siglen), + error_message: convert(self, msg, msglen), + lang_tag: convert(self, lang, langlen), + }) + } + + unsafe fn convert(chan: &Channel, ptr: *mut c_char, + len: size_t) -> Option<String> { + if ptr.is_null() { return None } + let ret = Vec::from_raw_buf(ptr as *const u8, len as usize); + raw::libssh2_free(chan.sess.raw(), ptr as *mut c_void); + String::from_utf8(ret).ok() } } @@ -382,6 +337,64 @@ impl<'sess> Channel<'sess> { pub fn limit_read(&mut self, limit: u64) { self.read_limit = Some(limit); } + + /// Check if the remote host has sent an EOF status for the selected stream. + pub fn eof(&self) -> bool { + self.read_limit == Some(0) || + unsafe { raw::libssh2_channel_eof(self.raw) != 0 } + } + + /// Tell the remote host that no further data will be sent on the specified + /// channel. + /// + /// Processes typically interpret this as a closed stdin descriptor. + pub fn send_eof(&mut self) -> Result<(), Error> { + unsafe { + self.sess.rc(raw::libssh2_channel_send_eof(self.raw)) + } + } + + /// Wait for the remote end to acknowledge an EOF request. + pub fn wait_eof(&mut self) -> Result<(), Error> { + unsafe { self.sess.rc(raw::libssh2_channel_wait_eof(self.raw)) } + } + + /// Close an active data channel. + /// + /// In practice this means sending an SSH_MSG_CLOSE packet to the remote + /// host which serves as instruction that no further data will be sent to + /// it. The remote host may still send data back until it sends its own + /// close message in response. + /// + /// To wait for the remote end to close its connection as well, follow this + /// command with `wait_closed` + pub fn close(&mut self) -> Result<(), Error> { + unsafe { + self.sess.rc(raw::libssh2_channel_close(self.raw)) + } + } + + /// Enter a temporary blocking state until the remote host closes the named + /// channel. + /// + /// Typically sent after `close` in order to examine the exit status. + pub fn wait_close(&mut self) -> Result<(), Error> { + unsafe { self.sess.rc(raw::libssh2_channel_wait_closed(self.raw)) } + } +} + +impl<'sess> SessionBinding<'sess> for Channel<'sess> { + type Raw = raw::LIBSSH2_CHANNEL; + + unsafe fn from_raw(sess: &'sess Session, + raw: *mut raw::LIBSSH2_CHANNEL) -> Channel<'sess> { + Channel { + raw: raw, + sess: sess, + read_limit: None, + } + } + fn raw(&self) -> *mut raw::LIBSSH2_CHANNEL { self.raw } } impl<'sess> Writer for Channel<'sess> { diff --git a/src/error.rs b/src/error.rs index 7118fa9..641116e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,10 @@ +use std::error::Error as StdError; use std::fmt; use std::str; use libc; use {raw, Session}; +use util::Binding; /// Representation of an error that can occur within libssh2 #[derive(Show)] @@ -23,7 +25,7 @@ impl Error { let rc = raw::libssh2_session_last_error(sess.raw(), &mut msg, 0 as *mut _, 0); if rc == 0 { return None } - let s = ::opt_bytes(&STATIC, msg as *const _).unwrap();; + let s = ::opt_bytes(&STATIC, msg as *const _).unwrap(); Some(Error::new(rc, str::from_utf8(s).unwrap())) } } @@ -106,3 +108,7 @@ impl fmt::String for Error { write!(f, "[{}] {}", self.code, self.msg) } } + +impl StdError for Error { + fn description(&self) -> &str { self.message() } +} diff --git a/src/knownhosts.rs b/src/knownhosts.rs index da8a697..70f5f4f 100644 --- a/src/knownhosts.rs +++ b/src/knownhosts.rs @@ -4,6 +4,7 @@ use std::str; use libc::{c_int, size_t}; use {raw, Session, Error, KnownHostFileKind, CheckResult}; +use util::{Binding, SessionBinding}; /// A set of known hosts which can be used to verify the identity of a remote /// server. @@ -63,18 +64,6 @@ pub struct Host<'kh> { } impl<'sess> KnownHosts<'sess> { - /// Wraps a raw pointer in a new KnownHosts structure tied to the lifetime - /// of the given session. - /// - /// This consumes ownership of `raw`. - pub unsafe fn from_raw(sess: &Session, - raw: *mut raw::LIBSSH2_KNOWNHOSTS) -> KnownHosts { - KnownHosts { - raw: raw, - sess: sess, - } - } - /// Reads a collection of known hosts from a specified file and adds them to /// the collection of known hosts. pub fn read_file(&mut self, file: &Path, kind: KnownHostFileKind) @@ -217,6 +206,20 @@ impl<'sess> KnownHosts<'sess> { } } +impl<'sess> SessionBinding<'sess> for KnownHosts<'sess> { + type Raw = raw::LIBSSH2_KNOWNHOSTS; + + unsafe fn from_raw(sess: &'sess Session, raw: *mut raw::LIBSSH2_KNOWNHOSTS) + -> KnownHosts<'sess> { + KnownHosts { + raw: raw, + sess: sess, + } + } + fn raw(&self) -> *mut raw::LIBSSH2_KNOWNHOSTS { self.raw } +} + + #[unsafe_destructor] impl<'sess> Drop for KnownHosts<'sess> { fn drop(&mut self) { @@ -232,7 +235,7 @@ impl<'kh> Iterator for Hosts<'kh> { match raw::libssh2_knownhost_get(self.hosts.raw, &mut next, self.prev) { - 0 => { self.prev = next; Some(Ok(Host::from_raw(next))) } + 0 => { self.prev = next; Some(Ok(Binding::from_raw(next))) } 1 => None, rc => Some(Err(self.hosts.sess.rc(rc).err().unwrap())), } @@ -241,18 +244,6 @@ impl<'kh> Iterator for Hosts<'kh> { } impl<'kh> Host<'kh> { - /// Creates a new host from its raw counterpart. - /// - /// Unsafe as there are no restrictions on the lifetime of the host returned - /// and the validity of `raw` is not known. - pub unsafe fn from_raw(raw: *mut raw::libssh2_knownhost) - -> Host<'kh> { - Host { - raw: raw, - marker: marker::ContravariantLifetime, - } - } - /// This is `None` if no plain text host name exists. pub fn name(&self) -> Option<&str> { unsafe { @@ -268,7 +259,16 @@ impl<'kh> Host<'kh> { }; str::from_utf8(bytes).unwrap() } +} + +impl<'kh> Binding for Host<'kh> { + type Raw = *mut raw::libssh2_knownhost; - /// Gain access to the underlying raw pointer - pub fn raw(&self) -> *mut raw::libssh2_knownhost { self.raw } + unsafe fn from_raw(raw: *mut raw::libssh2_knownhost) -> Host<'kh> { + Host { + raw: raw, + marker: marker::ContravariantLifetime, + } + } + fn raw(&self) -> *mut raw::libssh2_knownhost { self.raw } } @@ -11,7 +11,7 @@ //! //! ## Inspecting ssh-agent //! -//! ``` +//! ```no_run //! use ssh2::Session; //! //! // Almost all APIs require a `Session` to be available @@ -32,17 +32,16 @@ //! ## Authenticating with ssh-agent //! //! ```no_run +//! use std::io::TcpStream; //! use ssh2::Session; //! -//! let sess = Session::new().unwrap(); -//! // perform the handshake with a network socket +//! // Connect to the local SSH server +//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap(); +//! let mut sess = Session::new().unwrap(); +//! sess.handshake(&tcp).unwrap(); //! //! // Try to authenticate with the first identity in the agent. -//! let mut agent = sess.agent().unwrap(); -//! agent.connect().unwrap(); -//! agent.list_identities().unwrap(); -//! let identity = agent.identities().next().unwrap().unwrap(); -//! agent.userauth("foo", &identity).unwrap(); +//! sess.userauth_agent("username").unwrap(); //! //! // Make sure we succeeded //! assert!(sess.authenticated()); @@ -51,24 +50,47 @@ //! ## Authenticating with a password //! //! ```no_run +//! use std::io::TcpStream; //! use ssh2::Session; //! -//! let sess = Session::new().unwrap(); -//! // perform the handshake with a network socket +//! // Connect to the local SSH server +//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap(); +//! let mut sess = Session::new().unwrap(); +//! sess.handshake(&tcp).unwrap(); //! //! sess.userauth_password("username", "password").unwrap(); //! assert!(sess.authenticated()); //! ``` //! +//! ## Run a command +//! +//! ```no_run +//! use std::io::{self, TcpStream}; +//! use ssh2::Session; +//! +//! // Connect to the local SSH server +//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap(); +//! let mut sess = Session::new().unwrap(); +//! sess.handshake(&tcp).unwrap(); +//! sess.userauth_agent("username").unwrap(); +//! +//! let mut channel = sess.channel_session().unwrap(); +//! channel.exec("ls").unwrap(); +//! println!("{}", channel.read_to_string().unwrap()); +//! println!("{}", channel.exit_status().unwrap()); +//! ``` +//! //! ## Upload a file //! //! ```no_run -//! # #![allow(unstable)] -//! use std::io; +//! use std::io::{self, TcpStream}; //! use ssh2::Session; //! -//! let sess = Session::new().unwrap(); -//! // perform a handshake and authenticate the session +//! // Connect to the local SSH server +//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap(); +//! let mut sess = Session::new().unwrap(); +//! sess.handshake(&tcp).unwrap(); +//! sess.userauth_agent("username").unwrap(); //! //! let mut remote_file = sess.scp_send(&Path::new("remote"), //! io::USER_FILE, 10, None).unwrap(); @@ -78,20 +100,22 @@ //! ## Download a file //! //! ```no_run -//! # #![allow(unstable)] +//! use std::io::TcpStream; //! use ssh2::Session; //! -//! let sess = Session::new().unwrap(); -//! // perform a handshake and authenticate the session +//! // Connect to the local SSH server +//! let tcp = TcpStream::connect("127.0.0.1:22").unwrap(); +//! let mut sess = Session::new().unwrap(); +//! sess.handshake(&tcp).unwrap(); +//! sess.userauth_agent("username").unwrap(); //! //! let (mut remote_file, stat) = sess.scp_recv(&Path::new("remote")).unwrap(); -//! //! println!("remote file size: {}", stat.size); //! let contents = remote_file.read_to_end(); //! ``` #![feature(unsafe_destructor)] -#![deny(missing_docs)] +#![deny(missing_docs, unused_results)] #![cfg_attr(test, deny(warnings))] #![allow(unstable)] @@ -126,6 +150,7 @@ mod knownhosts; mod listener; mod session; mod sftp; +mod util; /// Initialize the libssh2 library. /// @@ -172,19 +197,6 @@ pub enum DisconnectCode { IllegalUserName = raw::SSH_DISCONNECT_ILLEGAL_USER_NAME as isize, } -/// Flags to be enabled/disabled on a Session -#[derive(Copy)] -pub enum SessionFlag { - /// If set, libssh2 will not attempt to block SIGPIPEs but will let them - /// trigger from the underlying socket layer. - SigPipe = raw::LIBSSH2_FLAG_SIGPIPE as isize, - - /// If set - before the connection negotiation is performed - libssh2 will - /// try to negotiate compression enabling for this connection. By default - /// libssh2 will not attempt to use compression. - Compress = raw::LIBSSH2_FLAG_COMPRESS as isize, -} - #[allow(missing_docs)] #[derive(Copy)] pub enum HostKeyType { diff --git a/src/listener.rs b/src/listener.rs index 6ea3ad8..a4c6f5e 100644 --- a/src/listener.rs +++ b/src/listener.rs @@ -1,4 +1,5 @@ use {raw, Session, Error, Channel}; +use util::SessionBinding; /// A listener represents a forwarding port from the remote server. /// @@ -10,35 +11,34 @@ pub struct Listener<'sess> { } impl<'sess> Listener<'sess> { - /// Wraps a raw pointer in a new Listener structure tied to the lifetime of the - /// given session. - /// - /// This consumes ownership of `raw`. - pub unsafe fn from_raw(sess: &Session, - raw: *mut raw::LIBSSH2_LISTENER) -> Listener { - Listener { - raw: raw, - sess: sess, - } - } - /// Accept a queued connection from this listener. pub fn accept(&mut self) -> Result<Channel<'sess>, Error> { unsafe { let ret = raw::libssh2_channel_forward_accept(self.raw); - if ret.is_null() { - Err(Error::last_error(self.sess).unwrap()) - } else { - Ok(Channel::from_raw(self.sess, ret)) - } + SessionBinding::from_raw_opt(self.sess, ret) + } + } +} + +impl<'sess> SessionBinding<'sess> for Listener<'sess> { + type Raw = raw::LIBSSH2_LISTENER; + + unsafe fn from_raw(sess: &'sess Session, + raw: *mut raw::LIBSSH2_LISTENER) -> Listener<'sess> { + Listener { + raw: raw, + sess: sess, } } + fn raw(&self) -> *mut raw::LIBSSH2_LISTENER { self.raw } } #[unsafe_destructor] impl<'sess> Drop for Listener<'sess> { fn drop(&mut self) { - unsafe { assert_eq!(raw::libssh2_channel_forward_cancel(self.raw), 0) } + unsafe { + let _ = raw::libssh2_channel_forward_cancel(self.raw); + } } } diff --git a/src/session.rs b/src/session.rs index 3ee43f5..fe96993 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,12 +1,13 @@ use std::ffi::CString; -use std::io; +use std::io::{self, TcpStream}; use std::mem; use std::slice; use std::str; use libc::{self, c_uint, c_int, c_void, c_long}; -use {raw, Error, DisconnectCode, ByApplication, SessionFlag, HostKeyType}; +use {raw, Error, DisconnectCode, ByApplication, HostKeyType}; use {MethodType, Agent, Channel, Listener, HashType, KnownHosts, Sftp}; +use util::{Binding, SessionBinding}; /// An SSH session, typically representing one TCP connection. /// @@ -21,42 +22,20 @@ unsafe impl Send for Session {} impl Session { /// Initializes an SSH session object. + /// + /// This function does not associate the session with a remote connection + /// just yet. Various configuration options can be set such as the blocking + /// mode, compression, sigpipe, the banner, etc. To associate this session + /// with a TCP connection, use the `handshake` method to pass in an + /// already-established TCP socket. pub fn new() -> Option<Session> { ::init(); unsafe { let ret = raw::libssh2_session_init_ex(None, None, None); - if ret.is_null() { return None } - Some(Session::from_raw(ret)) - } - } - - /// Takes ownership of the given raw pointer and wraps it in a session. - /// - /// This is unsafe as there is no guarantee about the validity of `raw`. - pub unsafe fn from_raw(raw: *mut raw::LIBSSH2_SESSION) -> Session { - Session { - raw: raw, + if ret.is_null() {None} else {Some(Binding::from_raw(ret))} } } - /// Get the remote banner - /// - /// Once the session has been setup and handshake() has completed - /// successfully, this function can be used to get the server id from the - /// banner each server presents. - /// - /// May return `None` on invalid utf-8 or if an error has ocurred. - pub fn banner(&self) -> Option<&str> { - self.banner_bytes().and_then(|s| str::from_utf8(s).ok()) - } - - /// See `banner`. - /// - /// Will only return `None` if an error has ocurred. - pub fn banner_bytes(&self) -> Option<&[u8]> { - unsafe { ::opt_bytes(self, raw::libssh2_session_banner_get(self.raw)) } - } - /// Set the SSH protocol banner for the local client /// /// Set the banner that will be sent to the remote host when the SSH session @@ -70,60 +49,54 @@ impl Session { } } - /// Terminate the transport layer. + /// Flag indicating whether SIGPIPE signals will be allowed or blocked. /// - /// Send a disconnect message to the remote host associated with session, - /// along with a reason symbol and a verbose description. - pub fn disconnect(&self, - reason: Option<DisconnectCode>, - description: &str, - lang: Option<&str>) -> Result<(), Error> { - let reason = reason.unwrap_or(ByApplication) as c_int; - let description = CString::from_slice(description.as_bytes()); - let lang = CString::from_slice(lang.unwrap_or("").as_bytes()); - unsafe { - self.rc(raw::libssh2_session_disconnect_ex(self.raw, - reason, - description.as_ptr(), - lang.as_ptr())) - } - } - - /// Enable or disable a flag for this session. - pub fn flag(&self, flag: SessionFlag, enable: bool) -> Result<(), Error> { - unsafe { - self.rc(raw::libssh2_session_flag(self.raw, flag as c_int, - enable as c_int)) - } + /// By default (on relevant platforms) this library will attempt to block + /// and catch SIGPIPE signals. Setting this flag to `true` will cause + /// the library to not attempt to block SIGPIPE from the underlying socket + /// layer. + pub fn set_allow_sigpipe(&self, block: bool) { + let res = unsafe { + self.rc(raw::libssh2_session_flag(self.raw, + raw::LIBSSH2_FLAG_SIGPIPE as c_int, + block as c_int)) + }; + res.unwrap(); } - /// Returns whether the session was previously set to nonblocking. - pub fn is_blocking(&self) -> bool { - unsafe { raw::libssh2_session_get_blocking(self.raw) != 0 } + /// Flag indicating whether this library will attempt to negotiate + /// compression. + /// + /// If set - before the connection negotiation is performed - libssh2 will + /// try to negotiate compression enabling for this connection. By default + /// libssh2 will not attempt to use compression. + pub fn set_compress(&self, compress: bool) { + let res = unsafe { + self.rc(raw::libssh2_session_flag(self.raw, + raw::LIBSSH2_FLAG_COMPRESS as c_int, + compress as c_int)) + }; + res.unwrap(); } /// Set or clear blocking mode on session /// - /// Set or clear blocking mode on the selected on the session. This will - /// instantly affect any channels associated with this session. If a read - /// is performed on a session with no data currently available, a blocking - /// session will wait for data to arrive and return what it receives. A - /// non-blocking session will return immediately with an empty buffer. If a - /// write is performed on a session with no room for more data, a blocking - /// session will wait for room. A non-blocking session will return - /// immediately without writing anything. + /// This will instantly affect any channels associated with this session. If + /// a read is performed on a session with no data currently available, a + /// blocking session will wait for data to arrive and return what it + /// receives. A non-blocking session will return immediately with an empty + /// buffer. If a write is performed on a session with no room for more data, + /// a blocking session will wait for room. A non-blocking session will + /// return immediately without writing anything. pub fn set_blocking(&self, blocking: bool) { unsafe { raw::libssh2_session_set_blocking(self.raw, blocking as c_int) } } - /// Returns the timeout, in milliseconds, for how long blocking calls may - /// wait until they time out. - /// - /// A timeout of 0 signifies no timeout. - pub fn timeout(&self) -> u32 { - unsafe { raw::libssh2_session_get_timeout(self.raw) as u32 } + /// Returns whether the session was previously set to nonblocking. + pub fn is_blocking(&self) -> bool { + unsafe { raw::libssh2_session_get_blocking(self.raw) != 0 } } /// Set timeout for blocking functions. @@ -139,211 +112,40 @@ impl Session { unsafe { raw::libssh2_session_set_timeout(self.raw, timeout_ms) } } - /// Get the remote key. - /// - /// Returns `None` if something went wrong. - pub fn host_key(&self) -> Option<(&[u8], HostKeyType)> { - let mut len = 0; - let mut kind = 0; - unsafe { - let ret = raw::libssh2_session_hostkey(self.raw, &mut len, &mut kind); - if ret.is_null() { return None } - let ret = ret as *const u8; - let data = mem::transmute(slice::from_raw_buf(&ret, len as usize)); - let kind = match kind { - raw::LIBSSH2_HOSTKEY_TYPE_RSA => HostKeyType::Rsa, - raw::LIBSSH2_HOSTKEY_TYPE_DSS => HostKeyType::Dss, - _ => HostKeyType::Unknown, - }; - Some((data, kind)) - } - } - - /// Returns the computed digest of the remote system's hostkey. - /// - /// The bytes returned are the raw hash, and are not printable. If the hash - /// is not yet available `None` is returned. - pub fn host_key_hash(&self, hash: HashType) -> Option<&[u8]> { - let len = match hash { - HashType::Md5 => 16, - HashType::Sha1 => 20, - }; - unsafe { - let ret = raw::libssh2_hostkey_hash(self.raw, hash as c_int); - if ret.is_null() { - None - } else { - let ret = ret as *const u8; - Some(mem::transmute(slice::from_raw_buf(&ret, len))) - } - } - } - - /// Set preferred key exchange method - /// - /// The preferences provided are a comma delimited list of preferred methods - /// to use with the most preferred listed first and the least preferred - /// listed last. If a method is listed which is not supported by libssh2 it - /// will be ignored and not sent to the remote host during protocol - /// negotiation. - pub fn method_pref(&self, - method_type: MethodType, - prefs: &str) -> Result<(), Error> { - let prefs = CString::from_slice(prefs.as_bytes()); - unsafe { - self.rc(raw::libssh2_session_method_pref(self.raw, - method_type as c_int, - prefs.as_ptr())) - } - } - - /// Return the currently active algorithms. - /// - /// Returns the actual method negotiated for a particular transport - /// parameter. May return `None` if the session has not yet been started. - pub fn methods(&self, method_type: MethodType) -> Option<&str> { - unsafe { - let ptr = raw::libssh2_session_methods(self.raw, - method_type as c_int); - ::opt_bytes(self, ptr).and_then(|s| str::from_utf8(s).ok()) - } - } - - /// Get list of supported algorithms. - pub fn supported_algs(&self, method_type: MethodType) - -> Result<Vec<&'static str>, Error> { - static STATIC: () = (); - let method_type = method_type as c_int; - let mut ret = Vec::new(); - unsafe { - let mut ptr = 0 as *mut _; - let rc = raw::libssh2_session_supported_algs(self.raw, method_type, - &mut ptr); - if rc <= 0 { try!(self.rc(rc)) } - for i in range(0, rc as isize) { - let s = ::opt_bytes(&STATIC, *ptr.offset(i)).unwrap();; - let s = str::from_utf8(s).unwrap(); - ret.push(s); - } - raw::libssh2_free(self.raw, ptr as *mut c_void); - } - Ok(ret) - } - - /// Init an ssh-agent handle. + /// Returns the timeout, in milliseconds, for how long blocking calls may + /// wait until they time out. /// - /// The returned agent will still need to be connected manually before use. - pub fn agent(&self) -> Result<Agent, Error> { - unsafe { - let ptr = raw::libssh2_agent_init(self.raw); - if ptr.is_null() { - Err(Error::last_error(self).unwrap()) - } else { - Ok(Agent::from_raw(self, ptr)) - } - } + /// A timeout of 0 signifies no timeout. + pub fn timeout(&self) -> u32 { + unsafe { raw::libssh2_session_get_timeout(self.raw) as u32 } } /// Begin transport layer protocol negotiation with the connected host. /// - /// The socket provided is a connected socket descriptor. Typically a TCP - /// connection though the protocol allows for any reliable transport and - /// the library will attempt to use any berkeley socket. - pub fn handshake(&mut self, socket: raw::libssh2_socket_t) - -> Result<(), Error> { - unsafe { - self.rc(raw::libssh2_session_handshake(self.raw, socket)) - } - } - - /// Allocate a new channel for exchanging data with the server. + /// This session does *not* take ownership of the socket provided, it is + /// recommended to ensure that the socket persists the lifetime of this + /// session to ensure that communication is correctly performed. /// - /// This is typically not called directly but rather through - /// `channel_open_session`, `channel_direct_tcpip`, or - /// `channel_forward_listen`. - pub fn channel_open(&self, channel_type: &str, - window_size: u32, packet_size: u32, - message: Option<&str>) -> Result<Channel, Error> { - let ret = unsafe { - let message_len = message.map(|s| s.len()).unwrap_or(0); - raw::libssh2_channel_open_ex(self.raw, - channel_type.as_ptr() as *const _, - channel_type.len() as c_uint, - window_size as c_uint, - packet_size as c_uint, - message.as_ref().map(|s| s.as_ptr()) - .unwrap_or(0 as *const _) - as *const _, - message_len as c_uint) - }; - if ret.is_null() { - Err(Error::last_error(self).unwrap()) - } else { - Ok(unsafe { Channel::from_raw(self, ret) }) + /// It is also highly recommended that the stream provided is not used + /// concurrently elsewhere for the duration of this session as it may + /// interfere with the protocol. + pub fn handshake(&mut self, stream: &TcpStream) -> Result<(), Error> { + unsafe { + return self.rc(handshake(self.raw, stream)); } - } - - /// Establish a new session-based channel. - pub fn channel_session(&self) -> Result<Channel, Error> { - self.channel_open("session", - raw::LIBSSH2_CHANNEL_WINDOW_DEFAULT as u32, - raw::LIBSSH2_CHANNEL_PACKET_DEFAULT as u32, None) - } - /// Tunnel a TCP connection through an SSH session. - /// - /// Tunnel a TCP/IP connection through the SSH transport via the remote host - /// to a third party. Communication from the client to the SSH server - /// remains encrypted, communication from the server to the 3rd party host - /// travels in cleartext. - /// - /// The optional `src` argument is the host/port to tell the SSH server - /// where the connection originated from. - pub fn channel_direct_tcpip(&self, host: &str, port: u16, - src: Option<(&str, u16)>) - -> Result<Channel, Error> { - let (shost, sport) = src.unwrap_or(("127.0.0.1", 22)); - let host = CString::from_slice(host.as_bytes()); - let shost = CString::from_slice(shost.as_bytes()); - let ret = unsafe { - raw::libssh2_channel_direct_tcpip_ex(self.raw, - host.as_ptr(), - port as c_int, - shost.as_ptr(), - sport as c_int) - }; - if ret.is_null() { - Err(Error::last_error(self).unwrap()) - } else { - Ok(unsafe { Channel::from_raw(self, ret) }) + #[cfg(windows)] + unsafe fn handshake(raw: *mut raw::LIBSSH2_SESSION, stream: &TcpStream) + -> libc::c_int { + use std::os::windows::AsRawSocket; + raw::libssh2_session_handshake(raw, stream.as_raw_socket()) } - } - /// Instruct the remote SSH server to begin listening for inbound TCP/IP - /// connections. - /// - /// New connections will be queued by the library until accepted by - /// `forward_accept`. - pub fn channel_forward_listen(&self, - remote_port: u16, - host: Option<&str>, - queue_maxsize: Option<u32>) - -> Result<(Listener, u16), Error> { - let mut bound_port = 0; - let ret = unsafe { - raw::libssh2_channel_forward_listen_ex(self.raw, - host.map(|s| s.as_ptr()) - .unwrap_or(0 as *const _) - as *mut _, - remote_port as c_int, - &mut bound_port, - queue_maxsize.unwrap_or(0) - as c_int) - }; - if ret.is_null() { - Err(Error::last_error(self).unwrap()) - } else { - Ok((unsafe { Listener::from_raw(self, ret) }, bound_port as u16)) + #[cfg(unix)] + unsafe fn handshake(raw: *mut raw::LIBSSH2_SESSION, stream: &TcpStream) + -> libc::c_int { + use std::os::unix::AsRawFd; + raw::libssh2_session_handshake(raw, stream.as_raw_fd()) } } @@ -365,9 +167,28 @@ impl Session { }) } + /// Attempt to perform SSH agent authentication. + /// + /// This is a helper method for attempting to authenticate the current + /// connection with the first public key found in an SSH agent. If more + /// control is needed than this method offers, it is recommended to use + /// `agent` directly to control how the identity is found. + pub fn userauth_agent(&self, username: &str) -> Result<(), Error> { + let mut agent = try!(self.agent()); + try!(agent.connect()); + try!(agent.list_identities()); + let identity = match agent.identities().next() { + Some(identity) => try!(identity), + None => return Err(Error::new(raw::LIBSSH2_ERROR_INVAL as c_int, + "no identities found in the ssh agent")) + }; + agent.userauth(username, &identity) + } + /// Attempt public key authentication using a PEM encoded private key file /// stored on disk. - pub fn userauth_pubkey_file(&self, username: &str, + pub fn userauth_pubkey_file(&self, + username: &str, pubkey: Option<&Path>, privatekey: &Path, passphrase: Option<&str>) -> Result<(), Error> { @@ -385,8 +206,10 @@ impl Session { }) } - /// Umm... I wish this were documented in libssh2? - pub fn userauth_hostbased_file(&self, username: &str, + // Umm... I wish this were documented in libssh2? + #[allow(missing_docs)] + pub fn userauth_hostbased_file(&self, + username: &str, publickey: &Path, privatekey: &Path, passphrase: Option<&str>, @@ -427,8 +250,8 @@ impl Session { /// authentication scheme (unlikely), it will return SSH_USERAUTH_FAILURE /// along with a listing of what authentication schemes it does support. In /// the unlikely event that none authentication succeeds, this method with - /// return NULL. This case may be distinguished from a failing case by - /// examining libssh2_userauth_authenticated. + /// return an error. This case may be distinguished from a failing case by + /// examining the return value of the `authenticated` method. /// /// The return value is a comma-separated string of supported auth schemes. pub fn auth_methods(&self, username: &str) -> Result<&str, Error> { @@ -445,31 +268,64 @@ impl Session { } } - /// Set how often keepalive messages should be sent. + /// Set preferred key exchange method /// - /// The want_reply argument indicates whether the keepalive messages should - /// request a response from the server. + /// The preferences provided are a comma delimited list of preferred methods + /// to use with the most preferred listed first and the least preferred + /// listed last. If a method is listed which is not supported by libssh2 it + /// will be ignored and not sent to the remote host during protocol + /// negotiation. + pub fn method_pref(&self, + method_type: MethodType, + prefs: &str) -> Result<(), Error> { + let prefs = CString::from_slice(prefs.as_bytes()); + unsafe { + self.rc(raw::libssh2_session_method_pref(self.raw, + method_type as c_int, + prefs.as_ptr())) + } + } + + /// Return the currently active algorithms. /// - /// The interval argument is number of seconds that can pass without any - /// I/O, use 0 (the default) to disable keepalives. To avoid some busy-loop - /// corner-cases, if you specify an interval of 1 it will be treated as 2. - pub fn keepalive_set(&self, want_reply: bool, interval: u32) - -> Result<(), Error> { + /// Returns the actual method negotiated for a particular transport + /// parameter. May return `None` if the session has not yet been started. + pub fn methods(&self, method_type: MethodType) -> Option<&str> { unsafe { - self.rc(raw::libssh2_keepalive_config(self.raw, want_reply as c_int, - interval as c_uint)) + let ptr = raw::libssh2_session_methods(self.raw, + method_type as c_int); + ::opt_bytes(self, ptr).and_then(|s| str::from_utf8(s).ok()) } } - /// Send a keepalive message if needed. + /// Get list of supported algorithms. + pub fn supported_algs(&self, method_type: MethodType) + -> Result<Vec<&'static str>, Error> { + static STATIC: () = (); + let method_type = method_type as c_int; + let mut ret = Vec::new(); + unsafe { + let mut ptr = 0 as *mut _; + let rc = raw::libssh2_session_supported_algs(self.raw, method_type, + &mut ptr); + if rc <= 0 { try!(self.rc(rc)) } + for i in range(0, rc as isize) { + let s = ::opt_bytes(&STATIC, *ptr.offset(i)).unwrap();; + let s = str::from_utf8(s).unwrap(); + ret.push(s); + } + raw::libssh2_free(self.raw, ptr as *mut c_void); + } + Ok(ret) + } + + /// Init an ssh-agent handle. /// - /// Returns how many seconds you can sleep after this call before you need - /// to call it again. - pub fn keepalive_send(&self) -> Result<u32, Error> { - let mut ret = 0; - let rc = unsafe { raw::libssh2_keepalive_send(self.raw, &mut ret) }; - try!(self.rc(rc)); - Ok(ret as u32) + /// The returned agent will still need to be connected manually before use. + pub fn agent(&self) -> Result<Agent, Error> { + unsafe { + SessionBinding::from_raw_opt(self, raw::libssh2_agent_init(self.raw)) + } } /// Init a collection of known hosts for this session. @@ -478,30 +334,90 @@ impl Session { /// collection. pub fn known_hosts(&self) -> Result<KnownHosts, Error> { unsafe { - let ret = raw::libssh2_knownhost_init(self.raw); - if ret.is_null() { - Err(Error::last_error(self).unwrap()) - } else { - Ok(KnownHosts::from_raw(self, ret)) - } + let ptr = raw::libssh2_knownhost_init(self.raw); + SessionBinding::from_raw_opt(self, ptr) + } + } + + /// Establish a new session-based channel. + /// + /// This method is commonly used to create a channel to execute commands + /// over or create a new login shell. + pub fn channel_session(&self) -> Result<Channel, Error> { + self.channel_open("session", + raw::LIBSSH2_CHANNEL_WINDOW_DEFAULT as u32, + raw::LIBSSH2_CHANNEL_PACKET_DEFAULT as u32, None) + } + + /// Tunnel a TCP connection through an SSH session. + /// + /// Tunnel a TCP/IP connection through the SSH transport via the remote host + /// to a third party. Communication from the client to the SSH server + /// remains encrypted, communication from the server to the 3rd party host + /// travels in cleartext. + /// + /// The optional `src` argument is the host/port to tell the SSH server + /// where the connection originated from. + /// + /// The `Channel` returned represents a connection between this host and the + /// specified remote host. + pub fn channel_direct_tcpip(&self, host: &str, port: u16, + src: Option<(&str, u16)>) + -> Result<Channel, Error> { + let (shost, sport) = src.unwrap_or(("127.0.0.1", 22)); + let host = CString::from_slice(host.as_bytes()); + let shost = CString::from_slice(shost.as_bytes()); + unsafe { + let ret = raw::libssh2_channel_direct_tcpip_ex(self.raw, + host.as_ptr(), + port as c_int, + shost.as_ptr(), + sport as c_int); + SessionBinding::from_raw_opt(self, ret) + } + } + + /// Instruct the remote SSH server to begin listening for inbound TCP/IP + /// connections. + /// + /// New connections will be queued by the library until accepted by the + /// `accept` method on the returned `Listener`. + pub fn channel_forward_listen(&self, + remote_port: u16, + host: Option<&str>, + queue_maxsize: Option<u32>) + -> Result<(Listener, u16), Error> { + let mut bound_port = 0; + unsafe { + let ret = raw::libssh2_channel_forward_listen_ex( + self.raw, + host.map(|s| s.as_ptr()).unwrap_or(0 as *const _) + as *mut _, + remote_port as c_int, + &mut bound_port, + queue_maxsize.unwrap_or(0) as c_int); + SessionBinding::from_raw_opt(self, ret).map(|l| (l, bound_port as u16)) } } /// Request a file from the remote host via SCP. + /// + /// The path specified is a path on the remote host which will attempt to be + /// sent over the returned channel. Some stat information is also returned + /// about the remote file to prepare for receiving the file. pub fn scp_recv(&self, path: &Path) -> Result<(Channel, io::FileStat), Error> { let path = CString::from_slice(path.as_vec()); unsafe { let mut sb: libc::stat = mem::zeroed(); let ret = raw::libssh2_scp_recv(self.raw, path.as_ptr(), &mut sb); - if ret.is_null() { return Err(Error::last_error(self).unwrap()) } + let mut c: Channel = try!(SessionBinding::from_raw_opt(self, ret)); // Hm, apparently when we scp_recv() a file the actual channel // itself does not respond well to read_to_end(), and it also sends // an extra 0 byte (or so it seems). To work around this we // artificially limit the channel to a certain amount of bytes that // can be read. - let mut c = Channel::from_raw(self, ret); c.limit_read(sb.st_size as u64); Ok((c, mkstat(&sb))) } @@ -512,6 +428,9 @@ impl Session { /// The `remote_path` provided will the remote file name. The `times` /// argument is a tuple of (mtime, atime), and will default to the remote /// host's current time if not specified. + /// + /// The size of the file, `size`, must be known ahead of time before + /// transmission. pub fn scp_send(&self, remote_path: &Path, mode: io::FilePermission, size: u64, times: Option<(u64, u64)>) -> Result<Channel, Error> { @@ -524,12 +443,7 @@ impl Session { size, mtime as libc::time_t, atime as libc::time_t); - - if ret.is_null() { - Err(Error::last_error(self).unwrap()) - } else { - Ok(Channel::from_raw(self, ret)) - } + SessionBinding::from_raw_opt(self, ret) } } @@ -542,16 +456,137 @@ impl Session { pub fn sftp(&self) -> Result<Sftp, Error> { unsafe { let ret = raw::libssh2_sftp_init(self.raw); + SessionBinding::from_raw_opt(self, ret) + } + } + + /// Allocate a new channel for exchanging data with the server. + /// + /// This is typically not called directly but rather through + /// `channel_session`, `channel_direct_tcpip`, or `channel_forward_listen`. + pub fn channel_open(&self, channel_type: &str, + window_size: u32, packet_size: u32, + message: Option<&str>) -> Result<Channel, Error> { + let message_len = message.map(|s| s.len()).unwrap_or(0); + unsafe { + let ret = raw::libssh2_channel_open_ex(self.raw, + channel_type.as_ptr() as *const _, + channel_type.len() as c_uint, + window_size as c_uint, + packet_size as c_uint, + message.as_ref().map(|s| s.as_ptr()) + .unwrap_or(0 as *const _) + as *const _, + message_len as c_uint); + SessionBinding::from_raw_opt(self, ret) + } + } + + /// Get the remote banner + /// + /// Once the session has been setup and handshake() has completed + /// successfully, this function can be used to get the server id from the + /// banner each server presents. + /// + /// May return `None` on invalid utf-8 or if an error has ocurred. + pub fn banner(&self) -> Option<&str> { + self.banner_bytes().and_then(|s| str::from_utf8(s).ok()) + } + + /// See `banner`. + /// + /// Will only return `None` if an error has ocurred. + pub fn banner_bytes(&self) -> Option<&[u8]> { + unsafe { ::opt_bytes(self, raw::libssh2_session_banner_get(self.raw)) } + } + + /// Get the remote key. + /// + /// Returns `None` if something went wrong. + pub fn host_key(&self) -> Option<(&[u8], HostKeyType)> { + let mut len = 0; + let mut kind = 0; + unsafe { + let ret = raw::libssh2_session_hostkey(self.raw, &mut len, &mut kind); + if ret.is_null() { return None } + let ret = ret as *const u8; + let data = mem::transmute(slice::from_raw_buf(&ret, len as usize)); + let kind = match kind { + raw::LIBSSH2_HOSTKEY_TYPE_RSA => HostKeyType::Rsa, + raw::LIBSSH2_HOSTKEY_TYPE_DSS => HostKeyType::Dss, + _ => HostKeyType::Unknown, + }; + Some((data, kind)) + } + } + + /// Returns the computed digest of the remote system's hostkey. + /// + /// The bytes returned are the raw hash, and are not printable. If the hash + /// is not yet available `None` is returned. + pub fn host_key_hash(&self, hash: HashType) -> Option<&[u8]> { + let len = match hash { + HashType::Md5 => 16, + HashType::Sha1 => 20, + }; + unsafe { + let ret = raw::libssh2_hostkey_hash(self.raw, hash as c_int); if ret.is_null() { - Err(Error::last_error(self).unwrap()) + None } else { - Ok(Sftp::from_raw(self, ret)) + let ret = ret as *const u8; + Some(mem::transmute(slice::from_raw_buf(&ret, len))) } } } - /// Gain access to the underlying raw libssh2 session pointer. - pub fn raw(&self) -> *mut raw::LIBSSH2_SESSION { self.raw } + /// Set how often keepalive messages should be sent. + /// + /// The want_reply argument indicates whether the keepalive messages should + /// request a response from the server. + /// + /// The interval argument is number of seconds that can pass without any + /// I/O, use 0 (the default) to disable keepalives. To avoid some busy-loop + /// corner-cases, if you specify an interval of 1 it will be treated as 2. + pub fn set_keepalive(&self, want_reply: bool, interval: u32) + -> Result<(), Error> { + unsafe { + self.rc(raw::libssh2_keepalive_config(self.raw, want_reply as c_int, + interval as c_uint)) + } + } + + /// Send a keepalive message if needed. + /// + /// Returns how many seconds you can sleep after this call before you need + /// to call it again. + pub fn keepalive_send(&self) -> Result<u32, Error> { + let mut ret = 0; + let rc = unsafe { raw::libssh2_keepalive_send(self.raw, &mut ret) }; + try!(self.rc(rc)); + Ok(ret as u32) + } + + /// Terminate the transport layer. + /// + /// Send a disconnect message to the remote host associated with session, + /// along with a reason symbol and a verbose description. + /// + /// Note that this does *not* close the underlying socket. + pub fn disconnect(&self, + reason: Option<DisconnectCode>, + description: &str, + lang: Option<&str>) -> Result<(), Error> { + let reason = reason.unwrap_or(ByApplication) as c_int; + let description = CString::from_slice(description.as_bytes()); + let lang = CString::from_slice(lang.unwrap_or("").as_bytes()); + unsafe { + self.rc(raw::libssh2_session_disconnect_ex(self.raw, + reason, + description.as_ptr(), + lang.as_ptr())) + } + } /// Translate a return code into a Rust-`Result`. pub fn rc(&self, rc: c_int) -> Result<(), Error> { @@ -566,6 +601,15 @@ impl Session { } } +impl Binding for Session { + type Raw = *mut raw::LIBSSH2_SESSION; + + unsafe fn from_raw(raw: *mut raw::LIBSSH2_SESSION) -> Session { + Session { raw: raw } + } + fn raw(&self) -> *mut raw::LIBSSH2_SESSION { self.raw } +} + // Sure do wish this was exported in libnative! fn mkstat(stat: &libc::stat) -> io::FileStat { #[cfg(windows)] type Mode = libc::c_int; diff --git a/src/sftp.rs b/src/sftp.rs index 3aa2260..d6058ea 100644 --- a/src/sftp.rs +++ b/src/sftp.rs @@ -4,6 +4,7 @@ use std::io; use libc::{c_int, c_ulong, c_long, c_uint, size_t}; use {raw, Session, Error}; +use util::SessionBinding; /// A handle to a remote filesystem over SFTP. /// @@ -95,18 +96,6 @@ pub enum OpenType { } impl<'sess> Sftp<'sess> { - /// Wraps a raw pointer in a new Sftp structure tied to the lifetime of the - /// given session. - /// - /// This consumes ownership of `raw`. - pub unsafe fn from_raw(_sess: &Session, - raw: *mut raw::LIBSSH2_SFTP) -> Sftp { - Sftp { - raw: raw, - marker: marker::ContravariantLifetime, - } - } - /// Open a handle to a file. pub fn open_mode(&self, filename: &Path, flags: OpenFlags, mode: io::FilePermission, @@ -321,6 +310,20 @@ impl<'sess> Sftp<'sess> { } } +impl<'sess> SessionBinding<'sess> for Sftp<'sess> { + type Raw = raw::LIBSSH2_SFTP; + + unsafe fn from_raw(_sess: &'sess Session, + raw: *mut raw::LIBSSH2_SFTP) -> Sftp<'sess> { + Sftp { + raw: raw, + marker: marker::ContravariantLifetime, + } + } + fn raw(&self) -> *mut raw::LIBSSH2_SFTP { self.raw } +} + + #[unsafe_destructor] impl<'sess> Drop for Sftp<'sess> { fn drop(&mut self) { @@ -333,8 +336,8 @@ impl<'sftp> File<'sftp> { /// given session. /// /// This consumes ownership of `raw`. - pub unsafe fn from_raw(sftp: &'sftp Sftp<'sftp>, - raw: *mut raw::LIBSSH2_SFTP_HANDLE) -> File<'sftp> { + unsafe fn from_raw(sftp: &'sftp Sftp<'sftp>, + raw: *mut raw::LIBSSH2_SFTP_HANDLE) -> File<'sftp> { File { raw: raw, sftp: sftp, diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..11b7ffb --- /dev/null +++ b/src/util.rs @@ -0,0 +1,26 @@ +use {Session, Error}; + +#[doc(hidden)] +pub trait Binding: Sized { + type Raw; + + unsafe fn from_raw(raw: Self::Raw) -> Self; + fn raw(&self) -> Self::Raw; +} + +#[doc(hidden)] +pub trait SessionBinding<'sess>: Sized { + type Raw; + + unsafe fn from_raw(sess: &'sess Session, raw: *mut Self::Raw) -> Self; + fn raw(&self) -> *mut Self::Raw; + + unsafe fn from_raw_opt(sess: &'sess Session, raw: *mut Self::Raw) + -> Result<Self, Error> { + if raw.is_null() { + Err(Error::last_error(sess).unwrap()) + } else { + Ok(SessionBinding::from_raw(sess, raw)) + } + } +} diff --git a/tests/all.rs b/tests/all.rs index 2b194f7..590d7d9 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -4,6 +4,7 @@ extern crate libc; use std::mem; use std::num::Int; use std::os; +use std::io::TcpStream; mod agent; mod session; @@ -11,34 +12,15 @@ mod channel; mod knownhosts; mod sftp; -pub struct TcpStream(libc::c_int); - pub fn socket() -> TcpStream { - unsafe { - let socket = libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0); - assert!(socket != -1, "{} {}", os::errno(), os::last_os_error()); - - let addr = libc::sockaddr_in { - sin_family: libc::AF_INET as libc::sa_family_t, - sin_port: 22.to_be(), - sin_addr: libc::in_addr { - s_addr: 0x7f000001.to_be(), - }, - ..mem::zeroed() - }; - - let r = libc::connect(socket, &addr as *const _ as *const _, - mem::size_of_val(&addr) as libc::c_uint); - assert!(r != -1, "{} {}", os::errno(), os::last_os_error()); - TcpStream(socket) - } + TcpStream::connect("127.0.0.1:22").unwrap() } pub fn authed_session() -> (TcpStream, ssh2::Session) { let user = os::getenv("USER").unwrap(); - let mut sess = ssh2::Session::new().unwrap(); let socket = socket(); - sess.handshake(socket.fd()).unwrap(); + let mut sess = ssh2::Session::new().unwrap(); + sess.handshake(&socket).unwrap(); assert!(!sess.authenticated()); { @@ -51,13 +33,3 @@ pub fn authed_session() -> (TcpStream, ssh2::Session) { assert!(sess.authenticated()); (socket, sess) } - -impl TcpStream { - fn fd(&self) -> libc::c_int { let TcpStream(fd) = *self; fd } -} - -impl Drop for TcpStream { - fn drop(&mut self) { - unsafe { libc::close(self.fd()); } - } -} diff --git a/tests/session.rs b/tests/session.rs index d95eb96..1040dad 100644 --- a/tests/session.rs +++ b/tests/session.rs @@ -10,7 +10,7 @@ fn smoke() { sess.set_banner("foo").unwrap(); assert!(sess.is_blocking()); assert_eq!(sess.timeout(), 0); - sess.flag(ssh2::SessionFlag::Compress, true).unwrap(); + sess.set_compress(true); assert!(sess.host_key().is_none()); sess.method_pref(MethodType::Kex, "diffie-hellman-group14-sha1").unwrap(); assert!(sess.methods(MethodType::Kex).is_none()); @@ -24,9 +24,9 @@ fn smoke() { #[test] fn smoke_handshake() { let user = os::getenv("USER").unwrap(); - let mut sess = Session::new().unwrap(); let socket = ::socket(); - sess.handshake(socket.fd()).unwrap(); + let mut sess = Session::new().unwrap(); + sess.handshake(&socket).unwrap(); sess.host_key().unwrap(); let methods = sess.auth_methods(user.as_slice()).unwrap(); assert!(methods.contains("publickey"), "{}", methods); @@ -46,7 +46,7 @@ fn smoke_handshake() { #[test] fn keepalive() { let (_tcp, sess) = ::authed_session(); - sess.keepalive_set(false, 10).unwrap(); + sess.set_keepalive(false, 10).unwrap(); sess.keepalive_send().unwrap(); } |