summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/agent.rs49
-rw-r--r--src/channel.rs277
-rw-r--r--src/error.rs8
-rw-r--r--src/knownhosts.rs54
-rw-r--r--src/lib.rs76
-rw-r--r--src/listener.rs36
-rw-r--r--src/session.rs660
-rw-r--r--src/sftp.rs31
-rw-r--r--src/util.rs26
-rw-r--r--tests/all.rs36
-rw-r--r--tests/session.rs8
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 }
}
diff --git a/src/lib.rs b/src/lib.rs
index d2dd940..0f55816 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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();
}