summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2014-09-17 21:23:41 -0700
committerAlex Crichton <alex@alexcrichton.com>2014-09-17 21:23:41 -0700
commitb8ac001faa09b1d692ecd651aa0ca121afb40b0e (patch)
treea3578b6c8a8c7826b6cf05a1120a96adc1ff330d
downloadssh2-rs-b8ac001faa09b1d692ecd651aa0ca121afb40b0e.zip
Initial commit
Bind a Session type (no I/O yet)
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml1
-rw-r--r--Cargo.toml14
-rw-r--r--src/agent.rs0
-rw-r--r--src/error.rs2
-rw-r--r--src/lib.rs93
-rw-r--r--src/raw.rs96
-rw-r--r--src/session.rs236
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();
+ }
+}