#[cfg(unix)]
use libc::size_t;
use libc::{self, c_char, c_int, c_long, c_uint, c_void};
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
use std::borrow::Cow;
use std::ffi::CString;
use std::mem;
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(windows)]
use std::os::windows::io::{AsRawSocket, RawSocket};
use std::path::Path;
use std::slice;
use std::str;
use std::sync::Arc;
use util;
use {raw, ByApplication, DisconnectCode, Error, ErrorCode, HostKeyType};
use {Agent, Channel, HashType, KnownHosts, Listener, MethodType, Sftp};
bitflags! {
pub struct TraceFlags: c_int {
const AUTH = raw::LIBSSH2_TRACE_AUTH;
const CONN = raw::LIBSSH2_TRACE_CONN;
const ERROR = raw::LIBSSH2_TRACE_ERROR;
const KEX = raw::LIBSSH2_TRACE_KEX;
const PUBLICKEY = raw::LIBSSH2_TRACE_PUBLICKEY;
const SCP = raw::LIBSSH2_TRACE_SCP;
const SFTP = raw::LIBSSH2_TRACE_SFTP;
const SOCKET = raw::LIBSSH2_TRACE_SOCKET;
const TRANS = raw::LIBSSH2_TRACE_TRANS;
}
}
pub trait KeyboardInteractivePrompt {
fn prompt<'a>(
&mut self,
username: &str,
instructions: &str,
prompts: &[Prompt<'a>],
) -> Vec<String>;
}
#[derive(Debug)]
pub struct Prompt<'a> {
pub text: Cow<'a, str>,
pub echo: bool,
}
unsafe fn with_abstract<R, F: FnOnce() -> R>(
sess: *mut raw::LIBSSH2_SESSION,
new_value: *mut c_void,
f: F,
) -> R {
let abstrakt = raw::libssh2_session_abstract(sess);
let old_value = *abstrakt;
*abstrakt = new_value;
let res = f();
*abstrakt = old_value;
res
}
pub(crate) struct SessionInner {
pub(crate) raw: *mut raw::LIBSSH2_SESSION,
#[cfg(unix)]
tcp: Option<Box<dyn AsRawFd>>,
#[cfg(windows)]
tcp: Option<Box<dyn AsRawSocket>>,
}
unsafe impl Send for SessionInner {}
#[derive(Clone)]
pub struct Session {
inner: Arc<Mutex<SessionInner>>,
}
pub struct ScpFileStat {
stat: libc::stat,
}
#[derive(Debug, PartialEq)]
pub enum BlockDirections {
None,
Inbound,
Outbound,
Both,
}
impl Session {
pub fn new() -> Result<Session, Error> {
::init();
unsafe {
let ret = raw::libssh2_session_init_ex(None, None, None, 0 as *mut _);
if ret.is_null() {
Err(Error::unknown())
} else {
Ok(Session {
inner: Arc::new(Mutex::new(SessionInner {
raw: ret,
tcp: None,
})),
})
}
}
}
#[doc(hidden)]
pub fn raw(&self) -> MappedMutexGuard<raw::LIBSSH2_SESSION> {
let inner = self.inner();
MutexGuard::map(inner, |inner| unsafe { &mut *inner.raw })
}
pub fn set_banner(&self, banner: &str) -> Result<(), Error> {
let banner = CString::new(banner)?;
let inner = self.inner();
unsafe { inner.rc(raw::libssh2_session_banner_set(inner.raw, banner.as_ptr())) }
}
pub fn set_allow_sigpipe(&self, block: bool) {
let inner = self.inner();
let res = unsafe {
inner.rc(raw::libssh2_session_flag(
inner.raw,
raw::LIBSSH2_FLAG_SIGPIPE as c_int,
block as c_int,
))
};
res.unwrap();
}
pub fn set_compress(&self, compress: bool) {
let inner = self.inner();
let res = unsafe {
inner.rc(raw::libssh2_session_flag(
inner.raw,
raw::LIBSSH2_FLAG_COMPRESS as c_int,
compress as c_int,
))
};
res.unwrap();
}
pub fn set_blocking(&self, blocking: bool) {
self.inner().set_blocking(blocking);
}
pub fn is_blocking(&self) -> bool {
self.inner().is_blocking()
}
pub fn set_timeout(&self, timeout_ms: u32) {
let timeout_ms = timeout_ms as c_long;
let inner = self.inner();
unsafe { raw::libssh2_session_set_timeout(inner.raw, timeout_ms) }
}
pub fn timeout(&self) -> u32 {
let inner = self.inner();
unsafe { raw::libssh2_session_get_timeout(inner.raw) as u32 }
}
pub fn handshake(&mut self) -> Result<(), Error> {
#[cfg(windows)]
unsafe fn handshake(
raw: *mut raw::LIBSSH2_SESSION,
stream: &dyn AsRawSocket,
) -> libc::c_int {
raw::libssh2_session_handshake(raw, stream.as_raw_socket())
}
#[cfg(unix)]
unsafe fn handshake(raw: *mut raw::LIBSSH2_SESSION, stream: &dyn AsRawFd) -> libc::c_int {
raw::libssh2_session_handshake(raw, stream.as_raw_fd())
}
let inner = self.inner();
unsafe {
let stream = inner.tcp.as_ref().ok_or_else(|| {
Error::new(
ErrorCode::Session(raw::LIBSSH2_ERROR_BAD_SOCKET),
"use set_tcp_stream() to associate with a TcpStream",
)
})?;
inner.rc(handshake(inner.raw, stream.as_ref()))
}
}
#[cfg(unix)]
pub fn set_tcp_stream<S: 'static + AsRawFd>(&mut self, stream: S) {
let mut inner = self.inner();
let _ = inner.tcp.replace(Box::new(stream));
}
#[cfg(windows)]
pub fn set_tcp_stream<S: 'static + AsRawSocket>(&mut self, stream: S) {
let mut inner = self.inner();
let _ = inner.tcp.replace(Box::new(stream));
}
pub fn userauth_password(&self, username: &str, password: &str) -> Result<(), Error> {
let inner = self.inner();
inner.rc(unsafe {
raw::libssh2_userauth_password_ex(
inner.raw,
username.as_ptr() as *const _,
username.len() as c_uint,
password.as_ptr() as *const _,
password.len() as c_uint,
None,
)
})
}
pub fn userauth_keyboard_interactive<P: KeyboardInteractivePrompt>(
&self,
username: &str,
prompter: &mut P,
) -> Result<(), Error> {
extern "C" fn prompt<P: KeyboardInteractivePrompt>(
username: *const c_char,
username_len: c_int,
instruction: *const c_char,
instruction_len: c_int,
num_prompts: c_int,
prompts: *const raw::LIBSSH2_USERAUTH_KBDINT_PROMPT,
responses: *mut raw::LIBSSH2_USERAUTH_KBDINT_RESPONSE,
abstrakt: *mut *mut c_void,
) {
use std::panic::{catch_unwind, AssertUnwindSafe};
let _ = catch_unwind(AssertUnwindSafe(|| {
let prompter = unsafe { &mut **(abstrakt as *mut *mut P) };
let username =
unsafe { slice::from_raw_parts(username as *const u8, username_len as usize) };
let username = String::from_utf8_lossy(username);
let instruction = unsafe {
slice::from_raw_parts(instruction as *const u8, instruction_len as usize)
};
let instruction = String::from_utf8_lossy(instruction);
let prompts = unsafe { slice::from_raw_parts(prompts, num_prompts as usize) };
let responses =
unsafe { slice::from_raw_parts_mut(responses, num_prompts as usize) };
let prompts: Vec<Prompt> = prompts
.iter()
.map(|item| {
let data = unsafe {
slice::from_raw_parts(item.text as *const u8, item.length as usize)
};
Prompt {
text: String::from_utf8_lossy(data),
echo: item.echo != 0,
}
})
.collect();
fn strdup_string(s: &str) -> *mut c_char {
let len = s.len();
let ptr = unsafe { libc::malloc(len + 1) as *mut c_char };
if !ptr.is_null() {
unsafe {
::std::ptr::copy_nonoverlapping(
s.as_bytes().as_ptr() as *const c_char,
ptr,
len,
);
*ptr.offset(len as isize) = 0;
}
}
ptr
}
for (i, response) in (*prompter)
.prompt(&username, &instruction, &prompts)
.into_iter()
.take(prompts.len())
.enumerate()
{
let ptr = strdup_string(&response);
if !ptr.is_null() {
responses[i].length = response.len() as c_uint;
} else {
responses[i].length = 0;
}
responses[i].text = ptr;
}
}));
}
let inner = self.inner();
unsafe {
with_abstract(inner.raw, prompter as *mut P as *mut c_void, || {
inner.rc(raw::libssh2_userauth_keyboard_interactive_ex(
inner.raw,
username.as_ptr() as *const _,
username.len() as c_uint,
Some(prompt::<P>),
))
})
}
}
pub fn userauth_agent(&self, username: &str) -> Result<(), Error> {
let mut agent = self.agent()?;
agent.connect()?;
agent.list_identities()?;
let identities = agent.identities()?;
let identity = match identities.get(0) {
Some(identity) => identity,
None => {
return Err(Error::new(
ErrorCode::Session(raw::LIBSSH2_ERROR_INVAL),
"no identities found in the ssh agent",
))
}
};
agent.userauth(username, &identity)
}
pub fn userauth_pubkey_file(
&self,
username: &str,
pubkey: Option<&Path>,
privatekey: &Path,
passphrase: Option<&str>,
) -> Result<(), Error> {
let pubkey = match pubkey {
Some(s) => Some(CString::new(util::path2bytes(s)?)?),
None => None,
};
let privatekey = CString::new(util::path2bytes(privatekey)?)?;
let passphrase = match passphrase {
Some(s) => Some(CString::new(s)?),
None => None,
};
let inner = self.inner();
inner.rc(unsafe {
raw::libssh2_userauth_publickey_fromfile_ex(
inner.raw,
username.as_ptr() as *const _,
username.len() as c_uint,
pubkey.as_ref().map(|s| s.as_ptr()).unwrap_or(0 as *const _),
privatekey.as_ptr(),
passphrase
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(0 as *const _),
)
})
}
#[cfg(unix)]
pub fn userauth_pubkey_memory(
&self,
username: &str,
pubkeydata: Option<&str>,
privatekeydata: &str,
passphrase: Option<&str>,
) -> Result<(), Error> {
let (pubkeydata, pubkeydata_len) = match pubkeydata {
Some(s) => (Some(CString::new(s)?), s.len()),
None => (None, 0),
};
let privatekeydata_len = privatekeydata.len();
let privatekeydata = CString::new(privatekeydata)?;
let passphrase = match passphrase {
Some(s) => Some(CString::new(s)?),
None => None,
};
let inner = self.inner();
inner.rc(unsafe {
raw::libssh2_userauth_publickey_frommemory(
inner.raw,
username.as_ptr() as *const _,
username.len() as size_t,
pubkeydata
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(0 as *const _),
pubkeydata_len as size_t,
privatekeydata.as_ptr(),
privatekeydata_len as size_t,
passphrase
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(0 as *const _),
)
})
}
#[allow(missing_docs)]
pub fn userauth_hostbased_file(
&self,
username: &str,
publickey: &Path,
privatekey: &Path,
passphrase: Option<&str>,
hostname: &str,
local_username: Option<&str>,
) -> Result<(), Error> {
let publickey = CString::new(util::path2bytes(publickey)?)?;
let privatekey = CString::new(util::path2bytes(privatekey)?)?;
let passphrase = match passphrase {
Some(s) => Some(CString::new(s)?),
None => None,
};
let local_username = match local_username {
Some(local) => local,
None => username,
};
let inner = self.inner();
inner.rc(unsafe {
raw::libssh2_userauth_hostbased_fromfile_ex(
inner.raw,
username.as_ptr() as *const _,
username.len() as c_uint,
publickey.as_ptr(),
privatekey.as_ptr(),
passphrase
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(0 as *const _),
hostname.as_ptr() as *const _,
hostname.len() as c_uint,
local_username.as_ptr() as *const _,
local_username.len() as c_uint,
)
})
}
pub fn authenticated(&self) -> bool {
let inner = self.inner();
unsafe { raw::libssh2_userauth_authenticated(inner.raw) != 0 }
}
pub fn auth_methods(&self, username: &str) -> Result<&str, Error> {
let len = username.len();
let username = CString::new(username)?;
let inner = self.inner();
unsafe {
let ret = raw::libssh2_userauth_list(inner.raw, username.as_ptr(), len as c_uint);
if ret.is_null() {
match inner.last_error() {
Some(err) => Err(err),
None => Ok(""),
}
} else {
Ok(str::from_utf8(::opt_bytes(self, ret).unwrap()).unwrap())
}
}
}
pub fn method_pref(&self, method_type: MethodType, prefs: &str) -> Result<(), Error> {
let prefs = CString::new(prefs)?;
let inner = self.inner();
unsafe {
inner.rc(raw::libssh2_session_method_pref(
inner.raw,
method_type as c_int,
prefs.as_ptr(),
))
}
}
pub fn methods(&self, method_type: MethodType) -> Option<&str> {
let inner = self.inner();
unsafe {
let ptr = raw::libssh2_session_methods(inner.raw, method_type as c_int);
::opt_bytes(self, ptr).and_then(|s| str::from_utf8(s).ok())
}
}
pub fn supported_algs(&self, method_type: MethodType) -> Result<Vec<&'static str>, Error> {
static STATIC: () = ();
let method_type = method_type as c_int;
let mut ret = Vec::new();
let inner = self.inner();
unsafe {
let mut ptr = 0 as *mut _;
let rc = raw::libssh2_session_supported_algs(inner.raw, method_type, &mut ptr);
if rc <= 0 {
inner.rc(rc)?;
}
for i in 0..(rc as isize) {
let s = ::opt_bytes(&STATIC, *ptr.offset(i)).unwrap();
let s = str::from_utf8(s).unwrap();
ret.push(s);
}
raw::libssh2_free(inner.raw, ptr as *mut c_void);
}
Ok(ret)
}
pub fn agent(&self) -> Result<Agent, Error> {
let inner = self.inner();
unsafe {
let agent = raw::libssh2_agent_init(inner.raw);
let err = inner.last_error();
Agent::from_raw_opt(agent, err, &self.inner)
}
}
pub fn known_hosts(&self) -> Result<KnownHosts, Error> {
let inner = self.inner();
unsafe {
let ptr = raw::libssh2_knownhost_init(inner.raw);
let err = inner.last_error();
KnownHosts::from_raw_opt(ptr, err, &self.inner)
}
}
pub fn channel_session(&self) -> Result<Channel, Error> {
self.channel_open(
"session",
raw::LIBSSH2_CHANNEL_WINDOW_DEFAULT as u32,
raw::LIBSSH2_CHANNEL_PACKET_DEFAULT as u32,
None,
)
}
pub fn channel_direct_tcpip(
&self,
host: &str,
port: u16,
src: Option<(&str, u16)>,
) -> Result<Channel, Error> {
let (shost, sport) = src.unwrap_or(("127.0.0.1", 22));
let host = CString::new(host)?;
let shost = CString::new(shost)?;
let inner = self.inner();
unsafe {
let ret = raw::libssh2_channel_direct_tcpip_ex(
inner.raw,
host.as_ptr(),
port as c_int,
shost.as_ptr(),
sport as c_int,
);
let err = inner.last_error();
Channel::from_raw_opt(ret, err, &self.inner)
}
}
pub fn channel_forward_listen(
&self,
remote_port: u16,
host: Option<&str>,
queue_maxsize: Option<u32>,
) -> Result<(Listener, u16), Error> {
let mut bound_port = 0;
let inner = self.inner();
unsafe {
let ret = raw::libssh2_channel_forward_listen_ex(
inner.raw,
host.map(|s| s.as_ptr()).unwrap_or(0 as *const _) as *mut _,
remote_port as c_int,
&mut bound_port,
queue_maxsize.unwrap_or(0) as c_int,
);
let err = inner.last_error();
Listener::from_raw_opt(ret, err, &self.inner).map(|l| (l, bound_port as u16))
}
}
pub fn scp_recv(&self, path: &Path) -> Result<(Channel, ScpFileStat), Error> {
let path = CString::new(util::path2bytes(path)?)?;
let inner = self.inner();
unsafe {
let mut sb: raw::libssh2_struct_stat = mem::zeroed();
let ret = raw::libssh2_scp_recv2(inner.raw, path.as_ptr(), &mut sb);
let err = inner.last_error();
let mut c = Channel::from_raw_opt(ret, err, &self.inner)?;
c.limit_read(sb.st_size as u64);
Ok((c, ScpFileStat { stat: *sb }))
}
}
pub fn scp_send(
&self,
remote_path: &Path,
mode: i32,
size: u64,
times: Option<(u64, u64)>,
) -> Result<Channel, Error> {
let path = CString::new(util::path2bytes(remote_path)?)?;
let (mtime, atime) = times.unwrap_or((0, 0));
let inner = self.inner();
unsafe {
let ret = raw::libssh2_scp_send64(
inner.raw,
path.as_ptr(),
mode as c_int,
size as i64,
mtime as libc::time_t,
atime as libc::time_t,
);
let err = inner.last_error();
Channel::from_raw_opt(ret, err, &self.inner)
}
}
pub fn sftp(&self) -> Result<Sftp, Error> {
let inner = self.inner();
unsafe {
let ret = raw::libssh2_sftp_init(inner.raw);
let err = inner.last_error();
Sftp::from_raw_opt(ret, err, &self.inner)
}
}
pub fn channel_open(
&self,
channel_type: &str,
window_size: u32,
packet_size: u32,
message: Option<&str>,
) -> Result<Channel, Error> {
let message_len = message.map(|s| s.len()).unwrap_or(0);
let inner = self.inner();
unsafe {
let ret = raw::libssh2_channel_open_ex(
inner.raw,
channel_type.as_ptr() as *const _,
channel_type.len() as c_uint,
window_size as c_uint,
packet_size as c_uint,
message
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(0 as *const _) as *const _,
message_len as c_uint,
);
let err = inner.last_error();
Channel::from_raw_opt(ret, err, &self.inner)
}
}
pub fn banner(&self) -> Option<&str> {
self.banner_bytes().and_then(|s| str::from_utf8(s).ok())
}
pub fn banner_bytes(&self) -> Option<&[u8]> {
let inner = self.inner();
unsafe { ::opt_bytes(self, raw::libssh2_session_banner_get(inner.raw)) }
}
pub fn host_key(&self) -> Option<(&[u8], HostKeyType)> {
let mut len = 0;
let mut kind = 0;
let inner = self.inner();
unsafe {
let ret = raw::libssh2_session_hostkey(inner.raw, &mut len, &mut kind);
if ret.is_null() {
return None;
}
let data = slice::from_raw_parts(ret as *const u8, len as usize);
let kind = match kind {
raw::LIBSSH2_HOSTKEY_TYPE_RSA => HostKeyType::Rsa,
raw::LIBSSH2_HOSTKEY_TYPE_DSS => HostKeyType::Dss,
raw::LIBSSH2_HOSTKEY_TYPE_ECDSA_256 => HostKeyType::Ecdsa256,
raw::LIBSSH2_HOSTKEY_TYPE_ECDSA_384 => HostKeyType::Ecdsa384,
raw::LIBSSH2_HOSTKEY_TYPE_ECDSA_521 => HostKeyType::Ecdsa521,
raw::LIBSSH2_HOSTKEY_TYPE_ED25519 => HostKeyType::Ed255219,
raw::LIBSSH2_HOSTKEY_TYPE_UNKNOWN => HostKeyType::Unknown,
_ => HostKeyType::Unknown,
};
Some((data, kind))
}
}
pub fn host_key_hash(&self, hash: HashType) -> Option<&[u8]> {
let len = match hash {
HashType::Md5 => 16,
HashType::Sha1 => 20,
HashType::Sha256 => 32,
};
let inner = self.inner();
unsafe {
let ret = raw::libssh2_hostkey_hash(inner.raw, hash as c_int);
if ret.is_null() {
None
} else {
let ret = ret as *const u8;
Some(slice::from_raw_parts(ret, len))
}
}
}
pub fn set_keepalive(&self, want_reply: bool, interval: u32) {
let inner = self.inner();
unsafe { raw::libssh2_keepalive_config(inner.raw, want_reply as c_int, interval as c_uint) }
}
pub fn keepalive_send(&self) -> Result<u32, Error> {
let mut ret = 0;
let inner = self.inner();
let rc = unsafe { raw::libssh2_keepalive_send(inner.raw, &mut ret) };
inner.rc(rc)?;
Ok(ret as u32)
}
pub fn disconnect(
&self,
reason: Option<DisconnectCode>,
description: &str,
lang: Option<&str>,
) -> Result<(), Error> {
let reason = reason.unwrap_or(ByApplication) as c_int;
let description = CString::new(description)?;
let lang = CString::new(lang.unwrap_or(""))?;
let inner = self.inner();
unsafe {
inner.rc(raw::libssh2_session_disconnect_ex(
inner.raw,
reason,
description.as_ptr(),
lang.as_ptr(),
))
}
}
pub fn block_directions(&self) -> BlockDirections {
let inner = self.inner();
let dir = unsafe { raw::libssh2_session_block_directions(inner.raw) };
match dir {
raw::LIBSSH2_SESSION_BLOCK_INBOUND => BlockDirections::Inbound,
raw::LIBSSH2_SESSION_BLOCK_OUTBOUND => BlockDirections::Outbound,
x if x == raw::LIBSSH2_SESSION_BLOCK_INBOUND | raw::LIBSSH2_SESSION_BLOCK_OUTBOUND => {
BlockDirections::Both
}
_ => BlockDirections::None,
}
}
fn inner(&self) -> MutexGuard<SessionInner> {
self.inner.lock()
}
pub fn trace(&self, bitmask: TraceFlags) {
let inner = self.inner();
unsafe { let _ = raw::libssh2_trace(inner.raw, bitmask.bits() as c_int); }
}
}
#[cfg(unix)]
impl AsRawFd for Session {
fn as_raw_fd(&self) -> RawFd {
let inner = self.inner();
match inner.tcp.as_ref() {
Some(tcp) => tcp.as_raw_fd(),
None => panic!("tried to obtain raw fd without tcp stream set"),
}
}
}
#[cfg(windows)]
impl AsRawSocket for Session {
fn as_raw_socket(&self) -> RawSocket {
let inner = self.inner();
match inner.tcp.as_ref() {
Some(tcp) => tcp.as_raw_socket(),
None => panic!("tried to obtain raw socket without tcp stream set"),
}
}
}
impl SessionInner {
pub fn rc(&self, rc: c_int) -> Result<(), Error> {
if rc >= 0 {
Ok(())
} else {
Err(Error::from_session_error_raw(self.raw, rc))
}
}
pub fn last_error(&self) -> Option<Error> {
Error::last_session_error_raw(self.raw)
}
pub fn set_blocking(&self, blocking: bool) {
unsafe { raw::libssh2_session_set_blocking(self.raw, blocking as c_int) }
}
pub fn is_blocking(&self) -> bool {
unsafe { raw::libssh2_session_get_blocking(self.raw) != 0 }
}
}
impl Drop for SessionInner {
fn drop(&mut self) {
unsafe {
let _rc = raw::libssh2_session_free(self.raw);
}
}
}
impl ScpFileStat {
pub fn size(&self) -> u64 {
self.stat.st_size as u64
}
pub fn mode(&self) -> i32 {
self.stat.st_mode as i32
}
pub fn is_dir(&self) -> bool {
self.mode() & (libc::S_IFMT as i32) == (libc::S_IFDIR as i32)
}
pub fn is_file(&self) -> bool {
self.mode() & (libc::S_IFMT as i32) == (libc::S_IFREG as i32)
}
}