diff options
author | Alex Crichton <alex@alexcrichton.com> | 2014-09-17 21:23:41 -0700 |
---|---|---|
committer | Alex Crichton <alex@alexcrichton.com> | 2014-09-17 21:23:41 -0700 |
commit | b8ac001faa09b1d692ecd651aa0ca121afb40b0e (patch) | |
tree | a3578b6c8a8c7826b6cf05a1120a96adc1ff330d | |
download | ssh2-rs-b8ac001faa09b1d692ecd651aa0ca121afb40b0e.zip |
Initial commit
Bind a Session type (no I/O yet)
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | Cargo.toml | 14 | ||||
-rw-r--r-- | src/agent.rs | 0 | ||||
-rw-r--r-- | src/error.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 93 | ||||
-rw-r--r-- | src/raw.rs | 96 | ||||
-rw-r--r-- | src/session.rs | 236 |
8 files changed, 444 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..22761ba --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: rust diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1e7e7cb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] + name = "ssh2" + version = "0.0.1" + authors = ["Alex Crichton <alex@alexcrichton.com>"] + +[lib] + name = "ssh2" + doctest = false + +[dependencies.libssh2-static-sys] + git = "https://github.com/alexcrichton/libssh2-static-sys" + +[dependencies.link-config] + git = "https://github.com/alexcrichton/link-config" diff --git a/src/agent.rs b/src/agent.rs new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/agent.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..e3ea407 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,2 @@ +#[deriving(Show)] +pub struct Error(pub int); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..623b517 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,93 @@ +#![feature(phase)] + +#[phase(plugin)] +extern crate "link-config" as link_config; +extern crate libc; + +use std::c_str::CString; +use std::mem; +use std::rt; +use std::sync::{Once, ONCE_INIT}; + +pub use session::Session; +pub use error::Error; + +pub mod raw; +mod session; +mod error; + +/// Initialize the libssh2 library. +/// +/// This is optional, it is lazily invoked. +pub fn init() { + static mut INIT: Once = ONCE_INIT; + unsafe { + INIT.doit(|| { + assert_eq!(raw::libssh2_init(0), 0); + rt::at_exit(proc() { + raw::libssh2_exit(); + }); + }) + } +} + +unsafe fn opt_bytes<'a, T>(_: &'a T, + c: *const libc::c_char) -> Option<&'a [u8]> { + if c.is_null() { + None + } else { + let s = CString::new(c, false); + Some(mem::transmute(s.as_bytes_no_nul())) + } +} + +#[allow(missing_doc)] +pub enum DisconnectCode { + HostNotAllowedToConnect = raw::SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT as int, + ProtocolError = raw::SSH_DISCONNECT_PROTOCOL_ERROR as int, + KeyExchangeFailed = raw::SSH_DISCONNECT_KEY_EXCHANGE_FAILED as int, + Reserved = raw::SSH_DISCONNECT_RESERVED as int, + MacError = raw::SSH_DISCONNECT_MAC_ERROR as int, + CompressionError = raw::SSH_DISCONNECT_COMPRESSION_ERROR as int, + ServiceNotAvailable = raw::SSH_DISCONNECT_SERVICE_NOT_AVAILABLE as int, + ProtocolVersionNotSupported = raw::SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED as int, + HostKeyNotVerifiable = raw::SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE as int, + ConnectionLost = raw::SSH_DISCONNECT_CONNECTION_LOST as int, + ByApplication = raw::SSH_DISCONNECT_BY_APPLICATION as int, + TooManyConnections = raw::SSH_DISCONNECT_TOO_MANY_CONNECTIONS as int, + AuthCancelledByUser = raw::SSH_DISCONNECT_AUTH_CANCELLED_BY_USER as int, + NoMoreAuthMethodsAvailable = raw::SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE as int, + IllegalUserName = raw::SSH_DISCONNECT_ILLEGAL_USER_NAME as int, + +} + +/// Flags to be enabled/disabled on a Session +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 int, + + /// 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 int, +} + +pub enum HostKeyType { + TypeUnknown = raw::LIBSSH2_HOSTKEY_TYPE_UNKNOWN as int, + TypeRsa = raw::LIBSSH2_HOSTKEY_TYPE_RSA as int, + TypeDss = raw::LIBSSH2_HOSTKEY_TYPE_DSS as int, +} + +pub enum MethodType { + MethodKex = raw::LIBSSH2_METHOD_KEX as int, + MethodHostKey = raw::LIBSSH2_METHOD_HOSTKEY as int, + MethodCryptCs = raw::LIBSSH2_METHOD_CRYPT_CS as int, + MethodCryptSc = raw::LIBSSH2_METHOD_CRYPT_SC as int, + MethodMacCs = raw::LIBSSH2_METHOD_MAC_CS as int, + MethodMacSc = raw::LIBSSH2_METHOD_MAC_SC as int, + MethodCompCs = raw::LIBSSH2_METHOD_COMP_CS as int, + MethodCompSc = raw::LIBSSH2_METHOD_COMP_SC as int, + MethodLangCs = raw::LIBSSH2_METHOD_LANG_CS as int, + MethodLangSc = raw::LIBSSH2_METHOD_LANG_SC as int, +} diff --git a/src/raw.rs b/src/raw.rs new file mode 100644 index 0000000..674e9dd --- /dev/null +++ b/src/raw.rs @@ -0,0 +1,96 @@ +#![allow(bad_style)] + +use libc::{c_int, size_t, c_void, c_char, c_long}; + +pub static SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT: c_int = 1; +pub static SSH_DISCONNECT_PROTOCOL_ERROR: c_int = 2; +pub static SSH_DISCONNECT_KEY_EXCHANGE_FAILED: c_int = 3; +pub static SSH_DISCONNECT_RESERVED: c_int = 4; +pub static SSH_DISCONNECT_MAC_ERROR: c_int = 5; +pub static SSH_DISCONNECT_COMPRESSION_ERROR: c_int = 6; +pub static SSH_DISCONNECT_SERVICE_NOT_AVAILABLE: c_int = 7; +pub static SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED: c_int = 8; +pub static SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE: c_int = 9; +pub static SSH_DISCONNECT_CONNECTION_LOST: c_int = 10; +pub static SSH_DISCONNECT_BY_APPLICATION: c_int = 11; +pub static SSH_DISCONNECT_TOO_MANY_CONNECTIONS: c_int = 12; +pub static SSH_DISCONNECT_AUTH_CANCELLED_BY_USER: c_int = 13; +pub static SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE: c_int = 14; +pub static SSH_DISCONNECT_ILLEGAL_USER_NAME: c_int = 15; + +pub static LIBSSH2_FLAG_SIGPIPE: c_int = 1; +pub static LIBSSH2_FLAG_COMPRESS: c_int = 2; + +pub static LIBSSH2_HOSTKEY_TYPE_UNKNOWN: c_int = 0; +pub static LIBSSH2_HOSTKEY_TYPE_RSA: c_int = 1; +pub static LIBSSH2_HOSTKEY_TYPE_DSS: c_int = 2; + +pub static LIBSSH2_METHOD_KEX: c_int = 0; +pub static LIBSSH2_METHOD_HOSTKEY: c_int = 1; +pub static LIBSSH2_METHOD_CRYPT_CS: c_int = 2; +pub static LIBSSH2_METHOD_CRYPT_SC: c_int = 3; +pub static LIBSSH2_METHOD_MAC_CS: c_int = 4; +pub static LIBSSH2_METHOD_MAC_SC: c_int = 5; +pub static LIBSSH2_METHOD_COMP_CS: c_int = 6; +pub static LIBSSH2_METHOD_COMP_SC: c_int = 7; +pub static LIBSSH2_METHOD_LANG_CS: c_int = 8; +pub static LIBSSH2_METHOD_LANG_SC: c_int = 9; + +pub enum LIBSSH2_SESSION {} + +pub type LIBSSH2_ALLOC_FUNC = extern fn(size_t, *mut *mut c_void) -> *mut c_void; +pub type LIBSSH2_FREE_FUNC = extern fn(*mut c_void, *mut *mut c_void); +pub type LIBSSH2_REALLOC_FUNC = extern fn(*mut c_void, size_t, *mut *mut c_void) + -> *mut c_void; + +#[cfg(unix)] +link_config!("libssh2", ["favor_static"]) + +#[cfg(unix)] +#[link(name = "z")] +extern {} + +#[cfg(windows)] +#[link(name = "ws2_32")] // needed by ssh2 +#[link(name = "bcrypt")] // needed by ssh2 +#[link(name = "crypt32")] // needed by ssh2 +#[link(name = "ssh2", kind = "static")] +extern {} + +extern { + pub fn libssh2_init(flag: c_int) -> c_int; + pub fn libssh2_exit(); + pub fn libssh2_free(sess: *mut LIBSSH2_SESSION, ptr: *mut c_void); + + pub fn libssh2_session_init_ex(alloc: Option<LIBSSH2_ALLOC_FUNC>, + free: Option<LIBSSH2_FREE_FUNC>, + realloc: Option<LIBSSH2_REALLOC_FUNC>) + -> *mut LIBSSH2_SESSION; + pub fn libssh2_session_free(sess: *mut LIBSSH2_SESSION) -> c_int; + pub fn libssh2_session_banner_get(sess: *mut LIBSSH2_SESSION) -> *const c_char; + pub fn libssh2_session_banner_set(sess: *mut LIBSSH2_SESSION, + banner: *const c_char) -> c_int; + pub fn libssh2_session_disconnect_ex(sess: *mut LIBSSH2_SESSION, + reason: c_int, + description: *const c_char, + lang: *const c_char) -> c_int; + pub fn libssh2_session_flag(sess: *mut LIBSSH2_SESSION, + flag: c_int, value: c_int) -> c_int; + pub fn libssh2_session_get_blocking(session: *mut LIBSSH2_SESSION) -> c_int; + pub fn libssh2_session_get_timeout(sess: *mut LIBSSH2_SESSION) -> c_long; + pub fn libssh2_session_hostkey(sess: *mut LIBSSH2_SESSION, + len: *mut size_t, + kind: *mut c_int) -> *const c_char; + pub fn libssh2_session_method_pref(sess: *mut LIBSSH2_SESSION, + method_type: c_int, + prefs: *const c_char) -> c_int; + pub fn libssh2_session_methods(sess: *mut LIBSSH2_SESSION, + method_type: c_int) -> *const c_char; + pub fn libssh2_session_set_blocking(session: *mut LIBSSH2_SESSION, + blocking: c_int); + pub fn libssh2_session_set_timeout(session: *mut LIBSSH2_SESSION, + timeout: c_long); + pub fn libssh2_session_supported_algs(session: *mut LIBSSH2_SESSION, + method_type: c_int, + algs: *mut *mut *const c_char) -> c_int; +} diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 0000000..421ec44 --- /dev/null +++ b/src/session.rs @@ -0,0 +1,236 @@ +use std::str; +use std::mem; +use std::raw as stdraw; +use libc; + +use {raw, Error, DisconnectCode, ByApplication, SessionFlag, HostKeyType}; +use MethodType; + +pub struct Session { + raw: *mut raw::LIBSSH2_SESSION, +} + +impl Session { + /// Initializes an SSH session object + 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 { + 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(str::from_utf8) + } + + /// 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 + /// is started with handshake(). This is optional; a banner + /// corresponding to the protocol and libssh2 version will be sent by + /// default. + pub fn set_banner(&mut self, banner: &str) -> Result<(), Error> { + let banner = banner.to_c_str(); + unsafe { + self.rc(raw::libssh2_session_banner_set(self.raw, banner.as_ptr())) + } + } + + /// Terminate the transport layer. + /// + /// Send a disconnect message to the remote host associated with session, + /// along with a reason symbol and a verbose description. + pub fn disconnect(&mut self, + reason: Option<DisconnectCode>, + description: &str, + lang: Option<&str>) -> Result<(), Error> { + let reason = reason.unwrap_or(ByApplication) as libc::c_int; + let description = description.to_c_str(); + let lang = lang.unwrap_or("").to_c_str(); + 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(&mut self, flag: SessionFlag, enable: bool) -> Result<(), Error> { + unsafe { + self.rc(raw::libssh2_session_flag(self.raw, flag as libc::c_int, + enable as libc::c_int)) + } + } + + /// 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 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. + pub fn set_blocking(&mut self, blocking: bool) { + unsafe { + raw::libssh2_session_set_blocking(self.raw, blocking as libc::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) -> uint { + unsafe { raw::libssh2_session_get_timeout(self.raw) as uint } + } + + /// Set timeout for blocking functions. + /// + /// Set the timeout in milliseconds for how long a blocking the libssh2 + /// function calls may wait until they consider the situation an error and + /// return an error. + /// + /// By default or if you set the timeout to zero, libssh2 has no timeout + /// for blocking functions. + pub fn set_timeout(&mut self, timeout_ms: uint) { + let timeout_ms = timeout_ms as libc::c_long; + unsafe { raw::libssh2_session_set_timeout(self.raw, timeout_ms) } + } + + /// Get the remote key. + /// + /// Returns `None` if something went wrong. + pub fn hostkey(&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 data: &[u8] = mem::transmute(stdraw::Slice { + data: ret as *const u8, + len: len as uint, + }); + let kind = match kind { + raw::LIBSSH2_HOSTKEY_TYPE_RSA => ::TypeRsa, + raw::LIBSSH2_HOSTKEY_TYPE_DSS => ::TypeDss, + _ => ::TypeUnknown, + }; + Some((data, kind)) + } + } + + /// 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(&mut self, + method_type: MethodType, + prefs: &str) -> Result<(), Error> { + let prefs = prefs.to_c_str(); + unsafe { + self.rc(raw::libssh2_session_method_pref(self.raw, + method_type as libc::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 libc::c_int); + ::opt_bytes(self, ptr).and_then(str::from_utf8) + } + } + + /// Get list of supported algorithms. + pub fn supported_algs(&self, method_type: MethodType) + -> Result<Vec<&'static str>, Error> { + let method_type = method_type as libc::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 int) { + ret.push(str::raw::c_str_to_static_slice(*ptr.offset(i))); + } + raw::libssh2_free(self.raw, ptr as *mut libc::c_void); + } + Ok(ret) + } + + /// Gain access to the underlying raw libssh2 session pointer. + pub fn raw(&self) -> *mut raw::LIBSSH2_SESSION { self.raw } + + fn rc(&self, rc: libc::c_int) -> Result<(), Error> { + if rc == 0 { + Ok(()) + } else { + Err(Error(rc as int)) + } + } +} + +impl Drop for Session { + fn drop(&mut self) { + unsafe { + assert_eq!(raw::libssh2_session_free(self.raw), 0); + } + } +} + +#[cfg(test)] +mod tests { + use Session; + + #[test] + fn smoke() { + let mut sess = Session::new().unwrap(); + assert!(sess.banner_bytes().is_none()); + sess.set_banner("foo").unwrap(); + assert!(sess.is_blocking()); + assert_eq!(sess.timeout(), 0); + sess.flag(::Compress, true).unwrap(); + assert!(sess.hostkey().is_none()); + sess.method_pref(::MethodKex, "diffie-hellman-group14-sha1").unwrap(); + assert!(sess.methods(::MethodKex).is_none()); + sess.set_blocking(true); + sess.set_timeout(0); + sess.supported_algs(::MethodKex).unwrap(); + sess.supported_algs(::MethodHostKey).unwrap(); + } +} |