1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-27 02:25:28 +03:00

ssh: introduce SftpWrap

This commit is contained in:
Wez Furlong 2021-10-18 22:59:58 -07:00
parent a6022f5c65
commit 9d44cc1720
7 changed files with 230 additions and 178 deletions

4
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=6894d2f3871344b08652f7fc09761a65d6c992d6#6894d2f3871344b08652f7fc09761a65d6c992d6"
source = "git+https://github.com/wez/libssh-rs.git?rev=02256a841e3859e8fc4c3c92e849245ac31e17f0#02256a841e3859e8fc4c3c92e849245ac31e17f0"
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=6894d2f3871344b08652f7fc09761a65d6c992d6#6894d2f3871344b08652f7fc09761a65d6c992d6"
source = "git+https://github.com/wez/libssh-rs.git?rev=02256a841e3859e8fc4c3c92e849245ac31e17f0#02256a841e3859e8fc4c3c92e849245ac31e17f0"
dependencies = [
"cc",
"libz-sys",

View File

@ -26,7 +26,7 @@ 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="6894d2f3871344b08652f7fc09761a65d6c992d6", features=["vendored", "vendored-openssl"]}
libssh-rs = {git="https://github.com/wez/libssh-rs.git", rev="02256a841e3859e8fc4c3c92e849245ac31e17f0", features=["vendored", "vendored-openssl"]}
#libssh-rs = {path="../../libssh-rs/libssh-rs", features=["vendored", "vendored-openssl"]}
thiserror = "1.0"

View File

@ -8,6 +8,7 @@ mod session;
mod sessioninner;
mod sessionwrap;
mod sftp;
mod sftpwrap;
pub use auth::*;
pub use config::*;

View File

