diff options
-rw-r--r-- | libssh2-sys/lib.rs | 82 | ||||
-rw-r--r-- | src/agent.rs | 2 | ||||
-rw-r--r-- | src/knownhosts.rs | 234 | ||||
-rw-r--r-- | src/lib.rs | 28 | ||||
-rw-r--r-- | src/session.rs | 39 | ||||
-rw-r--r-- | tests/all.rs | 1 | ||||
-rw-r--r-- | tests/channel.rs | 7 | ||||
-rw-r--r-- | tests/knownhosts.rs | 40 | ||||
-rw-r--r-- | tests/session.rs | 1 |
9 files changed, 433 insertions, 1 deletions
diff --git a/libssh2-sys/lib.rs b/libssh2-sys/lib.rs index 44ea38b..a8f5c16 100644 --- a/libssh2-sys/lib.rs +++ b/libssh2-sys/lib.rs @@ -92,10 +92,31 @@ pub static LIBSSH2_ERROR_ENCRYPT: c_int = -44; pub static LIBSSH2_ERROR_BAD_SOCKET: c_int = -45; pub static LIBSSH2_ERROR_KNOWN_HOSTS: c_int = -46; +pub static LIBSSH2_HOSTKEY_HASH_MD5: c_int = 1; +pub static LIBSSH2_HOSTKEY_HASH_SHA1: c_int = 2; + +pub static LIBSSH2_KNOWNHOST_FILE_OPENSSH: c_int = 1; + +pub static LIBSSH2_KNOWNHOST_CHECK_MATCH: c_int = 0; +pub static LIBSSH2_KNOWNHOST_CHECK_MISMATCH: c_int = 1; +pub static LIBSSH2_KNOWNHOST_CHECK_NOTFOUND: c_int = 2; +pub static LIBSSH2_KNOWNHOST_CHECK_FAILURE: c_int = 3; + +pub static LIBSSH2_KNOWNHOST_TYPE_PLAIN: c_int = 1; +pub static LIBSSH2_KNOWNHOST_TYPE_SHA1: c_int = 2; +pub static LIBSSH2_KNOWNHOST_TYPE_CUSTOM: c_int = 3; +pub static LIBSSH2_KNOWNHOST_KEYENC_RAW: c_int = 1 << 16; +pub static LIBSSH2_KNOWNHOST_KEYENC_BASE64: c_int = 2 << 16; +pub static LIBSSH2_KNOWNHOST_KEY_RSA1: c_int = 1 << 18; +pub static LIBSSH2_KNOWNHOST_KEY_SSHRSA: c_int = 2 << 18; +pub static LIBSSH2_KNOWNHOST_KEY_SSHDSS: c_int = 3 << 18; +pub static LIBSSH2_KNOWNHOST_KEY_UNKNOWN: c_int = 7 << 18; + pub enum LIBSSH2_SESSION {} pub enum LIBSSH2_AGENT {} pub enum LIBSSH2_CHANNEL {} pub enum LIBSSH2_LISTENER {} +pub enum LIBSSH2_KNOWNHOSTS {} #[repr(C)] pub struct libssh2_agent_publickey { @@ -106,6 +127,15 @@ pub struct libssh2_agent_publickey { pub comment: *const c_char, } +#[repr(C)] +pub struct libssh2_knownhost { + pub magic: c_uint, + pub node: *mut c_void, + pub name: *mut c_char, + pub key: *mut c_char, + pub typemask: c_int, +} + 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) @@ -133,6 +163,8 @@ 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_hostkey_hash(session: *mut LIBSSH2_SESSION, + hash_type: c_int) -> *const c_char; // session pub fn libssh2_session_init_ex(alloc: Option<LIBSSH2_ALLOC_FUNC>, @@ -281,4 +313,54 @@ extern { pub fn libssh2_userauth_list(sess: *mut LIBSSH2_SESSION, username: *const c_char, username_len: c_uint) -> *const c_char; + + // knownhost + pub fn libssh2_knownhost_free(hosts: *mut LIBSSH2_KNOWNHOSTS); + pub fn libssh2_knownhost_addc(hosts: *mut LIBSSH2_KNOWNHOSTS, + host: *mut c_char, + salt: *mut c_char, + key: *mut c_char, + keylen: size_t, + comment: *const c_char, + commentlen: size_t, + typemask: c_int, + store: *mut *mut libssh2_knownhost) -> c_int; + pub fn libssh2_knownhost_check(hosts: *mut LIBSSH2_KNOWNHOSTS, + host: *const c_char, + key: *const c_char, + keylen: size_t, + typemask: c_int, + knownhost: *mut *mut libssh2_knownhost) + -> c_int; + pub fn libssh2_knownhost_checkp(hosts: *mut LIBSSH2_KNOWNHOSTS, + host: *const c_char, + port: c_int, + key: *const c_char, + keylen: size_t, + typemask: c_int, + knownhost: *mut *mut libssh2_knownhost) + -> c_int; + pub fn libssh2_knownhost_del(hosts: *mut LIBSSH2_KNOWNHOSTS, + entry: *mut libssh2_knownhost) -> c_int; + pub fn libssh2_knownhost_get(hosts: *mut LIBSSH2_KNOWNHOSTS, + store: *mut *mut libssh2_knownhost, + prev: *mut libssh2_knownhost) -> c_int; + pub fn libssh2_knownhost_readfile(hosts: *mut LIBSSH2_KNOWNHOSTS, + filename: *const c_char, + kind: c_int) -> c_int; + pub fn libssh2_knownhost_readline(hosts: *mut LIBSSH2_KNOWNHOSTS, + line: *const c_char, + len: size_t, + kind: c_int) -> c_int; + pub fn libssh2_knownhost_writefile(hosts: *mut LIBSSH2_KNOWNHOSTS, + filename: *const c_char, + kind: c_int) -> c_int; + pub fn libssh2_knownhost_writeline(hosts: *mut LIBSSH2_KNOWNHOSTS, + known: *mut libssh2_knownhost, + buffer: *mut c_char, + buflen: size_t, + outlen: *mut size_t, + kind: c_int) -> c_int; + pub fn libssh2_knownhost_init(sess: *mut LIBSSH2_SESSION) + -> *mut LIBSSH2_KNOWNHOSTS; } diff --git a/src/agent.rs b/src/agent.rs index e96f5c8..d3e4ed3 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -21,6 +21,7 @@ pub struct PublicKey<'a> { marker1: marker::NoSync, marker2: marker::NoSend, marker3: marker::ContravariantLifetime<'a>, + marker4: marker::NoCopy, } impl<'a> Agent<'a> { @@ -103,6 +104,7 @@ impl<'a> PublicKey<'a> { marker1: marker::NoSync, marker2: marker::NoSend, marker3: marker::ContravariantLifetime, + marker4: marker::NoCopy, } } diff --git a/src/knownhosts.rs b/src/knownhosts.rs new file mode 100644 index 0000000..efdd7ae --- /dev/null +++ b/src/knownhosts.rs @@ -0,0 +1,234 @@ +use std::kinds::marker; +use std::str; +use libc::{c_int, size_t}; + +use {raw, Session, Error, KnownHostFileKind, CheckResult}; + +pub struct KnownHosts<'a> { + raw: *mut raw::LIBSSH2_KNOWNHOSTS, + sess: &'a Session, + marker: marker::NoSync, +} + +pub struct Hosts<'a> { + prev: *mut raw::libssh2_knownhost, + hosts: &'a KnownHosts<'a>, +} + +pub struct Host<'a> { + raw: *mut raw::libssh2_knownhost, + marker1: marker::NoSync, + marker2: marker::NoSend, + marker3: marker::ContravariantLifetime<'a>, + marker4: marker::NoCopy, +} + +impl<'a> KnownHosts<'a> { + /// 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, + marker: marker::NoSync, + } + } + + /// 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) + -> Result<uint, Error> { + let file = file.to_c_str(); + let n = unsafe { + raw::libssh2_knownhost_readfile(self.raw, file.as_ptr(), + kind as c_int) + }; + if n < 0 { try!(self.sess.rc(n)) } + Ok(n as uint) + } + + /// Read a line as if it were from a known hosts file. + pub fn read_str(&mut self, s: &str, kind: KnownHostFileKind) + -> Result<(), Error> { + self.sess.rc(unsafe { + raw::libssh2_knownhost_readline(self.raw, + s.as_ptr() as *const _, + s.len() as size_t, + kind as c_int) + }) + } + + /// Writes all the known hosts to the specified file using the specified + /// file format. + pub fn write_file(&self, file: &Path, kind: KnownHostFileKind) + -> Result<(), Error> { + let file = file.to_c_str(); + let n = unsafe { + raw::libssh2_knownhost_writefile(self.raw, file.as_ptr(), + kind as c_int) + }; + self.sess.rc(n) + } + + /// Converts a single known host to a single line of output for storage, + /// using the 'type' output format. + pub fn write_string(&self, host: &Host, kind: KnownHostFileKind) + -> Result<String, Error> { + let mut v = Vec::with_capacity(128); + loop { + let mut outlen = 0; + unsafe { + let rc = raw::libssh2_knownhost_writeline(self.raw, + host.raw, + v.as_mut_ptr() + as *mut _, + v.capacity() as size_t, + &mut outlen, + kind as c_int); + if rc == raw::LIBSSH2_ERROR_BUFFER_TOO_SMALL { + v.reserve(outlen as uint); + } else { + try!(self.sess.rc(rc)); + v.set_len(outlen as uint); + break + } + } + } + Ok(String::from_utf8(v).unwrap()) + } + + /// Create an iterator over all of the known hosts in this structure. + pub fn iter(&self) -> Hosts { + Hosts { prev: 0 as *mut _, hosts: self } + } + + /// Delete a known host entry from the collection of known hosts. + pub fn remove(&self, host: Host) -> Result<(), Error> { + self.sess.rc(unsafe { + raw::libssh2_knownhost_del(self.raw, host.raw) + }) + } + + /// Checks a host and its associated key against the collection of known + /// hosts, and returns info back about the (partially) matched entry. + /// + /// The host name can be the IP numerical address of the host or the full + /// name. The key must be the raw data of the key. + pub fn check(&self, host: &str, key: &[u8]) -> CheckResult { + self.check_port_(host, -1, key) + } + + /// Same as `check`, but takes a port as well. + pub fn check_port(&self, host: &str, port: u16, key: &[u8]) -> CheckResult { + self.check_port_(host, port as int, key) + } + + fn check_port_(&self, host: &str, port: int, key: &[u8]) -> CheckResult { + let host = host.to_c_str(); + let flags = raw::LIBSSH2_KNOWNHOST_TYPE_PLAIN | + raw::LIBSSH2_KNOWNHOST_KEYENC_RAW; + unsafe { + let rc = raw::libssh2_knownhost_checkp(self.raw, + host.as_ptr(), + port as c_int, + key.as_ptr() as *const _, + key.len() as size_t, + flags, + 0 as *mut _); + match rc { + raw::LIBSSH2_KNOWNHOST_CHECK_MATCH => ::CheckMatch, + raw::LIBSSH2_KNOWNHOST_CHECK_MISMATCH => ::CheckMismatch, + raw::LIBSSH2_KNOWNHOST_CHECK_NOTFOUND => ::CheckNotFound, + _ => ::CheckFailure, + } + } + } + + /// Adds a known host to the collection of known hosts. + /// + /// The host is the host name in plain text. The host name can be the IP + /// numerical address of the host or the full name. If you want to add a key + /// for a specific port number for the given host, you must provide the host + /// name like '[host]:port' with the actual characters '[' and ']' enclosing + /// the host name and a colon separating the host part from the port number. + /// For example: "[host.example.com]:222". + /// + /// The key provided must be the raw key for the host. + pub fn add(&mut self, host: &str, key: &[u8], comment: &str, + fmt: ::KnownHostKeyFormat) + -> Result<(), Error> { + let host = host.to_c_str(); + let flags = raw::LIBSSH2_KNOWNHOST_TYPE_PLAIN | + raw::LIBSSH2_KNOWNHOST_KEYENC_RAW | + (fmt as c_int); + unsafe { + let rc = raw::libssh2_knownhost_addc(self.raw, + host.as_ptr() as *mut _, + 0 as *mut _, + key.as_ptr() as *mut _, + key.len() as size_t, + comment.as_ptr() as *const _, + comment.len() as size_t, + flags, + 0 as *mut _); + self.sess.rc(rc) + } + } +} + +#[unsafe_destructor] +impl<'a> Drop for KnownHosts<'a> { + fn drop(&mut self) { + unsafe { raw::libssh2_knownhost_free(self.raw) } + } +} + +impl<'a> Iterator<Result<Host<'a>, Error>> for Hosts<'a> { + fn next(&mut self) -> Option<Result<Host<'a>, Error>> { + unsafe { + let mut next = 0 as *mut _; + match raw::libssh2_knownhost_get(self.hosts.raw, + &mut next, + self.prev) { + 0 => { self.prev = next; Some(Ok(Host::from_raw(next))) } + 1 => None, + rc => Some(Err(self.hosts.sess.rc(rc).err().unwrap())), + } + } + } +} + +impl<'a> Host<'a> { + pub unsafe fn from_raw<'a>(raw: *mut raw::libssh2_knownhost) + -> Host<'a> { + Host { + raw: raw, + marker1: marker::NoSync, + marker2: marker::NoSend, + marker3: marker::ContravariantLifetime, + marker4: marker::NoCopy, + } + } + + /// This is `None` if no plain text host name exists. + pub fn name(&self) -> Option<&str> { + unsafe { + ::opt_bytes(self, (*self.raw).name as *const _) + .and_then(str::from_utf8) + } + } + + /// Returns the key in base64/printable format + pub fn key(&self) -> &str { + let bytes = unsafe { + ::opt_bytes(self, (*self.raw).key as *const _).unwrap() + }; + str::from_utf8(bytes).unwrap() + } + + /// Gain access to the underlying raw pointer + pub fn raw(&self) -> *mut raw::libssh2_knownhost { self.raw } +} @@ -61,12 +61,14 @@ use std::sync::{Once, ONCE_INIT}; pub use agent::{Agent, Identities, PublicKey}; pub use channel::{Channel, ExitSignal, ReadWindow, WriteWindow}; pub use error::Error; +pub use knownhosts::{KnownHosts, Hosts, Host}; pub use listener::Listener; pub use session::Session; mod agent; mod channel; mod error; +mod knownhosts; mod listener; mod session; @@ -152,3 +154,29 @@ pub enum MethodType { pub static FlushExtendedData: uint = -1; pub static FlushAll: uint = -2; pub static ExtendedDataStderr: uint = 1; + +pub enum HashType { + HashMd5 = raw::LIBSSH2_HOSTKEY_HASH_MD5 as int, + HashSha1 = raw:: LIBSSH2_HOSTKEY_HASH_SHA1 as int, +} + +pub enum KnownHostFileKind { + OpenSSH = raw::LIBSSH2_KNOWNHOST_FILE_OPENSSH as int, +} + +pub enum CheckResult { + /// Hosts and keys match + CheckMatch = raw::LIBSSH2_KNOWNHOST_CHECK_MATCH as int, + /// Host was found, but the keys didn't match! + CheckMismatch = raw::LIBSSH2_KNOWNHOST_CHECK_MISMATCH as int, + /// No host match was found + CheckNotFound = raw::LIBSSH2_KNOWNHOST_CHECK_NOTFOUND as int, + /// Something prevented the check to be made + CheckFailure = raw::LIBSSH2_KNOWNHOST_CHECK_FAILURE as int, +} + +pub enum KnownHostKeyFormat { + KeyRsa1 = raw::LIBSSH2_KNOWNHOST_KEY_RSA1 as int, + KeySshRsa = raw::LIBSSH2_KNOWNHOST_KEY_SSHRSA as int, + KeySshDss = raw::LIBSSH2_KNOWNHOST_KEY_SSHDSS as int, +} diff --git a/src/session.rs b/src/session.rs index 23e3643..07bcf93 100644 --- a/src/session.rs +++ b/src/session.rs @@ -5,7 +5,7 @@ use std::str; use libc::{c_uint, c_int, c_void, c_long}; use {raw, Error, DisconnectCode, ByApplication, SessionFlag, HostKeyType}; -use {MethodType, Agent, Channel, Listener}; +use {MethodType, Agent, Channel, Listener, HashType, KnownHosts}; pub struct Session { raw: *mut raw::LIBSSH2_SESSION, @@ -155,6 +155,28 @@ impl Session { } } + /// 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 { + ::HashMd5 => 16, + ::HashSha1 => 20, + }; + unsafe { + let ret = raw::libssh2_hostkey_hash(self.raw, hash as c_int); + if ret.is_null() { + None + } else { + Some(mem::transmute(stdraw::Slice { + data: ret as *const u8, + len: len, + })) + } + } + } + /// Set preferred key exchange method /// /// The preferences provided are a comma delimited list of preferred methods @@ -377,6 +399,21 @@ impl Session { Ok(ret as uint) } + /// Init a collection of known hosts for this session. + /// + /// Returns the handle to an internal representation of a known host + /// 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)) + } + } + } + /// Gain access to the underlying raw libssh2 session pointer. pub fn raw(&self) -> *mut raw::LIBSSH2_SESSION { self.raw } diff --git a/tests/all.rs b/tests/all.rs index fb3bc21..bd5f085 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -9,6 +9,7 @@ use native::io::net::TcpStream; mod agent; mod session; mod channel; +mod knownhosts; pub fn socket() -> TcpStream { let stream = TcpStream::connect(SocketAddr { diff --git a/tests/channel.rs b/tests/channel.rs index bce11af..2344cce 100644 --- a/tests/channel.rs +++ b/tests/channel.rs @@ -56,8 +56,15 @@ fn shell() { #[test] fn setenv() { let (_tcp, sess) = ::authed_session(); + { let mut channel = sess.channel_session().unwrap(); let _ = channel.setenv("FOO", "BAR"); + channel.close().unwrap(); + drop(channel); + } + sess.disconnect(None, "lol", None).unwrap(); + drop(sess); + drop(_tcp); } #[test] diff --git a/tests/knownhosts.rs b/tests/knownhosts.rs new file mode 100644 index 0000000..b1c1f57 --- /dev/null +++ b/tests/knownhosts.rs @@ -0,0 +1,40 @@ +use ssh2::{Session, OpenSSH}; + +#[test] +fn smoke() { + let sess = Session::new().unwrap(); + let hosts = sess.known_hosts().unwrap(); + assert_eq!(hosts.iter().count(), 0); +} + +#[test] +fn reading() { + let encoded = "\ +|1|VXwDpq2cv4j3QtmrGiY+HntJc+Q=|80E+wqnFDhkxBDxRBOIPJPAVE6Y= \ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9I\ +DSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVD\ +BfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eF\ +zLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKS\ +CZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2R\ +PW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi\ +/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== +"; + let sess = Session::new().unwrap(); + let mut hosts = sess.known_hosts().unwrap(); + hosts.read_str(encoded, OpenSSH).unwrap(); + + assert_eq!(hosts.iter().count(), 1); + let host = hosts.iter().next().unwrap().unwrap(); + assert_eq!(host.name(), None); + assert_eq!(host.key(), "\ +AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9I\ +DSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVD\ +BfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eF\ +zLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKS\ +CZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2R\ +PW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi\ +/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=="); + + assert_eq!(hosts.write_string(&host, OpenSSH).unwrap().as_slice(), encoded); + hosts.remove(host).unwrap(); +} diff --git a/tests/session.rs b/tests/session.rs index c93e514..4c8f907 100644 --- a/tests/session.rs +++ b/tests/session.rs @@ -39,6 +39,7 @@ fn smoke_handshake() { agent.userauth(user.as_slice(), &identity).unwrap(); } assert!(sess.authenticated()); + sess.host_key_hash(ssh2::HashMd5).unwrap(); } #[test] |