1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 06:54:45 +03:00

ssh: can now pass e2e tests using libssh backend

This commit is contained in:
Wez Furlong 2021-10-19 09:19:33 -07:00
parent 390fcc56ca
commit 8be442e39b
8 changed files with 165 additions and 105 deletions

5
Cargo.lock generated
View File

@ -2413,7 +2413,7 @@ dependencies = [
[[package]]
name = "libssh-rs"
version = "0.1.0"
source = "git+https://github.com/wez/libssh-rs.git?rev=02256a841e3859e8fc4c3c92e849245ac31e17f0#02256a841e3859e8fc4c3c92e849245ac31e17f0"
source = "git+https://github.com/wez/libssh-rs.git?rev=a260815969fa3588996ac9eb25fe09f55ac52219#a260815969fa3588996ac9eb25fe09f55ac52219"
dependencies = [
"bitflags",
"libssh-rs-sys",
@ -2423,7 +2423,7 @@ dependencies = [
[[package]]
name = "libssh-rs-sys"
version = "0.1.0"
source = "git+https://github.com/wez/libssh-rs.git?rev=02256a841e3859e8fc4c3c92e849245ac31e17f0#02256a841e3859e8fc4c3c92e849245ac31e17f0"
source = "git+https://github.com/wez/libssh-rs.git?rev=a260815969fa3588996ac9eb25fe09f55ac52219#a260815969fa3588996ac9eb25fe09f55ac52219"
dependencies = [
"cc",
"libz-sys",
@ -5479,6 +5479,7 @@ dependencies = [
"filenamegen",
"indoc",
"k9",
"libc",
"libssh-rs",
"log",
"once_cell",

View File

@ -21,12 +21,13 @@ camino = "1.0"
dirs-next = "2.0"
filedescriptor = { version="0.8", path = "../filedescriptor" }
filenamegen = "0.2"
libc = "0.2"
log = "0.4"
portable-pty = { version="0.5", path = "../pty" }
regex = "1"
smol = "1.2"
ssh2 = {version="0.9.3", features=["openssl-on-win32"]}
libssh-rs = {git="https://github.com/wez/libssh-rs.git", rev="02256a841e3859e8fc4c3c92e849245ac31e17f0", features=["vendored", "vendored-openssl"]}
libssh-rs = {git="https://github.com/wez/libssh-rs.git", rev="a260815969fa3588996ac9eb25fe09f55ac52219", features=["vendored", "vendored-openssl"]}
#libssh-rs = {path="../../libssh-rs/libssh-rs", features=["vendored", "vendored-openssl"]}
thiserror = "1.0"

View File

@ -235,7 +235,7 @@ impl crate::sessioninner::SessionInner {
}
}
anyhow::bail!("unhandled auth case");
anyhow::bail!("unhandled auth case; methods={:?}", auth_methods);
}
}

View File

@ -1,35 +1,43 @@
use crate::sftp::types::Metadata;
use crate::sftp::{SftpChannelError, SftpChannelResult};
use libssh_rs as libssh;
use std::io::Write;
pub(crate) enum FileWrap {
Ssh2(ssh2::File),
LibSsh(libssh::SftpFile),
}
impl FileWrap {
pub fn reader(&mut self) -> impl std::io::Read + '_ {
pub fn reader(&mut self) -> Box<dyn std::io::Read + '_> {
match self {
Self::Ssh2(file) => file,
Self::Ssh2(file) => Box::new(file),
Self::LibSsh(file) => Box::new(file),
}
}
pub fn writer(&mut self) -> impl std::io::Write + '_ {
pub fn writer(&mut self) -> Box<dyn std::io::Write + '_> {
match self {
Self::Ssh2(file) => file,
Self::Ssh2(file) => Box::new(file),
Self::LibSsh(file) => Box::new(file),
}
}
pub fn set_metadata(&mut self, metadata: Metadata) -> SftpChannelResult<()> {
match self {
Self::Ssh2(file) => file
.setstat(metadata.into())
.map_err(SftpChannelError::from),
Self::Ssh2(file) => Ok(file.setstat(metadata.into())?),
Self::LibSsh(_file) => Err(libssh::Error::fatal(
"FileWrap::set_metadata not implemented for libssh::SftpFile",
)
.into()),
}
}
pub fn metadata(&mut self) -> SftpChannelResult<Metadata> {
match self {
Self::Ssh2(file) => file
.stat()
Self::Ssh2(file) => Ok(file.stat().map(Metadata::from)?),
Self::LibSsh(file) => file
.metadata()
.map(Metadata::from)
.map_err(SftpChannelError::from),
}
@ -38,6 +46,7 @@ impl FileWrap {
pub fn fsync(&mut self) -> SftpChannelResult<()> {
match self {
Self::Ssh2(file) => file.fsync().map_err(SftpChannelError::from),
Self::LibSsh(file) => Ok(file.flush()?),
}
}
}

View File

@ -114,6 +114,15 @@ impl SessionInner {
sess.set_option(libssh::SshOption::Hostname(hostname.clone()))?;
sess.set_option(libssh::SshOption::User(Some(user)))?;
sess.set_option(libssh::SshOption::Port(port))?;
if let Some(files) = self.config.get("identityfile") {
for file in files.split_whitespace() {
sess.set_option(libssh::SshOption::AddIdentity(file.to_string()))?;
}
}
if let Some(kh) = self.config.get("userknownhostsfile") {
sess.set_option(libssh::SshOption::KnownHosts(Some(kh.to_string())))?;
}
sess.options_parse_config(None)?; // FIXME: overridden config path?
sess.connect()?;
@ -948,7 +957,12 @@ impl SessionInner {
}
Ok(sess.sftp.as_mut().expect("sftp should have been set above"))
}
SessionWrap::LibSsh(_) => Err(SftpChannelError::NotImplemented),
SessionWrap::LibSsh(sess) => {
if sess.sftp.is_none() {
sess.sftp = Some(SftpWrap::LibSsh(sess.sess.sftp()?));
}
Ok(sess.sftp.as_mut().expect("sftp should have been set above"))
}
}
}
}

