#![allow(clippy::wildcard_in_or_patterns)]
pub use libssh_rs_sys as sys;
use std::ffi::{CStr, CString};
use std::os::raw::{c_int, c_uint, c_ulong};
#[cfg(unix)]
use std::os::unix::io::RawFd as RawSocket;
#[cfg(windows)]
use std::os::windows::io::RawSocket;
use std::sync::Once;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
mod channel;
mod error;
mod sftp;
pub use crate::channel::*;
pub use crate::error::*;
pub use crate::sftp::*;
struct LibraryState {}
impl LibraryState {
pub fn new() -> Option<Self> {
let res = unsafe { sys::ssh_init() };
if res != sys::SSH_OK as i32 {
None
} else {
Some(Self {})
}
}
}
impl Drop for LibraryState {
fn drop(&mut self) {
unsafe { sys::ssh_finalize() };
}
}
static INIT: Once = Once::new();
static mut LIB: Option<LibraryState> = None;
fn initialize() -> SshResult<()> {
INIT.call_once(|| unsafe {
LIB = LibraryState::new();
});
if unsafe { LIB.is_none() } {
Err(Error::fatal("ssh_init failed"))
} else {
Ok(())
}
}
pub(crate) struct SessionHolder {
sess: sys::ssh_session,
callbacks: sys::ssh_callbacks_struct,
auth_callback: Option<Box<dyn FnMut(&str, bool, bool, Option<String>) -> SshResult<String>>>,
}
unsafe impl Send for SessionHolder {}
impl std::ops::Deref for SessionHolder {
type Target = sys::ssh_session;
fn deref(&self) -> &sys::ssh_session {
&self.sess
}
}
impl Drop for SessionHolder {
fn drop(&mut self) {
unsafe {
sys::ssh_free(self.sess);
}
}
}
impl SessionHolder {
pub fn is_blocking(&self) -> bool {
unsafe { sys::ssh_is_blocking(self.sess) != 0 }
}
fn last_error(&self) -> Option<Error> {
let code = unsafe { sys::ssh_get_error_code(self.sess as _) } as sys::ssh_error_types_e;
if code == sys::ssh_error_types_e_SSH_NO_ERROR {
return None;
}
let reason = unsafe { sys::ssh_get_error(self.sess as _) };
let reason = if reason.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(reason) }
.to_string_lossy()
.to_string()
};
if code == sys::ssh_error_types_e_SSH_REQUEST_DENIED {
Some(Error::RequestDenied(reason))
} else {
Some(Error::Fatal(reason))
}
}
fn basic_status(&self, res: i32, what: &str) -> SshResult<()> {
if res == sys::SSH_OK as i32 {
Ok(())
} else if res == sys::SSH_AGAIN {
Err(Error::TryAgain)
} else if let Some(err) = self.last_error() {
Err(err)
} else {
Err(Error::fatal(what))
}
}
fn blocking_flush(&self, timeout: Option<Duration>) -> SshResult<()> {
let timeout = match timeout {
Some(t) => t.as_millis() as c_int,
None => -1,
};
let res = unsafe { sys::ssh_blocking_flush(self.sess, timeout) };
self.basic_status(res, "blocking_flush")
}
fn auth_result(&self, res: sys::ssh_auth_e, what: &str) -> SshResult<AuthStatus> {
match res {
sys::ssh_auth_e_SSH_AUTH_SUCCESS => Ok(AuthStatus::Success),
sys::ssh_auth_e_SSH_AUTH_DENIED => Ok(AuthStatus::Denied),
sys::ssh_auth_e_SSH_AUTH_PARTIAL => Ok(AuthStatus::Partial),
sys::ssh_auth_e_SSH_AUTH_INFO => Ok(AuthStatus::Info),
sys::ssh_auth_e_SSH_AUTH_AGAIN => Ok(AuthStatus::Again),
sys::ssh_auth_e_SSH_AUTH_ERROR | _ => {
if let Some(err) = self.last_error() {
Err(err)
} else {
Err(Error::fatal(what))
}
}
}
}
}
pub struct Session {
sess: Arc<Mutex<SessionHolder>>,
}
impl Session {
pub fn new() -> SshResult<Self> {
initialize()?;
let sess = unsafe { sys::ssh_new() };
if sess.is_null() {
Err(Error::fatal("ssh_new failed"))
} else {
let callbacks = sys::ssh_callbacks_struct {
size: std::mem::size_of::<sys::ssh_callbacks_struct>(),
userdata: std::ptr::null_mut(),
auth_function: None,
log_function: None,
connect_status_function: None,
global_request_function: None,
channel_open_request_x11_function: None,
channel_open_request_auth_agent_function: None,
};
let sess = Arc::new(Mutex::new(SessionHolder {
sess,
callbacks,
auth_callback: None,
}));
{
let mut sess = sess.lock().unwrap();
let ptr: *mut SessionHolder = &mut *sess;
sess.callbacks.userdata = ptr as _;
unsafe {
sys::ssh_set_callbacks(**sess, &mut sess.callbacks);
}
}
Ok(Self { sess })
}
}
unsafe extern "C" fn bridge_auth_callback(
prompt: *const ::std::os::raw::c_char,
buf: *mut ::std::os::raw::c_char,
len: usize,
echo: ::std::os::raw::c_int,
verify: ::std::os::raw::c_int,
userdata: *mut ::std::os::raw::c_void,
) -> ::std::os::raw::c_int {
let prompt = CStr::from_ptr(prompt).to_string_lossy().to_string();
let echo = if echo == 0 { false } else { true };
let verify = if verify == 0 { false } else { true };
let result = std::panic::catch_unwind(|| {
let sess: &mut SessionHolder = &mut *(userdata as *mut SessionHolder);
let identity = {
let mut value = std::ptr::null_mut();
sys::ssh_userauth_publickey_auto_get_current_identity(**sess, &mut value);
if value.is_null() {
None
} else {
let s = CStr::from_ptr(value).to_string_lossy().to_string();
sys::ssh_string_free_char(value);
Some(s)
}
};
let cb = sess.auth_callback.as_mut().unwrap();
let response = (cb)(&prompt, echo, verify, identity)?;
if response.len() > len {
return Err(Error::Fatal(format!(
"passphrase is larger than buffer allows {} vs available {}",
response.len(),
len
)));
}
let len = response.len().min(len);
let buf = std::slice::from_raw_parts_mut(buf as *mut u8, len);
buf.copy_from_slice(response.as_bytes());
Ok(())
});
match result {
Err(err) => {
eprintln!("Error in auth callback: {:?}", err);
sys::SSH_ERROR
}
Ok(Err(err)) => {
eprintln!("Error in auth callback: {:#}", err);
sys::SSH_ERROR
}
Ok(Ok(())) => sys::SSH_OK as c_int,
}
}
pub fn set_auth_callback<F>(&self, callback: F)
where
F: FnMut(&str, bool, bool, Option<String>) -> SshResult<String> + 'static,
{
let mut sess = self.lock_session();
sess.auth_callback.replace(Box::new(callback));
sess.callbacks.auth_function = Some(Self::bridge_auth_callback);
}
pub fn new_channel(&self) -> SshResult<Channel> {
let sess = self.lock_session();
let chan = unsafe { sys::ssh_channel_new(**sess) };
if chan.is_null() {
if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("ssh_channel_new failed"))
}
} else {
Ok(Channel {
sess: Arc::clone(&self.sess),
chan_inner: chan,
})
}
}
fn lock_session(&self) -> MutexGuard<SessionHolder> {
self.sess.lock().unwrap()
}
pub fn blocking_flush(&self, timeout: Option<Duration>) -> SshResult<()> {
let sess = self.lock_session();
sess.blocking_flush(timeout)
}
pub fn disconnect(&self) {
let sess = self.lock_session();
unsafe { sys::ssh_disconnect(**sess) };
}
pub fn connect(&self) -> SshResult<()> {
let sess = self.lock_session();
let res = unsafe { sys::ssh_connect(**sess) };
sess.basic_status(res, "ssh_connect failed")
}
pub fn is_known_server(&self) -> SshResult<KnownHosts> {
let sess = self.lock_session();
match unsafe { sys::ssh_session_is_known_server(**sess) } {
sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_NOT_FOUND => Ok(KnownHosts::NotFound),
sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_UNKNOWN => Ok(KnownHosts::Unknown),
sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_OK => Ok(KnownHosts::Ok),
sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_CHANGED => Ok(KnownHosts::Changed),
sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_OTHER => Ok(KnownHosts::Other),
sys::ssh_known_hosts_e_SSH_KNOWN_HOSTS_ERROR | _ => {
if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("unknown error in ssh_session_is_known_server"))
}
}
}
}
pub fn update_known_hosts_file(&self) -> SshResult<()> {
let sess = self.lock_session();
let res = unsafe { sys::ssh_session_update_known_hosts(**sess) };
if res == sys::SSH_OK as i32 {
Ok(())
} else if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("error updating known hosts file"))
}
}
pub fn options_parse_config(&self, file_name: Option<&str>) -> SshResult<()> {
let sess = self.lock_session();
let file_name = opt_str_to_cstring(file_name);
let res = unsafe { sys::ssh_options_parse_config(**sess, opt_cstring_to_cstr(&file_name)) };
if res == 0 {
Ok(())
} else if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::Fatal(format!(
"error parsing config file: {:?}",
file_name
)))
}
}
pub fn get_issue_banner(&self) -> SshResult<String> {
let sess = self.lock_session();
let banner = unsafe { sys::ssh_get_issue_banner(**sess) };
if banner.is_null() {
if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("failed to get issue banner"))
}
} else {
let banner_text = unsafe { CStr::from_ptr(banner) }
.to_string_lossy()
.to_string();
unsafe { sys::ssh_string_free_char(banner) };
Ok(banner_text)
}
}
pub fn get_server_banner(&self) -> SshResult<String> {
let sess = self.lock_session();
let banner = unsafe { sys::ssh_get_serverbanner(**sess) };
if banner.is_null() {
if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("failed to get server banner"))
}
} else {
let banner_text = unsafe { CStr::from_ptr(banner) }
.to_string_lossy()
.to_string();
Ok(banner_text)
}
}
pub fn get_user_name(&self) -> SshResult<String> {
let sess = self.lock_session();
let mut name = std::ptr::null_mut();
let res = unsafe {
sys::ssh_options_get(**sess, sys::ssh_options_e::SSH_OPTIONS_USER, &mut name)
};
if res != sys::SSH_OK as i32 || name.is_null() {
if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("error getting user name"))
}
} else {
let user_name = unsafe { CStr::from_ptr(name) }
.to_string_lossy()
.to_string();
unsafe { sys::ssh_string_free_char(name) };
Ok(user_name)
}
}
pub fn set_option(&self, option: SshOption) -> SshResult<()> {
let sess = self.lock_session();
let res = match option {
SshOption::LogLevel(level) => unsafe {
let level = match level {
LogLevel::NoLogging => sys::SSH_LOG_NOLOG,
LogLevel::Warning => sys::SSH_LOG_WARNING,
LogLevel::Protocol => sys::SSH_LOG_PROTOCOL,
LogLevel::Packet => sys::SSH_LOG_PACKET,
LogLevel::Functions => sys::SSH_LOG_FUNCTIONS,
} as u32 as c_int;
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_LOG_VERBOSITY,
&level as *const _ as _,
)
},
SshOption::Hostname(name) => unsafe {
let name = CString::new(name)?;
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_HOST,
name.as_ptr() as _,
)
},
SshOption::BindAddress(name) => unsafe {
let name = CString::new(name)?;
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_BINDADDR,
name.as_ptr() as _,
)
},
SshOption::PublicKeyAcceptedTypes(name) => unsafe {
let name = CString::new(name)?;
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES,
name.as_ptr() as _,
)
},
SshOption::AddIdentity(name) => unsafe {
let name = CString::new(name)?;
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_ADD_IDENTITY,
name.as_ptr() as _,
)
},
SshOption::IdentityAgent(name) => unsafe {
let name = opt_string_to_cstring(name);
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_IDENTITY_AGENT,
opt_cstring_to_cstr(&name) as _,
)
},
SshOption::User(name) => unsafe {
let name = opt_string_to_cstring(name);
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_USER,
opt_cstring_to_cstr(&name) as _,
)
},
SshOption::SshDir(name) => unsafe {
let name = opt_string_to_cstring(name);
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_SSH_DIR,
opt_cstring_to_cstr(&name) as _,
)
},
SshOption::KnownHosts(known_hosts) => unsafe {
let known_hosts = opt_string_to_cstring(known_hosts);
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_KNOWNHOSTS,
opt_cstring_to_cstr(&known_hosts) as _,
)
},
SshOption::ProxyCommand(cmd) => unsafe {
let cmd = opt_string_to_cstring(cmd);
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_PROXYCOMMAND,
opt_cstring_to_cstr(&cmd) as _,
)
},
SshOption::Port(port) => {
let port: c_uint = port.into();
unsafe {
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_PORT,
&port as *const _ as _,
)
}
}
SshOption::Socket(socket) => unsafe {
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_FD,
&socket as *const _ as _,
)
},
SshOption::Timeout(duration) => unsafe {
let micros: c_ulong = duration.as_micros() as c_ulong;
sys::ssh_options_set(
**sess,
sys::ssh_options_e::SSH_OPTIONS_TIMEOUT_USEC,
µs as *const _ as _,
)
},
};
if res == 0 {
Ok(())
} else if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("failed to set option"))
}
}
pub fn get_server_public_key(&self) -> SshResult<SshKey> {
let sess = self.lock_session();
let mut key = std::ptr::null_mut();
let res = unsafe { sys::ssh_get_server_publickey(**sess, &mut key) };
if res == sys::SSH_OK as i32 && !key.is_null() {
Ok(SshKey { key })
} else if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("failed to get server public key"))
}
}
pub fn userauth_agent(&self, username: Option<&str>) -> SshResult<AuthStatus> {
let sess = self.lock_session();
let username = opt_str_to_cstring(username);
let res = unsafe { sys::ssh_userauth_agent(**sess, opt_cstring_to_cstr(&username)) };
sess.auth_result(res, "authentication error")
}
pub fn userauth_public_key_auto(
&self,
username: Option<&str>,
password: Option<&str>,
) -> SshResult<AuthStatus> {
let sess = self.lock_session();
let username = opt_str_to_cstring(username);
let password = opt_str_to_cstring(password);
let res = unsafe {
sys::ssh_userauth_publickey_auto(
**sess,
opt_cstring_to_cstr(&username),
opt_cstring_to_cstr(&password),
)
};
sess.auth_result(res, "authentication error")
}
pub fn userauth_none(&self, username: Option<&str>) -> SshResult<AuthStatus> {
let sess = self.lock_session();
let username = opt_str_to_cstring(username);
let res = unsafe { sys::ssh_userauth_none(**sess, opt_cstring_to_cstr(&username)) };
sess.auth_result(res, "authentication error")
}
pub fn userauth_list(&self, username: Option<&str>) -> SshResult<AuthMethods> {
let sess = self.lock_session();
let username = opt_str_to_cstring(username);
Ok(unsafe {
AuthMethods::from_bits_unchecked(sys::ssh_userauth_list(
**sess,
opt_cstring_to_cstr(&username),
) as u32)
})
}
pub fn userauth_keyboard_interactive_info(&self) -> SshResult<InteractiveAuthInfo> {
let sess = self.lock_session();
let name = unsafe { sys::ssh_userauth_kbdint_getname(**sess) };
let name = unsafe { CStr::from_ptr(name) }
.to_string_lossy()
.to_string();
let instruction = unsafe { sys::ssh_userauth_kbdint_getinstruction(**sess) };
let instruction = unsafe { CStr::from_ptr(instruction) }
.to_string_lossy()
.to_string();
let n_prompts = unsafe { sys::ssh_userauth_kbdint_getnprompts(**sess) };
assert!(n_prompts >= 0);
let n_prompts = n_prompts as u32;
let mut prompts = vec![];
for i in 0..n_prompts {
let mut echo = 0;
let prompt = unsafe { sys::ssh_userauth_kbdint_getprompt(**sess, i, &mut echo) };
prompts.push(InteractiveAuthPrompt {
prompt: unsafe { CStr::from_ptr(prompt) }
.to_string_lossy()
.to_string(),
echo: echo != 0,
});
}
Ok(InteractiveAuthInfo {
name,
instruction,
prompts,
})
}
pub fn userauth_keyboard_interactive_set_answers(&self, answers: &[String]) -> SshResult<()> {
let sess = self.lock_session();
for (idx, answer) in answers.iter().enumerate() {
let answer = CString::new(answer.as_bytes())?;
let res =
unsafe { sys::ssh_userauth_kbdint_setanswer(**sess, idx as u32, answer.as_ptr()) };
if res != 0 {
if let Some(err) = sess.last_error() {
return Err(err);
}
return Err(Error::fatal("error setting answer"));
}
}
Ok(())
}
pub fn userauth_keyboard_interactive(
&self,
username: Option<&str>,
sub_methods: Option<&str>,
) -> SshResult<AuthStatus> {
let sess = self.lock_session();
let username = opt_str_to_cstring(username);
let sub_methods = opt_str_to_cstring(sub_methods);
let res = unsafe {
sys::ssh_userauth_kbdint(
**sess,
opt_cstring_to_cstr(&username),
opt_cstring_to_cstr(&sub_methods),
)
};
sess.auth_result(res, "authentication error")
}
pub fn userauth_password(
&self,
username: Option<&str>,
password: Option<&str>,
) -> SshResult<AuthStatus> {
let sess = self.lock_session();
let username = opt_str_to_cstring(username);
let password = opt_str_to_cstring(password);
let res = unsafe {
sys::ssh_userauth_password(
**sess,
opt_cstring_to_cstr(&username),
opt_cstring_to_cstr(&password),
)
};
sess.auth_result(res, "authentication error")
}
pub fn listen_forward(&self, bind_address: Option<&str>, port: u16) -> SshResult<u16> {
let sess = self.lock_session();
let bind_address = opt_str_to_cstring(bind_address);
let mut bound_port = 0;
let res = unsafe {
sys::ssh_channel_listen_forward(
**sess,
opt_cstring_to_cstr(&bind_address),
port as i32,
&mut bound_port,
)
};
if res == sys::SSH_OK as i32 {
Ok(bound_port as u16)
} else if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("error in ssh_channel_listen_forward"))
}
}
pub fn accept_forward(&self, timeout: Duration) -> SshResult<(u16, Channel)> {
let mut port = 0;
let sess = self.lock_session();
let chan =
unsafe { sys::ssh_channel_accept_forward(**sess, timeout.as_millis() as _, &mut port) };
if chan.is_null() {
if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::TryAgain)
}
} else {
let channel = Channel {
sess: Arc::clone(&self.sess),
chan_inner: chan,
};
Ok((port as u16, channel))
}
}
pub fn get_poll_state(&self) -> (bool, bool) {
let state = unsafe { sys::ssh_get_poll_flags(**self.lock_session()) };
let read_pending = (state & sys::SSH_READ_PENDING as i32) != 0;
let write_pending = (state & sys::SSH_WRITE_PENDING as i32) != 0;
(read_pending, write_pending)
}
pub fn is_blocking(&self) -> bool {
self.lock_session().is_blocking()
}
pub fn set_blocking(&self, blocking: bool) {
unsafe { sys::ssh_set_blocking(**self.lock_session(), if blocking { 1 } else { 0 }) }
}
pub fn is_connected(&self) -> bool {
unsafe { sys::ssh_is_connected(**self.lock_session()) != 0 }
}
pub fn sftp(&self) -> SshResult<Sftp> {
let sftp = {
let sess = self.lock_session();
let sftp = unsafe { sys::sftp_new(**sess) };
if sftp.is_null() {
return if let Some(err) = sess.last_error() {
Err(err)
} else {
Err(Error::fatal("failed to allocate sftp session"))
};
}
Sftp {
sess: Arc::clone(&self.sess),
sftp_inner: sftp,
}
};
sftp.init()?;
Ok(sftp)
}
}
#[cfg(unix)]
impl std::os::unix::io::AsRawFd for Session {
fn as_raw_fd(&self) -> RawSocket {
unsafe { sys::ssh_get_fd(**self.lock_session()) }
}
}
#[cfg(windows)]
impl std::os::windows::io::AsRawSocket for Session {
fn as_raw_socket(&self) -> RawSocket {
unsafe { sys::ssh_get_fd(**self.lock_session()) as RawSocket }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthStatus {
Success,
Denied,
Partial,
Info,
Again,
}
bitflags::bitflags! {
pub struct AuthMethods : u32 {
const NONE = sys::SSH_AUTH_METHOD_NONE;
const PASSWORD = sys::SSH_AUTH_METHOD_PASSWORD;
const PUBLIC_KEY = sys::SSH_AUTH_METHOD_PUBLICKEY;
const HOST_BASED = sys::SSH_AUTH_METHOD_HOSTBASED;
const INTERACTIVE = sys::SSH_AUTH_METHOD_INTERACTIVE;
const GSSAPI_MIC = sys::SSH_AUTH_METHOD_GSSAPI_MIC;
}
}
pub struct SshKey {
key: sys::ssh_key,
}
impl Drop for SshKey {
fn drop(&mut self) {
unsafe { sys::ssh_key_free(self.key) }
}
}
impl SshKey {
pub fn get_public_key_hash(&self, hash_type: PublicKeyHashType) -> SshResult<Vec<u8>> {
let mut bytes = std::ptr::null_mut();
let mut len = 0;
let res = unsafe {
sys::ssh_get_publickey_hash(
self.key,
match hash_type {
PublicKeyHashType::Sha1 => {
sys::ssh_publickey_hash_type::SSH_PUBLICKEY_HASH_SHA1
}
PublicKeyHashType::Md5 => sys::ssh_publickey_hash_type::SSH_PUBLICKEY_HASH_MD5,
PublicKeyHashType::Sha256 => {
sys::ssh_publickey_hash_type::SSH_PUBLICKEY_HASH_SHA256
}
},
&mut bytes,
&mut len,
)
};
if res != 0 || bytes.is_null() {
Err(Error::fatal("failed to get public key hash"))
} else {
let data = unsafe { std::slice::from_raw_parts(bytes, len).to_vec() };
unsafe {
sys::ssh_clean_pubkey_hash(&mut bytes);
}
Ok(data)
}
}
pub fn get_public_key_hash_hexa(&self, hash_type: PublicKeyHashType) -> SshResult<String> {
let bytes = self.get_public_key_hash(hash_type)?;
let hexa = unsafe { sys::ssh_get_hexa(bytes.as_ptr(), bytes.len()) };
if hexa.is_null() {
Err(Error::fatal(
"failed to allocate bytes for hexa representation",
))
} else {
let res = unsafe { CStr::from_ptr(hexa) }
.to_string_lossy()
.to_string();
unsafe { sys::ssh_string_free_char(hexa) };
Ok(res)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
NoLogging,
Warning,
Protocol,
Packet,
Functions,
}
#[derive(Debug)]
pub enum SshOption {
Hostname(String),
Port(u16),
LogLevel(LogLevel),
Socket(RawSocket),
BindAddress(String),
User(Option<String>),
SshDir(Option<String>),
KnownHosts(Option<String>),
ProxyCommand(Option<String>),
AddIdentity(String),
Timeout(Duration),
IdentityAgent(Option<String>),
PublicKeyAcceptedTypes(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KnownHosts {
NotFound,
Unknown,
Ok,
Changed,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PublicKeyHashType {
Sha1,
Md5,
Sha256,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InteractiveAuthPrompt {
pub prompt: String,
pub echo: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InteractiveAuthInfo {
pub instruction: String,
pub name: String,
pub prompts: Vec<InteractiveAuthPrompt>,
}
pub fn get_input(
prompt: &str,
default_value: Option<&str>,
echo: bool,
verify: bool,
) -> Option<String> {
const BUF_LEN: usize = 128;
let mut buf = [0u8; BUF_LEN];
if let Some(def) = default_value {
let def = def.as_bytes();
let len = buf.len().min(def.len());
buf[0..len].copy_from_slice(&def[0..len]);
}
let prompt = CString::new(prompt).ok()?;
let res = unsafe {
sys::ssh_getpass(
prompt.as_ptr(),
buf.as_mut_ptr() as *mut _,
buf.len(),
if echo { 1 } else { 0 },
if verify { 1 } else { 0 },
)
};
if res == 0 {
Some(
unsafe { CStr::from_ptr(buf.as_ptr() as *const _) }
.to_string_lossy()
.to_string(),
)
} else {
None
}
}
fn opt_str_to_cstring(s: Option<&str>) -> Option<CString> {
s.and_then(|s| CString::new(s).ok())
}
fn opt_string_to_cstring(s: Option<String>) -> Option<CString> {
s.and_then(|s| CString::new(s).ok())
}
fn opt_cstring_to_cstr(s: &Option<CString>) -> *const ::std::os::raw::c_char {
match s {
Some(s) => s.as_ptr(),
None => std::ptr::null(),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn init() {
let sess = Session::new().unwrap();
assert!(!sess.is_connected());
assert_eq!(sess.connect(), Err(Error::fatal("Hostname required")));
}
}