@ -5,10 +5,10 @@ use crate::pty::*;
use crate::session::{Exec, ExecResult, SessionEvent, SessionRequest, SignalChannel};
use crate::sessionwrap::SessionWrap;
use crate::sftp::{
self, File, FileId, FileRequest, Metadata, SftpChannelError, SftpChannelResult, SftpRequest,
self, File, FileId, FileRequest, SftpChannelError, SftpChannelResult, SftpRequest,
};
use crate::sftpwrap::SftpWrap;
use anyhow::{anyhow, Context};
use camino::Utf8PathBuf;
use filedescriptor::{
poll, pollfd, socketpair, AsRawSocketDescriptor, FileDescriptor, POLLIN, POLLOUT,
};
@ -16,7 +16,6 @@ use libssh_rs as libssh;
use portable_pty::ExitStatus;
use smol::channel::{bounded, Receiver, Sender, TryRecvError};
use std::collections::{HashMap, VecDeque};
use std::convert::TryFrom;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::time::Duration;
@ -420,18 +419,6 @@ impl SessionInner {
}
Ok(true)
}
SessionRequest::Sftp(SftpRequest::Open(msg)) => {
if let Err(err) = self.open(sess, &msg) {
log::error!("{:?} -> error: {:#}", msg, err);
}
Ok(true)
}
SessionRequest::Sftp(SftpRequest::Create(msg)) => {
if let Err(err) = self.create(sess, &msg) {
log::error!("{:?} -> error: {:#}", msg, err);
}
Ok(true)
}
SessionRequest::Sftp(SftpRequest::OpenDir(msg)) => {
if let Err(err) = self.open_dir(sess, &msg) {
log::error!("{:?} -> error: {:#}", msg, err);
@ -649,58 +636,15 @@ impl SessionInner {
sess: &mut SessionWrap,
msg: &sftp::OpenWithMode,
) -> anyhow::Result<()> {
let flags: ssh2::OpenFlags = msg.opts.into();
let mode = msg.opts.mode;
let open_type: ssh2::OpenType = msg.opts.ty.into();
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.open_mode(msg.filename.as_std_path(), flags, mode, open_type)
.map_err(SftpChannelError::from)
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.open(&msg.filename, msg.opts));
match result {
Ok(ssh_file) => {
let (file_id, file) = self.make_file();
msg.reply.try_send(Ok(file))?;
self.files.insert(file_id, FileWrap::Ssh2(ssh_file));
}
Err(x) => msg.reply.try_send(Err(x))?,
}
Ok(())
}
/// Helper to open a file in the `Read` mode.
pub fn open(&mut self, sess: &mut SessionWrap, msg: &sftp::Open) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.open(msg.filename.as_std_path())
.map_err(SftpChannelError::from)
});
match result {
Ok(ssh_file) => {
let (file_id, file) = self.make_file();
msg.reply.try_send(Ok(file))?;
self.files.insert(file_id, FileWrap::Ssh2(ssh_file));
}
Err(x) => msg.reply.try_send(Err(x))?,
}
Ok(())
}
/// Helper to create a file in write-only mode with truncation.
pub fn create(&mut self, sess: &mut SessionWrap, msg: &sftp::Create) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.create(msg.filename.as_std_path())
.map_err(SftpChannelError::from)
});
match result {
Ok(ssh_file) => {
let (file_id, file) = self.make_file();
msg.reply.try_send(Ok(file))?;
self.files.insert(file_id, FileWrap::Ssh2(ssh_file));
self.files.insert(file_id, ssh_file);
}
Err(x) => msg.reply.try_send(Err(x))?,
}
@ -710,16 +654,15 @@ impl SessionInner {
/// Helper to open a directory for reading its contents.
pub fn open_dir(&mut self, sess: &mut SessionWrap, msg: &sftp::OpenDir) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.opendir(msg.filename.as_std_path())
.map_err(SftpChannelError::from)
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.open_dir(&msg.filename));
match result {
Ok(ssh_file) => {
let (file_id, file) = self.make_file();
msg.reply.try_send(Ok(file))?;
self.files.insert(file_id, FileWrap::Ssh2(ssh_file));
self.files.insert(file_id, ssh_file);
}
Err(x) => msg.reply.try_send(Err(x))?,
}
@ -859,26 +802,9 @@ impl SessionInner {
/// Convenience function to read the files in a directory.
pub fn read_dir(&mut self, sess: &mut SessionWrap, msg: &sftp::ReadDir) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.readdir(msg.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,
)));
}
}
}
Ok(mapped_entries)
})
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.read_dir(&msg.filename));
msg.reply.try_send(result)?;
Ok(())
@ -890,10 +816,9 @@ impl SessionInner {
sess: &mut SessionWrap,
msg: &sftp::CreateDir,
) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.mkdir(msg.filename.as_std_path(), msg.mode)
.map_err(SftpChannelError::from)
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.create_dir(&msg.filename, msg.mode));
msg.reply.try_send(result)?;
Ok(())
@ -905,10 +830,9 @@ impl SessionInner {
sess: &mut SessionWrap,
msg: &sftp::RemoveDir,
) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.rmdir(msg.filename.as_std_path())
.map_err(SftpChannelError::from)
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.remove_dir(&msg.filename));
msg.reply.try_send(result)?;
Ok(())
@ -920,11 +844,9 @@ impl SessionInner {
sess: &mut SessionWrap,
msg: &sftp::GetMetadata,
) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.stat(msg.filename.as_std_path())
.map(Metadata::from)
.map_err(SftpChannelError::from)
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.metadata(&msg.filename));
msg.reply.try_send(result)?;
Ok(())
@ -936,11 +858,9 @@ impl SessionInner {
sess: &mut SessionWrap,
msg: &sftp::SymlinkMetadata,
) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.lstat(msg.filename.as_std_path())
.map(Metadata::from)
.map_err(SftpChannelError::from)
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.symlink_metadata(&msg.filename));
msg.reply.try_send(result)?;
Ok(())
@ -952,10 +872,9 @@ impl SessionInner {
sess: &mut SessionWrap,
msg: &sftp::SetMetadata,
) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.setstat(msg.filename.as_std_path(), msg.metadata.into())
.map_err(SftpChannelError::from)
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.set_metadata(&msg.filename, msg.metadata));
msg.reply.try_send(result)?;
Ok(())
@ -963,10 +882,9 @@ impl SessionInner {
/// Create symlink at `target` pointing at `path`.
pub fn symlink(&mut self, sess: &mut SessionWrap, msg: &sftp::Symlink) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.symlink(msg.path.as_std_path(), msg.target.as_std_path())
.map_err(SftpChannelError::from)
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.symlink(&msg.path, &msg.target));
msg.reply.try_send(result)?;
Ok(())
@ -978,18 +896,9 @@ impl SessionInner {
sess: &mut SessionWrap,
msg: &sftp::ReadLink,
) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.readlink(msg.path.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,
))
})
})
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.read_link(&msg.path));
msg.reply.try_send(result)?;
Ok(())
@ -1001,18 +910,9 @@ impl SessionInner {
sess: &mut SessionWrap,
msg: &sftp::Canonicalize,
) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.realpath(msg.path.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,
))
})
})
});
let result = self
.init_sftp(sess)
.and_then(|sftp| sftp.canonicalize(&msg.path));
msg.reply.try_send(result)?;
Ok(())
@ -1021,12 +921,8 @@ impl SessionInner {
/// Rename the filesystem object on the remote filesystem.
pub fn rename(&mut self, sess: &mut SessionWrap, msg: &sftp::Rename) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.rename(
msg.src.as_std_path(),
msg.dst.as_std_path(),
Some(msg.opts.into()),
)
.map_err(SftpChannelError::from)
sftp.rename(&msg.src, &msg.dst, msg.opts)
.map_err(SftpChannelError::from)
});
msg.reply.try_send(result)?;
@ -1039,24 +935,18 @@ impl SessionInner {
sess: &mut SessionWrap,
msg: &sftp::RemoveFile,
) -> anyhow::Result<()> {
let result = self.init_sftp(sess).and_then(|sftp| {
sftp.unlink(msg.file.as_std_path())
.map_err(SftpChannelError::from)
});
let result = self.init_sftp(sess).and_then(|sftp| sftp.unlink(&msg.file));
msg.reply.try_send(result)?;
Ok(())
}
/// Initialize the sftp channel if not already created, returning a mutable reference to it
fn init_sftp<'a>(
&mut self,
sess: &'a mut SessionWrap,
) -> SftpChannelResult<&'a mut ssh2::Sftp> {
fn init_sftp<'a>(&mut self, sess: &'a mut SessionWrap) -> SftpChannelResult<&'a mut SftpWrap> {
match sess {
SessionWrap::Ssh2(sess) => {
if sess.sftp.is_none() {
sess.sftp = Some(sess.sess.sftp()?);
sess.sftp = Some(SftpWrap::Ssh2(sess.sess.sftp()?));
}
Ok(sess.sftp.as_mut().expect("sftp should have been set above"))
}

View File

@ -1,11 +1,12 @@
use crate::channelwrap::ChannelWrap;
use crate::sftpwrap::SftpWrap;
use filedescriptor::{AsRawSocketDescriptor, SocketDescriptor, POLLIN, POLLOUT};
use libssh_rs as libssh;
use ssh2::BlockDirections;
pub(crate) struct Ssh2Session {
pub sess: ssh2::Session,
pub sftp: Option<ssh2::Sftp>,
pub sftp: Option<SftpWrap>,
}
pub(crate) enum SessionWrap {

View File

@ -91,16 +91,16 @@ impl Sftp {
T: TryInto<Utf8PathBuf, Error = E>,
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Open(Open {
filename: filename.try_into().map_err(into_invalid_data)?,
reply,
})))
.await?;
let mut result = rx.recv().await??;
result.initialize_sender(self.tx.clone());
Ok(result)
self.open_with_mode(
filename,
OpenOptions {
read: true,
write: None,
mode: 0,
ty: OpenFileType::File,
},
)
.await
}
/// Helper to create a file in write-only mode with truncation.
@ -109,16 +109,16 @@ impl Sftp {
T: TryInto<Utf8PathBuf, Error = E>,
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
let (reply, rx) = bounded(1);
self.tx
.send(SessionRequest::Sftp(SftpRequest::Create(Create {
filename: filename.try_into().map_err(into_invalid_data)?,
reply,
})))
.await?;
let mut result = rx.recv().await??;
result.initialize_sender(self.tx.clone());
Ok(result)
self.open_with_mode(
filename,
OpenOptions {
read: false,
write: Some(WriteMode::Write),
mode: 0o666,
ty: OpenFileType::File,
},
)
.await
}
/// Helper to open a directory for reading its contents.
@ -356,8 +356,6 @@ impl Sftp {
#[derive(Debug)]
pub(crate) enum SftpRequest {
OpenWithMode(OpenWithMode),
Open(Open),
Create(Create),
OpenDir(OpenDir),
ReadDir(ReadDir),
CreateDir(CreateDir),

162
wezterm-ssh/src/sftpwrap.rs Normal file
View File

@ -0,0 +1,162 @@
use crate::filewrap::FileWrap;
use crate::sftp::{Metadata, OpenOptions, RenameOptions, SftpChannelError, SftpChannelResult};
use camino::{Utf8Path, Utf8PathBuf};
use std::convert::TryFrom;
pub(crate) enum SftpWrap {
Ssh2(ssh2::Sftp),
}
impl SftpWrap {
pub fn open(&self, filename: &Utf8Path, opts: OpenOptions) -> SftpChannelResult<FileWrap> {
match self {
Self::Ssh2(sftp) => {
let flags: ssh2::OpenFlags = opts.into();
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)?;
Ok(FileWrap::Ssh2(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),
}
}
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,
))
})
}),
}
}
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,
))
})
}),
}
}
pub fn unlink(&self, filename: &Utf8Path) -> SftpChannelResult<()> {
match self {
Self::Ssh2(sftp) => sftp
.unlink(filename.as_std_path())
.map_err(SftpChannelError::from),
}
}
pub fn remove_dir(&self, filename: &Utf8Path) -> SftpChannelResult<()> {
match self {
Self::Ssh2(sftp) => sftp
.rmdir(filename.as_std_path())
.map_err(SftpChannelError::from),
}
}
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),
}
}
pub fn rename(
&self,
src: &Utf8Path,
dest: &Utf8Path,
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),
}
}
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),
}
}
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),
}
}
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),
}
}
// FIXME: need a DirWrap
pub fn open_dir(&self, filename: &Utf8Path) -> SftpChannelResult<FileWrap> {
match self {
Self::Ssh2(sftp) => sftp
.opendir(filename.as_std_path())
.map_err(SftpChannelError::from)
.map(FileWrap::Ssh2),
}
}
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,
)));
}
}
}
Ok(mapped_entries)
}),
}
}
}