View File

@ -9,9 +9,14 @@ pub(crate) struct Ssh2Session {
pub sftp: Option<SftpWrap>,
}
pub(crate) struct LibSshSession {
pub sess: libssh::Session,
pub sftp: Option<SftpWrap>,
}
pub(crate) enum SessionWrap {
Ssh2(Ssh2Session),
LibSsh(libssh::Session),
LibSsh(LibSshSession),
}
impl SessionWrap {
@ -20,13 +25,13 @@ impl SessionWrap {
}
pub fn with_libssh(sess: libssh::Session) -> Self {
Self::LibSsh(sess)
Self::LibSsh(LibSshSession { sess, sftp: None })
}
pub fn set_blocking(&mut self, blocking: bool) {
match self {
Self::Ssh2(sess) => sess.sess.set_blocking(blocking),
Self::LibSsh(sess) => sess.set_blocking(blocking),
Self::LibSsh(sess) => sess.sess.set_blocking(blocking),
}
}
@ -39,7 +44,7 @@ impl SessionWrap {
BlockDirections::Both => POLLIN | POLLOUT,
},
Self::LibSsh(sess) => {
let (read, write) = sess.get_poll_state();
let (read, write) = sess.sess.get_poll_state();
match (read, write) {
(false, false) => 0,
(true, false) => POLLIN,
@ -53,7 +58,7 @@ impl SessionWrap {
pub fn as_socket_descriptor(&self) -> SocketDescriptor {
match self {
Self::Ssh2(sess) => sess.sess.as_socket_descriptor(),
Self::LibSsh(sess) => sess.as_socket_descriptor(),
Self::LibSsh(sess) => sess.sess.as_socket_descriptor(),
}
}
@ -64,7 +69,7 @@ impl SessionWrap {
Ok(ChannelWrap::Ssh2(channel))
}
Self::LibSsh(sess) => {
let channel = sess.new_channel()?;
let channel = sess.sess.new_channel()?;
channel.open_session()?;
Ok(ChannelWrap::LibSsh(channel))
}

View File

@ -362,6 +362,16 @@ impl From<libssh::FileType> for FileType {
}
}
fn sys_time_to_unix(t: SystemTime) -> u64 {
t.duration_since(SystemTime::UNIX_EPOCH)
.expect("UNIX_EPOCH < SystemTime")
.as_secs()
}
fn unix_to_sys(u: u64) -> SystemTime {
SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(u)
}
impl From<libssh::Metadata> for Metadata {
fn from(stat: libssh::Metadata) -> Self {
Self {
@ -373,16 +383,33 @@ impl From<libssh::Metadata> for Metadata {
size: stat.len(),
uid: stat.uid(),
gid: stat.gid(),
accessed: stat.accessed().map(|t| {
t.duration_since(SystemTime::UNIX_EPOCH)
.expect("UNIX_EPOCH < SystemTime")
.as_secs()
}),
modified: stat.modified().map(|t| {
t.duration_since(SystemTime::UNIX_EPOCH)
.expect("UNIX_EPOCH < SystemTime")
.as_secs()
}),
accessed: stat.accessed().map(sys_time_to_unix),
modified: stat.modified().map(sys_time_to_unix),
}
}
}
impl Into<libssh::SetAttributes> for Metadata {
fn into(self) -> libssh::SetAttributes {
let size = self.size;
let uid_gid = match (self.uid, self.gid) {
(Some(uid), Some(gid)) => Some((uid, gid)),
_ => None,
};
let permissions = self.permissions.map(FilePermissions::to_unix_mode);
let atime_mtime = match (self.accessed, self.modified) {
(Some(a), Some(m)) => {
let a = unix_to_sys(a);
let m = unix_to_sys(m);
Some((a, m))
}
_ => None,
};
libssh::SetAttributes {
size,
uid_gid,
permissions,
atime_mtime,
}
}
}

View File

@ -1,12 +1,21 @@
use crate::dirwrap::DirWrap;
use crate::filewrap::FileWrap;
use crate::sftp::types::{Metadata, OpenOptions, RenameOptions};
use crate::sftp::types::{Metadata, OpenOptions, RenameOptions, WriteMode};
use crate::sftp::{SftpChannelError, SftpChannelResult};
use camino::{Utf8Path, Utf8PathBuf};
use std::convert::TryFrom;
use libc::{O_APPEND, O_RDONLY, O_RDWR, O_WRONLY};
use libssh_rs as libssh;
use std::convert::{TryFrom, TryInto};
pub(crate) enum SftpWrap {
Ssh2(ssh2::Sftp),
LibSsh(libssh::Sftp),
}
fn pathconv(path: std::path::PathBuf) -> SftpChannelResult<Utf8PathBuf> {
Ok(Utf8PathBuf::try_from(path).map_err(|x| {
SftpChannelError::from(std::io::Error::new(std::io::ErrorKind::InvalidData, x))
})?)
}
impl SftpWrap {
@ -17,75 +26,64 @@ impl SftpWrap {
let mode = opts.mode;
let open_type: ssh2::OpenType = opts.ty.into();
let file = sftp
.open_mode(filename.as_std_path(), flags, mode, open_type)
.map_err(SftpChannelError::from)?;
let file = sftp.open_mode(filename.as_std_path(), flags, mode, open_type)?;
Ok(FileWrap::Ssh2(file))
}
Self::LibSsh(sftp) => {
let accesstype = match (opts.write, opts.read) {
(Some(WriteMode::Append), true) => O_RDWR | O_APPEND,
(Some(WriteMode::Append), false) => O_WRONLY | O_APPEND,
(Some(WriteMode::Write), false) => O_WRONLY,
(Some(WriteMode::Write), true) => O_RDWR,
(None, true) => O_RDONLY,
(None, false) => 0,
};
let file =
sftp.open(filename.as_str(), accesstype, opts.mode.try_into().unwrap())?;
Ok(FileWrap::LibSsh(file))
}
}
}
pub fn symlink(&self, path: &Utf8Path, target: &Utf8Path) -> SftpChannelResult<()> {
match self {
Self::Ssh2(sftp) => sftp
.symlink(path.as_std_path(), target.as_std_path())
.map_err(SftpChannelError::from),
Self::Ssh2(sftp) => Ok(sftp.symlink(path.as_std_path(), target.as_std_path())?),
Self::LibSsh(sftp) => Ok(sftp.symlink(path.as_str(), target.as_str())?),
}
}
pub fn read_link(&self, filename: &Utf8Path) -> SftpChannelResult<Utf8PathBuf> {
match self {
Self::Ssh2(sftp) => sftp
.readlink(filename.as_std_path())
.map_err(SftpChannelError::from)
.and_then(|path| {
Utf8PathBuf::try_from(path).map_err(|x| {
SftpChannelError::from(std::io::Error::new(
std::io::ErrorKind::InvalidData,
x,
))
})
}),
Self::Ssh2(sftp) => Ok(pathconv(sftp.readlink(filename.as_std_path())?)?),
Self::LibSsh(sftp) => Ok(sftp.read_link(filename.as_str())?.into()),
}
}
pub fn canonicalize(&self, filename: &Utf8Path) -> SftpChannelResult<Utf8PathBuf> {
match self {
Self::Ssh2(sftp) => sftp
.realpath(filename.as_std_path())
.map_err(SftpChannelError::from)
.and_then(|path| {
Utf8PathBuf::try_from(path).map_err(|x| {
SftpChannelError::from(std::io::Error::new(
std::io::ErrorKind::InvalidData,
x,
))
})
}),
Self::Ssh2(sftp) => Ok(pathconv(sftp.realpath(filename.as_std_path())?)?),
Self::LibSsh(sftp) => Ok(sftp.canonicalize(filename.as_str())?.into()),
}
}
pub fn unlink(&self, filename: &Utf8Path) -> SftpChannelResult<()> {
match self {
Self::Ssh2(sftp) => sftp
.unlink(filename.as_std_path())
.map_err(SftpChannelError::from),
Self::Ssh2(sftp) => Ok(sftp.unlink(filename.as_std_path())?),
Self::LibSsh(sftp) => Ok(sftp.remove_file(filename.as_str())?),
}
}
pub fn remove_dir(&self, filename: &Utf8Path) -> SftpChannelResult<()> {
match self {
Self::Ssh2(sftp) => sftp
.rmdir(filename.as_std_path())
.map_err(SftpChannelError::from),
Self::Ssh2(sftp) => Ok(sftp.rmdir(filename.as_std_path())?),
Self::LibSsh(sftp) => Ok(sftp.remove_dir(filename.as_str())?),
}
}
pub fn create_dir(&self, filename: &Utf8Path, mode: i32) -> SftpChannelResult<()> {
match self {
Self::Ssh2(sftp) => sftp
.mkdir(filename.as_std_path(), mode)
.map_err(SftpChannelError::from),
Self::Ssh2(sftp) => Ok(sftp.mkdir(filename.as_std_path(), mode)?),
Self::LibSsh(sftp) => Ok(sftp.create_dir(filename.as_str(), mode.try_into().unwrap())?),
}
}
@ -96,68 +94,73 @@ impl SftpWrap {
opts: RenameOptions,
) -> SftpChannelResult<()> {
match self {
Self::Ssh2(sftp) => sftp
.rename(src.as_std_path(), dest.as_std_path(), Some(opts.into()))
.map_err(SftpChannelError::from),
Self::Ssh2(sftp) => {
Ok(sftp.rename(src.as_std_path(), dest.as_std_path(), Some(opts.into()))?)
}
Self::LibSsh(sftp) => Ok(sftp.rename(src.as_str(), dest.as_str())?),
}
}
pub fn symlink_metadata(&self, filename: &Utf8Path) -> SftpChannelResult<Metadata> {
match self {
Self::Ssh2(sftp) => sftp
.lstat(filename.as_std_path())
.map(Metadata::from)
.map_err(SftpChannelError::from),
Self::Ssh2(sftp) => Ok(sftp.lstat(filename.as_std_path()).map(Metadata::from)?),
Self::LibSsh(sftp) => Ok(sftp
.symlink_metadata(filename.as_str())
.map(Metadata::from)?),
}
}
pub fn metadata(&self, filename: &Utf8Path) -> SftpChannelResult<Metadata> {
match self {
Self::Ssh2(sftp) => sftp
.stat(filename.as_std_path())
.map(Metadata::from)
.map_err(SftpChannelError::from),
Self::Ssh2(sftp) => Ok(sftp.stat(filename.as_std_path()).map(Metadata::from)?),
Self::LibSsh(sftp) => Ok(sftp.metadata(filename.as_str()).map(Metadata::from)?),
}
}
pub fn set_metadata(&self, filename: &Utf8Path, metadata: Metadata) -> SftpChannelResult<()> {
match self {
Self::Ssh2(sftp) => sftp
.setstat(filename.as_std_path(), metadata.into())
.map_err(SftpChannelError::from),
Self::Ssh2(sftp) => Ok(sftp.setstat(filename.as_std_path(), metadata.into())?),
Self::LibSsh(sftp) => {
let attr: libssh::SetAttributes = metadata.into();
Ok(sftp.set_metadata(filename.as_str(), &attr)?)
}
}
}
pub fn open_dir(&self, filename: &Utf8Path) -> SftpChannelResult<DirWrap> {
match self {
Self::Ssh2(sftp) => sftp
.opendir(filename.as_std_path())
.map_err(SftpChannelError::from)
.map(DirWrap::Ssh2),
Self::Ssh2(sftp) => Ok(sftp.opendir(filename.as_std_path()).map(DirWrap::Ssh2)?),
Self::LibSsh(sftp) => Ok(sftp.open_dir(filename.as_str()).map(DirWrap::LibSsh)?),
}
}
pub fn read_dir(&self, filename: &Utf8Path) -> SftpChannelResult<Vec<(Utf8PathBuf, Metadata)>> {
match self {
Self::Ssh2(sftp) => sftp
.readdir(filename.as_std_path())
.map_err(SftpChannelError::from)
.and_then(|entries| {
let mut mapped_entries = Vec::new();
for (path, stat) in entries {
match Utf8PathBuf::try_from(path) {
Ok(path) => mapped_entries.push((path, Metadata::from(stat))),
Err(x) => {
return Err(SftpChannelError::from(std::io::Error::new(
std::io::ErrorKind::InvalidData,
x,
)));
}
}
}
Self::Ssh2(sftp) => {
let entries = sftp.readdir(filename.as_std_path())?;
let mut mapped_entries = vec![];
for (path, stat) in entries {
let path = pathconv(path)?;
mapped_entries.push((path, Metadata::from(stat)));
}
Ok(mapped_entries)
}),
Ok(mapped_entries)
}
Self::LibSsh(sftp) => {
let entries = sftp.read_dir(filename.as_str())?;
let mut mapped_entries = vec![];
for metadata in entries {
let path = metadata
.name()
.expect("name to be present in read dir results");
if path == "." || path == ".." {
continue;
}
mapped_entries.push((filename.join(path), metadata.into()));
}
Ok(mapped_entries)
}
}
}
}