use libc::{c_int, size_t};
use parking_lot::{Mutex, MutexGuard};
use std::ffi::CString;
use std::path::Path;
use std::str;
use std::sync::Arc;
use util;
use {raw, CheckResult, Error, KnownHostFileKind, SessionInner};
pub struct KnownHosts {
raw: *mut raw::LIBSSH2_KNOWNHOSTS,
sess: Arc<Mutex<SessionInner>>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Host {
name: Option<String>,
key: String,
}
impl KnownHosts {
pub(crate) fn from_raw_opt(
raw: *mut raw::LIBSSH2_KNOWNHOSTS,
err: Option<Error>,
sess: &Arc<Mutex<SessionInner>>,
) -> Result<Self, Error> {
if raw.is_null() {
Err(err.unwrap_or_else(Error::unknown))
} else {
Ok(Self {
raw,
sess: Arc::clone(sess),
})
}
}
pub fn read_file(&mut self, file: &Path, kind: KnownHostFileKind) -> Result<u32, Error> {
let file = CString::new(util::path2bytes(file)?)?;
let sess = self.sess.lock();
let n = unsafe { raw::libssh2_knownhost_readfile(self.raw, file.as_ptr(), kind as c_int) };
if n < 0 {
sess.rc(n)?
}
Ok(n as u32)
}
pub fn read_str(&mut self, s: &str, kind: KnownHostFileKind) -> Result<(), Error> {
let sess = self.sess.lock();
sess.rc(unsafe {
raw::libssh2_knownhost_readline(
self.raw,
s.as_ptr() as *const _,
s.len() as size_t,
kind as c_int,
)
})
}
pub fn write_file(&self, file: &Path, kind: KnownHostFileKind) -> Result<(), Error> {
let file = CString::new(util::path2bytes(file)?)?;
let sess = self.sess.lock();
let n = unsafe { raw::libssh2_knownhost_writefile(self.raw, file.as_ptr(), kind as c_int) };
sess.rc(n)
}
pub fn write_string(&self, host: &Host, kind: KnownHostFileKind) -> Result<String, Error> {
let mut v = Vec::with_capacity(128);
let sess = self.sess.lock();
let raw_host = self.resolve_to_raw_host(&sess, host)?.ok_or_else(|| {
Error::new(
raw::LIBSSH2_ERROR_BAD_USE,
"Host is not in the set of known hosts",
)
})?;
loop {
let mut outlen = 0;
unsafe {
let rc = raw::libssh2_knownhost_writeline(
self.raw,
raw_host,
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 usize + 1);
} else {
sess.rc(rc)?;
v.set_len(outlen as usize);
break;
}
}
}
Ok(String::from_utf8(v).unwrap())
}
pub fn iter(&self) -> Result<Vec<Host>, Error> {
self.hosts()
}
pub fn hosts(&self) -> Result<Vec<Host>, Error> {
let mut next = 0 as *mut _;
let mut prev = 0 as *mut _;
let sess = self.sess.lock();
let mut hosts = vec![];
loop {
match unsafe { raw::libssh2_knownhost_get(self.raw, &mut next, prev) } {
0 => {
prev = next;
hosts.push(unsafe { Host::from_raw(next) });
}
1 => break,
rc => return Err(Error::from_session_error_raw(sess.raw, rc)),
}
}
Ok(hosts)
}
fn resolve_to_raw_host(
&self,
sess: &MutexGuard<SessionInner>,
host: &Host,
) -> Result<Option<*mut raw::libssh2_knownhost>, Error> {
let mut next = 0 as *mut _;
let mut prev = 0 as *mut _;
loop {
match unsafe { raw::libssh2_knownhost_get(self.raw, &mut next, prev) } {
0 => {
prev = next;
let current = unsafe { Host::from_raw(next) };
if current == *host {
return Ok(Some(next));
}
}
1 => break,
rc => return Err(Error::from_session_error_raw(sess.raw, rc)),
}
}
Ok(None)
}
pub fn remove(&self, host: &Host) -> Result<(), Error> {
let sess = self.sess.lock();
if let Some(raw_host) = self.resolve_to_raw_host(&sess, host)? {
return sess.rc(unsafe { raw::libssh2_knownhost_del(self.raw, raw_host) });
} else {
Ok(())
}
}
pub fn check(&self, host: &str, key: &[u8]) -> CheckResult {
self.check_port_(host, -1, key)
}
pub fn check_port(&self, host: &str, port: u16, key: &[u8]) -> CheckResult {
self.check_port_(host, port as i32, key)
}
fn check_port_(&self, host: &str, port: i32, key: &[u8]) -> CheckResult {
let host = CString::new(host).unwrap();
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 => CheckResult::Match,
raw::LIBSSH2_KNOWNHOST_CHECK_MISMATCH => CheckResult::Mismatch,
raw::LIBSSH2_KNOWNHOST_CHECK_NOTFOUND => CheckResult::NotFound,
_ => CheckResult::Failure,
}
}
}
pub fn add(
&mut self,
host: &str,
key: &[u8],
comment: &str,
fmt: ::KnownHostKeyFormat,
) -> Result<(), Error> {
let host = CString::new(host)?;
let flags =
raw::LIBSSH2_KNOWNHOST_TYPE_PLAIN | raw::LIBSSH2_KNOWNHOST_KEYENC_RAW | (fmt as c_int);
let sess = self.sess.lock();
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 _,
);
sess.rc(rc)
}
}
}
impl Drop for KnownHosts {
fn drop(&mut self) {
let _sess = self.sess.lock();
unsafe { raw::libssh2_knownhost_free(self.raw) }
}
}
impl Host {
pub fn name(&self) -> Option<&str> {
self.name.as_ref().map(String::as_str)
}
pub fn key(&self) -> &str {
&self.key
}
unsafe fn from_raw(raw: *mut raw::libssh2_knownhost) -> Self {
let name = ::opt_bytes(&raw, (*raw).name).and_then(|s| String::from_utf8(s.to_vec()).ok());
let key = ::opt_bytes(&raw, (*raw).key).unwrap();
let key = String::from_utf8(key.to_vec()).unwrap();
Self { name, key }
}
}