mirror of
https://github.com/wez/wezterm.git
synced 2024-09-19 18:57:59 +03:00
Support gating libssh-rs and ssh2 behind features of same name
This commit is contained in:
parent
b3987bec12
commit
1f17416e60
@ -11,6 +11,7 @@ documentation = "https://docs.rs/wezterm-ssh"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["libssh-rs", "ssh2"]
|
||||
vendored-openssl = ["ssh2/vendored-openssl", "libssh-rs/vendored-openssl"]
|
||||
|
||||
[dependencies]
|
||||
@ -26,9 +27,9 @@ log = "0.4"
|
||||
portable-pty = { version="0.7", path = "../pty" }
|
||||
regex = "1"
|
||||
smol = "1.2"
|
||||
ssh2 = {version="0.9.3", features=["openssl-on-win32"]}
|
||||
libssh-rs = {version="0.1.2", features=["vendored"], git="https://github.com/wez/libssh-rs.git"}
|
||||
#libssh-rs = {path="../../libssh-rs/libssh-rs", features=["vendored"]}
|
||||
ssh2 = {version="0.9.3", features=["openssl-on-win32"], optional = true}
|
||||
libssh-rs = {version="0.1.2", features=["vendored"], git="https://github.com/wez/libssh-rs.git", optional = true}
|
||||
#libssh-rs = {path="../../libssh-rs/libssh-rs", features=["vendored"], optional = true}
|
||||
thiserror = "1.0"
|
||||
|
||||
# Not used directly, but is used to centralize the openssl vendor feature selection
|
||||
|
@ -1,9 +1,6 @@
|
||||
use crate::session::SessionEvent;
|
||||
use anyhow::Context;
|
||||
use libssh_rs as libssh;
|
||||
use smol::channel::{bounded, Sender};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AuthenticationPrompt {
|
||||
@ -30,6 +27,7 @@ impl AuthenticationEvent {
|
||||
}
|
||||
|
||||
impl crate::sessioninner::SessionInner {
|
||||
#[cfg(feature = "ssh2")]
|
||||
fn agent_auth(&mut self, sess: &ssh2::Session, user: &str) -> anyhow::Result<bool> {
|
||||
if let Some(only) = self.config.get("identitiesonly") {
|
||||
if only == "yes" {
|
||||
@ -55,12 +53,15 @@ impl crate::sessioninner::SessionInner {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
fn pubkey_auth(
|
||||
&mut self,
|
||||
sess: &ssh2::Session,
|
||||
user: &str,
|
||||
host: &str,
|
||||
) -> anyhow::Result<bool> {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
if let Some(files) = self.config.get("identityfile") {
|
||||
for file in files.split_whitespace() {
|
||||
let pubkey: PathBuf = format!("{}.pub", file).into();
|
||||
@ -127,7 +128,9 @@ impl crate::sessioninner::SessionInner {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn authenticate_libssh(&mut self, sess: &libssh::Session) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
pub fn authenticate_libssh(&mut self, sess: &libssh_rs::Session) -> anyhow::Result<()> {
|
||||
use std::collections::HashMap;
|
||||
let tx = self.tx_event.clone();
|
||||
|
||||
// Set the callback for pubkey auth
|
||||
@ -153,7 +156,7 @@ impl crate::sessioninner::SessionInner {
|
||||
Ok(answers.remove(0))
|
||||
});
|
||||
|
||||
use libssh::{AuthMethods, AuthStatus};
|
||||
use libssh_rs::{AuthMethods, AuthStatus};
|
||||
match sess.userauth_none(None)? {
|
||||
AuthStatus::Success => return Ok(()),
|
||||
_ => {}
|
||||
@ -249,12 +252,15 @@ impl crate::sessioninner::SessionInner {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
pub fn authenticate(
|
||||
&mut self,
|
||||
sess: &ssh2::Session,
|
||||
user: &str,
|
||||
host: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
use std::collections::HashSet;
|
||||
|
||||
loop {
|
||||
if sess.authenticated() {
|
||||
return Ok(());
|
||||
|
@ -1,12 +1,15 @@
|
||||
use crate::pty::{NewPty, ResizePty};
|
||||
use libssh_rs as libssh;
|
||||
use portable_pty::ExitStatus;
|
||||
|
||||
pub(crate) enum ChannelWrap {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Ssh2(ssh2::Channel),
|
||||
LibSsh(libssh::Channel),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
LibSsh(libssh_rs::Channel),
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
fn has_signal(chan: &ssh2::Channel) -> Option<ssh2::ExitSignal> {
|
||||
if let Ok(sig) = chan.exit_signal() {
|
||||
if sig.exit_signal.is_some() {
|
||||
@ -19,6 +22,7 @@ fn has_signal(chan: &ssh2::Channel) -> Option<ssh2::ExitSignal> {
|
||||
impl ChannelWrap {
|
||||
pub fn exit_status(&mut self) -> Option<ExitStatus> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(chan) => {
|
||||
if chan.eof() && chan.wait_close().is_ok() {
|
||||
if let Some(_sig) = has_signal(chan) {
|
||||
@ -32,6 +36,8 @@ impl ChannelWrap {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => {
|
||||
if chan.is_eof() {
|
||||
if let Some(status) = chan.get_exit_status() {
|
||||
@ -45,7 +51,10 @@ impl ChannelWrap {
|
||||
|
||||
pub fn reader(&mut self, idx: usize) -> Box<dyn std::io::Read + '_> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(chan) => Box::new(chan.stream(idx as i32)),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => match idx {
|
||||
0 => Box::new(chan.stdout()),
|
||||
1 => Box::new(chan.stderr()),
|
||||
@ -56,16 +65,22 @@ impl ChannelWrap {
|
||||
|
||||
pub fn writer(&mut self) -> Box<dyn std::io::Write + '_> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(chan) => Box::new(chan),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => Box::new(chan.stdin()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(chan) => {
|
||||
let _ = chan.close();
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => {
|
||||
let _ = chan.close();
|
||||
}
|
||||
@ -74,6 +89,7 @@ impl ChannelWrap {
|
||||
|
||||
pub fn request_pty(&mut self, newpty: &NewPty) -> anyhow::Result<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(chan) => Ok(chan.request_pty(
|
||||
&newpty.term,
|
||||
None,
|
||||
@ -84,6 +100,8 @@ impl ChannelWrap {
|
||||
newpty.size.pixel_height.into(),
|
||||
)),
|
||||
)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => Ok(chan.request_pty(
|
||||
&newpty.term,
|
||||
newpty.size.cols.into(),
|
||||
@ -94,42 +112,60 @@ impl ChannelWrap {
|
||||
|
||||
pub fn request_env(&mut self, name: &str, value: &str) -> anyhow::Result<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(chan) => Ok(chan.setenv(name, value)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => Ok(chan.request_env(name, value)?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_exec(&mut self, command_line: &str) -> anyhow::Result<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(chan) => Ok(chan.exec(command_line)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => Ok(chan.request_exec(command_line)?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_shell(&mut self) -> anyhow::Result<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(chan) => Ok(chan.shell()?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => Ok(chan.request_shell()?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize_pty(&mut self, resize: &ResizePty) -> anyhow::Result<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(chan) => Ok(chan.request_pty_size(
|
||||
resize.size.cols.into(),
|
||||
resize.size.rows.into(),
|
||||
Some(resize.size.pixel_width.into()),
|
||||
Some(resize.size.pixel_height.into()),
|
||||
)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => {
|
||||
Ok(chan.change_pty_size(resize.size.cols.into(), resize.size.rows.into())?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_signal(&mut self, signame: &str) -> anyhow::Result<()> {
|
||||
pub fn send_signal(
|
||||
&mut self,
|
||||
#[cfg_attr(not(feature = "libssh-rs"), allow(unused_variables))] signame: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(_) => Ok(()),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(chan) => Ok(chan.request_send_signal(signame)?),
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
use crate::sftp::types::Metadata;
|
||||
use crate::sftp::{SftpChannelError, SftpChannelResult};
|
||||
use camino::Utf8PathBuf;
|
||||
use libssh_rs as libssh;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub(crate) enum DirWrap {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Ssh2(ssh2::File),
|
||||
LibSsh(libssh::SftpDir),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
LibSsh(libssh_rs::SftpDir),
|
||||
}
|
||||
|
||||
impl DirWrap {
|
||||
pub fn read_dir(&mut self) -> SftpChannelResult<(Utf8PathBuf, Metadata)> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(file) => {
|
||||
use std::convert::TryFrom;
|
||||
file.readdir()
|
||||
.map_err(SftpChannelError::from)
|
||||
.and_then(|(path, stat)| match Utf8PathBuf::try_from(path) {
|
||||
@ -23,6 +26,8 @@ impl DirWrap {
|
||||
))),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(dir) => match dir.read_dir() {
|
||||
None => Err(SftpChannelError::from(std::io::Error::new(
|
||||
std::io::ErrorKind::UnexpectedEof,
|
||||
|
@ -1,32 +1,45 @@
|
||||
use crate::sftp::types::Metadata;
|
||||
use crate::sftp::{SftpChannelError, SftpChannelResult};
|
||||
use libssh_rs as libssh;
|
||||
use std::io::Write;
|
||||
|
||||
pub(crate) enum FileWrap {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Ssh2(ssh2::File),
|
||||
LibSsh(libssh::SftpFile),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
LibSsh(libssh_rs::SftpFile),
|
||||
}
|
||||
|
||||
impl FileWrap {
|
||||
pub fn reader(&mut self) -> Box<dyn std::io::Read + '_> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(file) => Box::new(file),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(file) => Box::new(file),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writer(&mut self) -> Box<dyn std::io::Write + '_> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(file) => Box::new(file),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(file) => Box::new(file),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_metadata(&mut self, metadata: Metadata) -> SftpChannelResult<()> {
|
||||
pub fn set_metadata(
|
||||
&mut self,
|
||||
#[cfg_attr(not(feature = "ssh2"), allow(unused_variables))] metadata: Metadata,
|
||||
) -> SftpChannelResult<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(file) => Ok(file.setstat(metadata.into())?),
|
||||
Self::LibSsh(_file) => Err(libssh::Error::fatal(
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(_file) => Err(libssh_rs::Error::fatal(
|
||||
"FileWrap::set_metadata not implemented for libssh::SftpFile",
|
||||
)
|
||||
.into()),
|
||||
@ -35,7 +48,10 @@ impl FileWrap {
|
||||
|
||||
pub fn metadata(&mut self) -> SftpChannelResult<Metadata> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(file) => Ok(file.stat().map(Metadata::from)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(file) => file
|
||||
.metadata()
|
||||
.map(Metadata::from)
|
||||
@ -45,8 +61,14 @@ impl FileWrap {
|
||||
|
||||
pub fn fsync(&mut self) -> SftpChannelResult<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(file) => file.fsync().map_err(SftpChannelError::from),
|
||||
Self::LibSsh(file) => Ok(file.flush()?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(file) => {
|
||||
use std::io::Write;
|
||||
Ok(file.flush()?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
use crate::session::SessionEvent;
|
||||
use anyhow::{anyhow, Context};
|
||||
use libssh_rs as libssh;
|
||||
use anyhow::Context;
|
||||
use smol::channel::{bounded, Sender};
|
||||
use ssh2::CheckResult;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HostVerificationEvent {
|
||||
@ -22,19 +18,20 @@ impl HostVerificationEvent {
|
||||
}
|
||||
|
||||
impl crate::sessioninner::SessionInner {
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
pub fn host_verification_libssh(
|
||||
&mut self,
|
||||
sess: &libssh::Session,
|
||||
sess: &libssh_rs::Session,
|
||||
hostname: &str,
|
||||
port: u16,
|
||||
) -> anyhow::Result<()> {
|
||||
let key = sess
|
||||
.get_server_public_key()?
|
||||
.get_public_key_hash_hexa(libssh::PublicKeyHashType::Sha256)?;
|
||||
.get_public_key_hash_hexa(libssh_rs::PublicKeyHashType::Sha256)?;
|
||||
|
||||
match sess.is_known_server()? {
|
||||
libssh::KnownHosts::Ok => Ok(()),
|
||||
libssh::KnownHosts::NotFound | libssh::KnownHosts::Unknown => {
|
||||
libssh_rs::KnownHosts::Ok => Ok(()),
|
||||
libssh_rs::KnownHosts::NotFound | libssh_rs::KnownHosts::Unknown => {
|
||||
let (reply, confirm) = bounded(1);
|
||||
self.tx_event
|
||||
.try_send(SessionEvent::HostVerify(HostVerificationEvent {
|
||||
@ -57,7 +54,7 @@ impl crate::sessioninner::SessionInner {
|
||||
|
||||
Ok(sess.update_known_hosts_file()?)
|
||||
}
|
||||
libssh::KnownHosts::Changed => {
|
||||
libssh_rs::KnownHosts::Changed => {
|
||||
anyhow::bail!(
|
||||
"host key mismatch for ssh server {}:{}.\n\
|
||||
Got fingerprint {} instead of expected value from known_hosts\n\
|
||||
@ -68,7 +65,7 @@ impl crate::sessioninner::SessionInner {
|
||||
key,
|
||||
);
|
||||
}
|
||||
libssh::KnownHosts::Other => {
|
||||
libssh_rs::KnownHosts::Other => {
|
||||
anyhow::bail!(
|
||||
"The host key for this server was not found, but another\n\
|
||||
type of key exists. An attacker might change the default\n\
|
||||
@ -78,6 +75,8 @@ impl crate::sessioninner::SessionInner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
pub fn host_verification(
|
||||
&mut self,
|
||||
sess: &ssh2::Session,
|
||||
@ -85,6 +84,10 @@ impl crate::sessioninner::SessionInner {
|
||||
port: u16,
|
||||
remote_address: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
use anyhow::anyhow;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
let mut known_hosts = sess.known_hosts().context("preparing known hosts")?;
|
||||
|
||||
let known_hosts_files = self
|
||||
@ -135,8 +138,8 @@ impl crate::sessioninner::SessionInner {
|
||||
.ok_or_else(|| anyhow!("failed to get host fingerprint"))?;
|
||||
|
||||
match known_hosts.check_port(&remote_host_name, port, key) {
|
||||
CheckResult::Match => {}
|
||||
CheckResult::NotFound => {
|
||||
ssh2::CheckResult::Match => {}
|
||||
ssh2::CheckResult::NotFound => {
|
||||
let (reply, confirm) = bounded(1);
|
||||
self.tx_event
|
||||
.try_send(SessionEvent::HostVerify(HostVerificationEvent {
|
||||
@ -171,7 +174,7 @@ impl crate::sessioninner::SessionInner {
|
||||
.write_file(&file, ssh2::KnownHostFileKind::OpenSSH)
|
||||
.with_context(|| format!("writing known_hosts file {}", file.display()))?;
|
||||
}
|
||||
CheckResult::Mismatch => {
|
||||
ssh2::CheckResult::Mismatch => {
|
||||
anyhow::bail!(
|
||||
"host key mismatch for ssh server {}.\n\
|
||||
Got fingerprint {} instead of expected value from known_hosts\n\
|
||||
@ -182,7 +185,7 @@ impl crate::sessioninner::SessionInner {
|
||||
file.display()
|
||||
);
|
||||
}
|
||||
CheckResult::Failure => {
|
||||
ssh2::CheckResult::Failure => {
|
||||
anyhow::bail!("failed to check the known hosts");
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
#[cfg(not(any(feature = "libssh-rs", feature = "ssh2")))]
|
||||
compile_error!("Either libssh-rs or ssh2 must be enabled!");
|
||||
|
||||
mod auth;
|
||||
mod channelwrap;
|
||||
mod config;
|
||||
|
@ -14,13 +14,10 @@ use camino::Utf8PathBuf;
|
||||
use filedescriptor::{
|
||||
poll, pollfd, socketpair, AsRawSocketDescriptor, FileDescriptor, POLLIN, POLLOUT,
|
||||
};
|
||||
use libssh_rs as libssh;
|
||||
use portable_pty::ExitStatus;
|
||||
use smol::channel::{bounded, Receiver, Sender, TryRecvError};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::ffi::CStr;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -70,10 +67,31 @@ impl SessionInner {
|
||||
.config
|
||||
.get("wezterm_ssh_backend")
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("libssh");
|
||||
.unwrap_or(
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
"libssh",
|
||||
#[cfg(not(feature = "libssh-rs"))]
|
||||
"ssh2",
|
||||
);
|
||||
match backend {
|
||||
#[cfg(feature = "ssh2")]
|
||||
"ssh2" => self.run_impl_ssh2(),
|
||||
|
||||
#[cfg(not(feature = "ssh2"))]
|
||||
"ssh2" => anyhow::bail!(
|
||||
"invalid wezterm_ssh_backend value: {}, not compiled with `ssh2`",
|
||||
backend
|
||||
),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
"libssh" => self.run_impl_libssh(),
|
||||
|
||||
#[cfg(not(feature = "libssh-rs"))]
|
||||
"libssh" => anyhow::bail!(
|
||||
"invalid wezterm_ssh_backend value: {}, not compiled with `libssh`",
|
||||
backend
|
||||
),
|
||||
|
||||
_ => anyhow::bail!(
|
||||
"invalid wezterm_ssh_backend value: {}, expected either `ssh2` or `libssh`",
|
||||
backend
|
||||
@ -81,6 +99,7 @@ impl SessionInner {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
fn run_impl_libssh(&mut self) -> anyhow::Result<()> {
|
||||
let hostname = self
|
||||
.config
|
||||
@ -105,7 +124,7 @@ impl SessionInner {
|
||||
))))
|
||||
.context("notifying user of banner")?;
|
||||
|
||||
let sess = libssh::Session::new()?;
|
||||
let sess = libssh_rs::Session::new()?;
|
||||
if self
|
||||
.config
|
||||
.get("wezterm_ssh_verbose")
|
||||
@ -113,7 +132,7 @@ impl SessionInner {
|
||||
.unwrap_or("false")
|
||||
== "true"
|
||||
{
|
||||
sess.set_option(libssh::SshOption::LogLevel(libssh::LogLevel::Packet))?;
|
||||
sess.set_option(libssh_rs::SshOption::LogLevel(libssh_rs::LogLevel::Packet))?;
|
||||
|
||||
/// libssh logs to stderr, but on Windows in the GUI there isn't a valid
|
||||
/// stderr for it to log to.
|
||||
@ -125,6 +144,7 @@ impl SessionInner {
|
||||
message: *const std::os::raw::c_char,
|
||||
_userdata: *mut std::os::raw::c_void,
|
||||
) {
|
||||
use std::ffi::CStr;
|
||||
let function = CStr::from_ptr(function).to_string_lossy().to_string();
|
||||
let message = CStr::from_ptr(message).to_string_lossy().to_string();
|
||||
|
||||
@ -147,32 +167,34 @@ impl SessionInner {
|
||||
);
|
||||
}
|
||||
unsafe {
|
||||
libssh::sys::ssh_set_log_callback(Some(log_callback));
|
||||
libssh_rs::sys::ssh_set_log_callback(Some(log_callback));
|
||||
}
|
||||
}
|
||||
sess.set_option(libssh::SshOption::Hostname(hostname.clone()))?;
|
||||
sess.set_option(libssh::SshOption::User(Some(user)))?;
|
||||
sess.set_option(libssh::SshOption::Port(port))?;
|
||||
sess.set_option(libssh_rs::SshOption::Hostname(hostname.clone()))?;
|
||||
sess.set_option(libssh_rs::SshOption::User(Some(user)))?;
|
||||
sess.set_option(libssh_rs::SshOption::Port(port))?;
|
||||
sess.options_parse_config(None)?; // FIXME: overridden config path?
|
||||
if let Some(agent) = self.config.get("identityagent") {
|
||||
sess.set_option(libssh::SshOption::IdentityAgent(Some(agent.clone())))?;
|
||||
sess.set_option(libssh_rs::SshOption::IdentityAgent(Some(agent.clone())))?;
|
||||
}
|
||||
if let Some(files) = self.config.get("identityfile") {
|
||||
for file in files.split_whitespace() {
|
||||
sess.set_option(libssh::SshOption::AddIdentity(file.to_string()))?;
|
||||
sess.set_option(libssh_rs::SshOption::AddIdentity(file.to_string()))?;
|
||||
}
|
||||
}
|
||||
if let Some(kh) = self.config.get("userknownhostsfile") {
|
||||
for file in kh.split_whitespace() {
|
||||
sess.set_option(libssh::SshOption::KnownHosts(Some(file.to_string())))?;
|
||||
sess.set_option(libssh_rs::SshOption::KnownHosts(Some(file.to_string())))?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(cmd) = self.config.get("proxycommand") {
|
||||
sess.set_option(libssh::SshOption::ProxyCommand(Some(cmd.to_string())))?;
|
||||
sess.set_option(libssh_rs::SshOption::ProxyCommand(Some(cmd.to_string())))?;
|
||||
}
|
||||
if let Some(types) = self.config.get("pubkeyacceptedtypes") {
|
||||
sess.set_option(libssh::SshOption::PublicKeyAcceptedTypes(types.to_string()))?;
|
||||
sess.set_option(libssh_rs::SshOption::PublicKeyAcceptedTypes(
|
||||
types.to_string(),
|
||||
))?;
|
||||
}
|
||||
|
||||
sess.connect()?;
|
||||
@ -200,7 +222,9 @@ impl SessionInner {
|
||||
self.request_loop(&mut sess)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
fn run_impl_ssh2(&mut self) -> anyhow::Result<()> {
|
||||
use std::net::TcpStream;
|
||||
let hostname = self
|
||||
.config
|
||||
.get("hostname")
|
||||
@ -795,12 +819,15 @@ impl SessionInner {
|
||||
/// 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 SftpWrap> {
|
||||
match sess {
|
||||
#[cfg(feature = "ssh2")]
|
||||
SessionWrap::Ssh2(sess) => {
|
||||
if sess.sftp.is_none() {
|
||||
sess.sftp = Some(SftpWrap::Ssh2(sess.sess.sftp()?));
|
||||
}
|
||||
Ok(sess.sftp.as_mut().expect("sftp should have been set above"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
SessionWrap::LibSsh(sess) => {
|
||||
if sess.sftp.is_none() {
|
||||
sess.sftp = Some(SftpWrap::LibSsh(sess.sess.sftp()?));
|
||||
|
@ -1,48 +1,59 @@
|
||||
use crate::channelwrap::ChannelWrap;
|
||||
use crate::sftpwrap::SftpWrap;
|
||||
use filedescriptor::{AsRawSocketDescriptor, SocketDescriptor, POLLIN, POLLOUT};
|
||||
use libssh_rs as libssh;
|
||||
use ssh2::BlockDirections;
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
pub(crate) struct Ssh2Session {
|
||||
pub sess: ssh2::Session,
|
||||
pub sftp: Option<SftpWrap>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
pub(crate) struct LibSshSession {
|
||||
pub sess: libssh::Session,
|
||||
pub sess: libssh_rs::Session,
|
||||
pub sftp: Option<SftpWrap>,
|
||||
}
|
||||
|
||||
pub(crate) enum SessionWrap {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Ssh2(Ssh2Session),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
LibSsh(LibSshSession),
|
||||
}
|
||||
|
||||
impl SessionWrap {
|
||||
#[cfg(feature = "ssh2")]
|
||||
pub fn with_ssh2(sess: ssh2::Session) -> Self {
|
||||
Self::Ssh2(Ssh2Session { sess, sftp: None })
|
||||
}
|
||||
|
||||
pub fn with_libssh(sess: libssh::Session) -> Self {
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
pub fn with_libssh(sess: libssh_rs::Session) -> Self {
|
||||
Self::LibSsh(LibSshSession { sess, sftp: None })
|
||||
}
|
||||
|
||||
pub fn set_blocking(&mut self, blocking: bool) {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sess) => sess.sess.set_blocking(blocking),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sess) => sess.sess.set_blocking(blocking),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_poll_flags(&self) -> i16 {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sess) => match sess.sess.block_directions() {
|
||||
BlockDirections::None => 0,
|
||||
BlockDirections::Inbound => POLLIN,
|
||||
BlockDirections::Outbound => POLLOUT,
|
||||
BlockDirections::Both => POLLIN | POLLOUT,
|
||||
ssh2::BlockDirections::None => 0,
|
||||
ssh2::BlockDirections::Inbound => POLLIN,
|
||||
ssh2::BlockDirections::Outbound => POLLOUT,
|
||||
ssh2::BlockDirections::Both => POLLIN | POLLOUT,
|
||||
},
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sess) => {
|
||||
let (read, write) = sess.sess.get_poll_state();
|
||||
match (read, write) {
|
||||
@ -57,17 +68,23 @@ impl SessionWrap {
|
||||
|
||||
pub fn as_socket_descriptor(&self) -> SocketDescriptor {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sess) => sess.sess.as_socket_descriptor(),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sess) => sess.sess.as_socket_descriptor(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_session(&self) -> anyhow::Result<ChannelWrap> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sess) => {
|
||||
let channel = sess.sess.channel_session()?;
|
||||
Ok(ChannelWrap::Ssh2(channel))
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sess) => {
|
||||
let channel = sess.sess.new_channel()?;
|
||||
channel.open_session()?;
|
||||
|
@ -36,20 +36,28 @@ pub enum SftpError {
|
||||
NoMedia = 13,
|
||||
|
||||
// Below are libssh2-specific errors
|
||||
#[cfg(feature = "ssh2")]
|
||||
#[error("No space available on filesystem")]
|
||||
NoSpaceOnFilesystem = 14,
|
||||
#[cfg(feature = "ssh2")]
|
||||
#[error("Quota exceeded")]
|
||||
QuotaExceeded = 15,
|
||||
#[cfg(feature = "ssh2")]
|
||||
#[error("Unknown principal")]
|
||||
UnknownPrincipal = 16,
|
||||
#[cfg(feature = "ssh2")]
|
||||
#[error("Filesystem lock conflict")]
|
||||
LockConflict = 17,
|
||||
#[cfg(feature = "ssh2")]
|
||||
#[error("Directory is not empty")]
|
||||
DirNotEmpty = 18,
|
||||
#[cfg(feature = "ssh2")]
|
||||
#[error("Operation attempted against a path that is not a directory")]
|
||||
NotADirectory = 19,
|
||||
#[cfg(feature = "ssh2")]
|
||||
#[error("Filename invalid")]
|
||||
InvalidFilename = 20,
|
||||
#[cfg(feature = "ssh2")]
|
||||
#[error("Symlink loop encountered")]
|
||||
LinkLoop = 21,
|
||||
}
|
||||
@ -90,13 +98,23 @@ impl TryFrom<i32> for SftpError {
|
||||
11 => Ok(Self::FileAlreadyExists),
|
||||
12 => Ok(Self::WriteProtect),
|
||||
13 => Ok(Self::NoMedia),
|
||||
|
||||
// Errors only available with ssh2
|
||||
#[cfg(feature = "ssh2")]
|
||||
14 => Ok(Self::NoSpaceOnFilesystem),
|
||||
#[cfg(feature = "ssh2")]
|
||||
15 => Ok(Self::QuotaExceeded),
|
||||
#[cfg(feature = "ssh2")]
|
||||
16 => Ok(Self::UnknownPrincipal),
|
||||
#[cfg(feature = "ssh2")]
|
||||
17 => Ok(Self::LockConflict),
|
||||
#[cfg(feature = "ssh2")]
|
||||
18 => Ok(Self::DirNotEmpty),
|
||||
#[cfg(feature = "ssh2")]
|
||||
19 => Ok(Self::NotADirectory),
|
||||
#[cfg(feature = "ssh2")]
|
||||
20 => Ok(Self::InvalidFilename),
|
||||
#[cfg(feature = "ssh2")]
|
||||
21 => Ok(Self::LinkLoop),
|
||||
|
||||
// Unsupported codes get reflected back
|
||||
@ -105,6 +123,7 @@ impl TryFrom<i32> for SftpError {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
impl TryFrom<ssh2::Error> for SftpError {
|
||||
type Error = ssh2::Error;
|
||||
|
||||
@ -119,6 +138,7 @@ impl TryFrom<ssh2::Error> for SftpError {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
impl TryFrom<ssh2::ErrorCode> for SftpError {
|
||||
type Error = ssh2::ErrorCode;
|
||||
|
||||
|
@ -4,9 +4,7 @@ use crate::sftp::file::{File, FileRequest};
|
||||
use crate::sftp::types::{Metadata, OpenFileType, OpenOptions, RenameOptions, WriteMode};
|
||||
use camino::Utf8PathBuf;
|
||||
use error::SftpError;
|
||||
use libssh_rs as libssh;
|
||||
use smol::channel::{bounded, RecvError, Sender};
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
@ -41,11 +39,13 @@ pub enum SftpChannelError {
|
||||
#[error("Failed to receive response: {}", .0)]
|
||||
RecvFailed(#[from] RecvError),
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
#[error("Library-specific error: {}", .0)]
|
||||
Ssh2(#[source] ssh2::Error),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
#[error("Library-specific error: {}", .0)]
|
||||
LibSsh(#[source] libssh::Error),
|
||||
LibSsh(#[source] libssh_rs::Error),
|
||||
|
||||
#[error("Not Implemented")]
|
||||
NotImplemented,
|
||||
@ -409,8 +409,10 @@ pub(crate) struct Rename {
|
||||
pub opts: RenameOptions,
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
impl From<ssh2::Error> for SftpChannelError {
|
||||
fn from(err: ssh2::Error) -> Self {
|
||||
use std::convert::TryFrom;
|
||||
match SftpError::try_from(err) {
|
||||
Ok(x) => Self::Sftp(x),
|
||||
Err(x) => Self::Ssh2(x),
|
||||
@ -418,8 +420,9 @@ impl From<ssh2::Error> for SftpChannelError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<libssh::Error> for SftpChannelError {
|
||||
fn from(err: libssh::Error) -> Self {
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
impl From<libssh_rs::Error> for SftpChannelError {
|
||||
fn from(err: libssh_rs::Error) -> Self {
|
||||
Self::LibSsh(err)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
use bitflags::bitflags;
|
||||
use libssh_rs as libssh;
|
||||
use std::time::SystemTime;
|
||||
|
||||
bitflags! {
|
||||
struct FileTypeFlags: u32 {
|
||||
@ -251,6 +249,7 @@ impl Default for RenameOptions {
|
||||
}
|
||||
|
||||
/// Contains libssh2-specific implementations
|
||||
#[cfg(feature = "ssh2")]
|
||||
mod ssh2_impl {
|
||||
use super::*;
|
||||
use ::ssh2::{
|
||||
@ -351,65 +350,71 @@ mod ssh2_impl {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<libssh::FileType> for FileType {
|
||||
fn from(ft: libssh::FileType) -> Self {
|
||||
match ft {
|
||||
libssh::FileType::Directory => Self::Dir,
|
||||
libssh::FileType::Regular => Self::File,
|
||||
libssh::FileType::Symlink => Self::Symlink,
|
||||
_ => Self::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
mod libssh_impl {
|
||||
use super::*;
|
||||
use std::time::SystemTime;
|
||||
|
||||
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 {
|
||||
ty: stat
|
||||
.file_type()
|
||||
.map(FileType::from)
|
||||
.unwrap_or(FileType::Other),
|
||||
permissions: stat.permissions().map(FilePermissions::from_unix_mode),
|
||||
size: stat.len(),
|
||||
uid: stat.uid(),
|
||||
gid: stat.gid(),
|
||||
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))
|
||||
impl From<libssh_rs::FileType> for FileType {
|
||||
fn from(ft: libssh_rs::FileType) -> Self {
|
||||
match ft {
|
||||
libssh_rs::FileType::Directory => Self::Dir,
|
||||
libssh_rs::FileType::Regular => Self::File,
|
||||
libssh_rs::FileType::Symlink => Self::Symlink,
|
||||
_ => Self::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_rs::Metadata> for Metadata {
|
||||
fn from(stat: libssh_rs::Metadata) -> Self {
|
||||
Self {
|
||||
ty: stat
|
||||
.file_type()
|
||||
.map(FileType::from)
|
||||
.unwrap_or(FileType::Other),
|
||||
permissions: stat.permissions().map(FilePermissions::from_unix_mode),
|
||||
size: stat.len(),
|
||||
uid: stat.uid(),
|
||||
gid: stat.gid(),
|
||||
accessed: stat.accessed().map(sys_time_to_unix),
|
||||
modified: stat.modified().map(sys_time_to_unix),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<libssh_rs::SetAttributes> for Metadata {
|
||||
fn into(self) -> libssh_rs::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_rs::SetAttributes {
|
||||
size,
|
||||
uid_gid,
|
||||
permissions,
|
||||
atime_mtime,
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
libssh::SetAttributes {
|
||||
size,
|
||||
uid_gid,
|
||||
permissions,
|
||||
atime_mtime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
use crate::dirwrap::DirWrap;
|
||||
use crate::filewrap::FileWrap;
|
||||
use crate::sftp::types::{Metadata, OpenOptions, RenameOptions, WriteMode};
|
||||
use crate::sftp::{SftpChannelError, SftpChannelResult};
|
||||
use crate::sftp::types::{Metadata, OpenOptions, RenameOptions};
|
||||
use crate::sftp::SftpChannelResult;
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use libc::{O_APPEND, O_RDONLY, O_RDWR, O_WRONLY};
|
||||
use libssh_rs as libssh;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
pub(crate) enum SftpWrap {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Ssh2(ssh2::Sftp),
|
||||
LibSsh(libssh::Sftp),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
LibSsh(libssh_rs::Sftp),
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssh2")]
|
||||
fn pathconv(path: std::path::PathBuf) -> SftpChannelResult<Utf8PathBuf> {
|
||||
use crate::sftp::SftpChannelError;
|
||||
use std::convert::TryFrom;
|
||||
Ok(Utf8PathBuf::try_from(path).map_err(|x| {
|
||||
SftpChannelError::from(std::io::Error::new(std::io::ErrorKind::InvalidData, x))
|
||||
})?)
|
||||
@ -21,6 +24,7 @@ fn pathconv(path: std::path::PathBuf) -> SftpChannelResult<Utf8PathBuf> {
|
||||
impl SftpWrap {
|
||||
pub fn open(&self, filename: &Utf8Path, opts: OpenOptions) -> SftpChannelResult<FileWrap> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => {
|
||||
let flags: ssh2::OpenFlags = opts.into();
|
||||
let mode = opts.mode;
|
||||
@ -29,7 +33,12 @@ impl SftpWrap {
|
||||
let file = sftp.open_mode(filename.as_std_path(), flags, mode, open_type)?;
|
||||
Ok(FileWrap::Ssh2(file))
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => {
|
||||
use crate::sftp::types::WriteMode;
|
||||
use libc::{O_APPEND, O_RDONLY, O_RDWR, O_WRONLY};
|
||||
use std::convert::TryInto;
|
||||
let accesstype = match (opts.write, opts.read) {
|
||||
(Some(WriteMode::Append), true) => O_RDWR | O_APPEND,
|
||||
(Some(WriteMode::Append), false) => O_WRONLY | O_APPEND,
|
||||
@ -47,43 +56,64 @@ impl SftpWrap {
|
||||
|
||||
pub fn symlink(&self, path: &Utf8Path, target: &Utf8Path) -> SftpChannelResult<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => Ok(sftp.symlink(path.as_std_path(), target.as_std_path())?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => Ok(sftp.symlink(path.as_str(), target.as_str())?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_link(&self, filename: &Utf8Path) -> SftpChannelResult<Utf8PathBuf> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => Ok(pathconv(sftp.readlink(filename.as_std_path())?)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => Ok(sftp.read_link(filename.as_str())?.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn canonicalize(&self, filename: &Utf8Path) -> SftpChannelResult<Utf8PathBuf> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => Ok(pathconv(sftp.realpath(filename.as_std_path())?)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => Ok(sftp.canonicalize(filename.as_str())?.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unlink(&self, filename: &Utf8Path) -> SftpChannelResult<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => Ok(sftp.unlink(filename.as_std_path())?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => Ok(sftp.remove_file(filename.as_str())?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_dir(&self, filename: &Utf8Path) -> SftpChannelResult<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => Ok(sftp.rmdir(filename.as_std_path())?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => Ok(sftp.remove_dir(filename.as_str())?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_dir(&self, filename: &Utf8Path, mode: i32) -> SftpChannelResult<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
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())?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => {
|
||||
use std::convert::TryInto;
|
||||
Ok(sftp.create_dir(filename.as_str(), mode.try_into().unwrap())?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,19 +121,25 @@ impl SftpWrap {
|
||||
&self,
|
||||
src: &Utf8Path,
|
||||
dest: &Utf8Path,
|
||||
opts: RenameOptions,
|
||||
#[cfg_attr(not(feature = "ssh2"), allow(unused_variables))] opts: RenameOptions,
|
||||
) -> SftpChannelResult<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => {
|
||||
Ok(sftp.rename(src.as_std_path(), dest.as_std_path(), Some(opts.into()))?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => Ok(sftp.rename(src.as_str(), dest.as_str())?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symlink_metadata(&self, filename: &Utf8Path) -> SftpChannelResult<Metadata> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => Ok(sftp.lstat(filename.as_std_path()).map(Metadata::from)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => Ok(sftp
|
||||
.symlink_metadata(filename.as_str())
|
||||
.map(Metadata::from)?),
|
||||
@ -112,16 +148,22 @@ impl SftpWrap {
|
||||
|
||||
pub fn metadata(&self, filename: &Utf8Path) -> SftpChannelResult<Metadata> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => Ok(sftp.stat(filename.as_std_path()).map(Metadata::from)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => Ok(sftp.metadata(filename.as_str()).map(Metadata::from)?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_metadata(&self, filename: &Utf8Path, metadata: Metadata) -> SftpChannelResult<()> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => Ok(sftp.setstat(filename.as_std_path(), metadata.into())?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => {
|
||||
let attr: libssh::SetAttributes = metadata.into();
|
||||
let attr: libssh_rs::SetAttributes = metadata.into();
|
||||
Ok(sftp.set_metadata(filename.as_str(), &attr)?)
|
||||
}
|
||||
}
|
||||
@ -129,13 +171,17 @@ impl SftpWrap {
|
||||
|
||||
pub fn open_dir(&self, filename: &Utf8Path) -> SftpChannelResult<DirWrap> {
|
||||
match self {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => Ok(sftp.opendir(filename.as_std_path()).map(DirWrap::Ssh2)?),
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
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 {
|
||||
#[cfg(feature = "ssh2")]
|
||||
Self::Ssh2(sftp) => {
|
||||
let entries = sftp.readdir(filename.as_std_path())?;
|
||||
let mut mapped_entries = vec![];
|
||||
@ -146,6 +192,8 @@ impl SftpWrap {
|
||||
|
||||
Ok(mapped_entries)
|
||||
}
|
||||
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Self::LibSsh(sftp) => {
|
||||
let entries = sftp.read_dir(filename.as_str())?;
|
||||
let mut mapped_entries = vec![];
|
||||
|
@ -611,8 +611,9 @@ async fn canonicalize_should_either_return_resolved_path_or_error_if_missing(
|
||||
.await;
|
||||
match result {
|
||||
Ok(_) => {}
|
||||
Err(SftpChannelError::Sftp(SftpError::NoSuchFile))
|
||||
| Err(SftpChannelError::LibSsh(libssh_rs::Error::Sftp(_))) => {}
|
||||
Err(SftpChannelError::Sftp(SftpError::NoSuchFile)) => {}
|
||||
#[cfg(feature = "libssh-rs")]
|
||||
Err(SftpChannelError::LibSsh(libssh_rs::Error::Sftp(_))) => {}
|
||||
x => panic!(
|
||||
"Unexpected result from canonicalize({}: {:?}",
|
||||
missing.path().display(),
|
||||
|
@ -424,6 +424,11 @@ pub async fn session(sshd: &'_ Sshd) -> Session {
|
||||
let mut config = config.for_host("localhost");
|
||||
config.insert("port".to_string(), port.to_string());
|
||||
config.insert("wezterm_ssh_verbose".to_string(), "true".to_string());
|
||||
|
||||
// If libssh-rs is not loaded (but ssh2 is), then we use ssh2 as the backend
|
||||
#[cfg(not(feature = "libssh-rs"))]
|
||||
config.insert("wezterm_ssh_backend".to_string(), "ssh2".to_string());
|
||||
|
||||
config.insert("user".to_string(), USERNAME.to_string());
|
||||
config.insert("identitiesonly".to_string(), "yes".to_string());
|
||||
config.insert(
|
||||
|
Loading…
Reference in New Issue
Block a user