Skip to content

Commit 3bb57cc

Browse files
committed
feat!: Support for multiple SSH backends. Support for libssh.org
Added new feature to enable **libssh2** backend; Added a new feature to enable **libssh** backend. Renamed ssh2-vendored feature to libssh2-vendored. Removed `new`; use `libssh2` and `libssh` constructors instead. By default libssh2 feature is enabled. See README for more details BREAKING CHANGE: Removed `From<SshOpts>`; Removed `new`; use `libssh2` and `libssh` constructors instead; Renamed ssh2-vendored feature to libssh2-vendored
1 parent 9b5e1d9 commit 3bb57cc

File tree

20 files changed

+3260
-2753
lines changed

20 files changed

+3260
-2753
lines changed

.cargo/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[alias]
2+
lint = "clippy --features libssh2 -- -Dwarnings"
3+
test-all = "test --features libssh2"

.github/workflows/linux.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,18 @@ jobs:
1212
steps:
1313
- uses: actions/checkout@v2
1414
- name: Install dependencies
15-
run: sudo apt update && sudo apt install -y libssh2-1-dev libssl-dev
15+
run: sudo apt update && sudo apt install -y libssh2-1-dev libssh-dev libssl-dev
1616
- uses: dtolnay/rust-toolchain@stable
1717
with:
1818
toolchain: stable
1919
components: rustfmt, clippy
2020
- name: Build
2121
run: cargo build --all-features
22-
- name: Build (unsecure)
23-
run: cargo build --no-default-features
2422
- name: Format
2523
run: cargo fmt --all -- --check
2624
- name: Clippy
27-
run: cargo clippy --features find -- -Dwarnings
25+
run: cargo lint
2826
- name: Run tests
29-
run: cargo test --all-features --features find,github-actions
27+
run: cargo test-all
3028
env:
3129
RUST_LOG: trace

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
- [Changelog](#changelog)
4+
- [0.7.0](#070)
45
- [0.6.4](#064)
56
- [0.6.3](#063)
67
- [0.6.2](#062)
@@ -22,6 +23,17 @@
2223

2324
---
2425

26+
## 0.7.0
27+
28+
Released on 16/08/2025
29+
30+
- **BREAKING**: Support for multiple SSH backends:
31+
- Added new feature to enable **libssh2** backend:
32+
- Use `libssh2` feature to enable the backend
33+
- Use `libssh2-vendored` to build the backend with vendored libssh2
34+
- Removed `new`; use `libssh2` and `libssh` constructors instead.
35+
- Removed `From<SshOpts>`
36+
2537
## 0.6.4
2638

2739
Released on 15/08/2025

Cargo.toml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ license = "MIT"
1010
name = "remotefs-ssh"
1111
readme = "README.md"
1212
repository = "https://github.com/remotefs-rs/remotefs-rs-ssh"
13-
version = "0.6.4"
13+
version = "0.7.0"
1414
rust-version = "1.85.1"
1515

1616
[dependencies]
@@ -19,7 +19,7 @@ lazy-regex = "3"
1919
log = "^0.4"
2020
remotefs = "^0.3"
2121
ssh2-config = "^0.6"
22-
ssh2 = "^0.9"
22+
ssh2 = { version = "^0.9", optional = true }
2323

2424
[dev-dependencies]
2525
env_logger = "^0.11"
@@ -29,13 +29,11 @@ tempfile = "^3"
2929
testcontainers = { version = "0.25", features = ["blocking"] }
3030

3131
[features]
32-
default = ["find"]
33-
# misc
32+
default = ["find", "libssh2"]
3433
find = ["remotefs/find"]
34+
libssh2 = ["dep:ssh2"]
35+
libssh2-vendored = ["libssh2", "ssh2/vendored-openssl"]
3536
no-log = ["log/max_level_off"]
36-
ssh2-vendored = ["ssh2/vendored-openssl"]
37-
# tests
38-
github-actions = []
3937

4038
[target."cfg(target_os = \"windows\")"]
4139
[target."cfg(target_os = \"windows\")".dependencies]

README.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<p align="center">~ Remotefs SSH client ~</p>
1212

1313
<p align="center">Developed by <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
14-
<p align="center">Current version: 0.6.4 (15/08/2025)</p>
14+
<p align="center">Current version: 0.7.0 (16/08/2025)</p>
1515

1616
<p align="center">
1717
<a href="https://opensource.org/licenses/MIT"
@@ -72,14 +72,35 @@ First of all, add `remotefs-ssh` to your project dependencies:
7272

7373
```toml
7474
remotefs = "0.3"
75-
remotefs-ssh = "^0.6"
75+
remotefs-ssh = "^0.7"
7676
```
7777

78+
> ![NOTE]
79+
> The library supports multiple ssh backends.
80+
> Currently `libssh2` and `libssh` are supported.
81+
>
82+
> By default the library is using `libssh2`.
83+
84+
### Available backends
85+
86+
Each backend can be set as a feature in your `Cargo.toml`. Multiple backends can be enabled at the same time.
87+
88+
- `libssh2`: The default backend, using the `libssh2` library for SSH connections.
89+
- `libssh`: An alternative backend, using the `libssh` library for SSH connections.
90+
91+
Each backend can be built with the vendored version, using the vendored feature instead:
92+
93+
- `libssh2-vendored`: Build the `libssh2` backend with the vendored version of the library.
94+
- `libssh-vendored`: Build the `libssh` backend with the vendored version of the library.
95+
96+
If the vendored feature is **NOT** provided, you will need to have the corresponding system libraries installed on your machine.
97+
98+
### Other features
99+
78100
these features are supported:
79101

80102
- `find`: enable `find()` method on client (*enabled by default*)
81103
- `no-log`: disable logging. By default, this library will log via the `log` crate.
82-
- `ssh2-vendored`: build with static libssl
83104

84105
---
85106

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ extern crate log;
6666
mod ssh;
6767
pub use ssh::{
6868
KeyMethod, MethodType, ParseRule as SshConfigParseRule, ScpFs, SftpFs, SshAgentIdentity,
69-
SshKeyStorage, SshOpts,
69+
SshKeyStorage, SshOpts, SshSession,
7070
};
7171

7272
// -- utils

src/mock/mod.rs renamed to src/mock.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub fn logger() {
1414
let _ = env_logger::builder()
1515
.filter_level(log::LevelFilter::Trace)
1616
.is_test(true)
17+
.format_source_path(true)
1718
.format_line_number(true)
1819
.try_init();
1920
});

src/ssh/mod.rs renamed to src/ssh.rs

Lines changed: 8 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,20 @@ use std::path::{Path, PathBuf};
77
use std::time::Duration;
88

99
// -- modules
10-
mod commons;
10+
mod backend;
1111
mod config;
1212
#[cfg(test)]
1313
mod container;
14+
mod key_method;
1415
mod scp;
1516
mod sftp;
16-
mod stream;
17-
// -- export
18-
pub use scp::ScpFs;
19-
pub use sftp::SftpFs;
20-
pub use ssh2::MethodType as SshMethodType;
17+
2118
pub use ssh2_config::ParseRule;
22-
use stream::{SftpReadStream, SftpWriteStream};
19+
20+
pub use self::backend::SshSession;
21+
pub use self::key_method::{KeyMethod, MethodType};
22+
pub use self::scp::ScpFs;
23+
pub use self::sftp::SftpFs;
2324

2425
// -- Ssh key storage
2526

@@ -29,30 +30,6 @@ pub trait SshKeyStorage: Send + Sync {
2930
fn resolve(&self, host: &str, username: &str) -> Option<PathBuf>;
3031
}
3132

32-
// -- key method
33-
34-
/// Ssh key method.
35-
/// Defined by [`MethodType`] (see ssh2 docs) and the list of supported algorithms.
36-
pub struct KeyMethod {
37-
pub(crate) method_type: MethodType,
38-
algos: Vec<String>,
39-
}
40-
41-
impl KeyMethod {
42-
/// Instantiates a new [`KeyMethod`]
43-
pub fn new(method_type: MethodType, algos: &[String]) -> Self {
44-
Self {
45-
method_type,
46-
algos: algos.to_vec(),
47-
}
48-
}
49-
50-
/// Get preferred algos in ssh protocol syntax
51-
pub(crate) fn prefs(&self) -> String {
52-
self.algos.join(",")
53-
}
54-
}
55-
5633
// -- ssh options
5734

5835
/// Ssh agent identity
@@ -216,42 +193,6 @@ impl SshOpts {
216193
}
217194
}
218195

219-
impl From<SshOpts> for SftpFs {
220-
fn from(opts: SshOpts) -> Self {
221-
SftpFs::new(opts)
222-
}
223-
}
224-
225-
impl From<SshOpts> for ScpFs {
226-
fn from(opts: SshOpts) -> Self {
227-
ScpFs::new(opts)
228-
}
229-
}
230-
231-
/// Re-implementation of ssh key method, in order to use `Eq`
232-
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
233-
pub enum MethodType {
234-
CryptClientServer,
235-
CryptServerClient,
236-
HostKey,
237-
Kex,
238-
MacClientServer,
239-
MacServerClient,
240-
}
241-
242-
impl From<MethodType> for SshMethodType {
243-
fn from(t: MethodType) -> Self {
244-
match t {
245-
MethodType::CryptClientServer => SshMethodType::CryptCs,
246-
MethodType::CryptServerClient => SshMethodType::CryptSc,
247-
MethodType::HostKey => SshMethodType::HostKey,
248-
MethodType::Kex => SshMethodType::Kex,
249-
MethodType::MacClientServer => SshMethodType::MacCs,
250-
MethodType::MacServerClient => SshMethodType::MacSc,
251-
}
252-
}
253-
}
254-
255196
#[cfg(test)]
256197
mod test {
257198

@@ -332,14 +273,4 @@ mod test {
332273
assert!(opts.key_storage.is_some());
333274
assert_eq!(opts.methods.len(), 1);
334275
}
335-
336-
#[test]
337-
fn should_build_sftp_client() {
338-
let _: SftpFs = SshOpts::new("localhost").into();
339-
}
340-
341-
#[test]
342-
fn should_build_scp_client() {
343-
let _: ScpFs = SshOpts::new("localhost").into();
344-
}
345276
}

src/ssh/backend.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//! Defines the main trait for SSH Backends to be used with the clients and the backend implementations
2+
//! to support different SSH libraries (e.g. libssh2, libssh)
3+
4+
#[cfg(feature = "libssh2")]
5+
mod libssh2;
6+
7+
use std::io::{Read, Write};
8+
use std::path::{Path, PathBuf};
9+
10+
use remotefs::fs::{Metadata, ReadStream, WriteStream};
11+
use remotefs::{File, RemoteResult};
12+
13+
#[cfg(feature = "libssh2")]
14+
pub use self::libssh2::LibSsh2Session;
15+
use crate::SshOpts;
16+
17+
/// SSH session trait.
18+
///
19+
/// Provides SSH channel functions
20+
pub trait SshSession: Sized {
21+
type Sftp: Sftp;
22+
23+
/// Connects to the SSH server and establishes a new [`SshSession`]
24+
fn connect(opts: &SshOpts) -> RemoteResult<Self>;
25+
26+
/// Disconnect from the server
27+
fn disconnect(&self) -> RemoteResult<()>;
28+
29+
/// Get the SSH server banner.
30+
fn banner(&self) -> RemoteResult<Option<String>>;
31+
32+
/// Check if the session is authenticated.
33+
fn authenticated(&self) -> RemoteResult<bool>;
34+
35+
/// Executes a command on the SSH server and returns the exit code and the output.
36+
fn cmd<S>(&mut self, cmd: S) -> RemoteResult<(u32, String)>
37+
where
38+
S: AsRef<str>;
39+
40+
/// Executes a command on the SSH server at a specific path and returns the exit code and the output.
41+
fn cmd_at<S>(&mut self, cmd: S, path: &Path) -> RemoteResult<(u32, String)>
42+
where
43+
S: AsRef<str>,
44+
{
45+
self.cmd(format!("cd \"{}\"; {}", path.display(), cmd.as_ref()))
46+
}
47+
48+
/// Receives a file over SCP.
49+
///
50+
/// Returns a channel can be read from server.
51+
fn scp_recv(&self, path: &Path) -> RemoteResult<Box<dyn Read + Send>>;
52+
53+
/// Send a file over SCP.
54+
///
55+
/// Returns a channel which can be written to send data
56+
fn scp_send(
57+
&self,
58+
remote_path: &Path,
59+
mode: i32,
60+
size: u64,
61+
times: Option<(u64, u64)>,
62+
) -> RemoteResult<Box<dyn Write + Send>>;
63+
64+
/// Returns a SFTP client
65+
fn sftp(&self) -> RemoteResult<Self::Sftp>;
66+
}
67+
68+
/// Sftp provider for a [`SshSession`] implementation via the [`SshSession::sftp`] method.
69+
pub trait Sftp {
70+
/// Creates a new directory at the specified `path` with the given `mode`.
71+
fn mkdir(&self, path: &Path, mode: i32) -> RemoteResult<()>;
72+
73+
/// Opens a file for reading at the specified `path`.
74+
fn open_read(&self, path: &Path) -> RemoteResult<ReadStream>;
75+
76+
/// Open a file for write at the specified `path` with the given `flags`. If the file is created, set the mode.
77+
fn open_write(&self, path: &Path, flags: WriteMode, mode: i32) -> RemoteResult<WriteStream>;
78+
79+
/// Lists the contents of a directory at `dirname` and returns the listed [`File`] for it.
80+
fn readdir<T>(&self, dirname: T) -> RemoteResult<Vec<File>>
81+
where
82+
T: AsRef<Path>;
83+
84+
/// Resolve the real path for `path`.
85+
fn realpath(&self, path: &Path) -> RemoteResult<PathBuf>;
86+
87+
/// Renames a file from `src` to `dest`.
88+
fn rename(&self, src: &Path, dest: &Path) -> RemoteResult<()>;
89+
90+
/// Removes a directory at `path`.
91+
fn rmdir(&self, path: &Path) -> RemoteResult<()>;
92+
93+
/// Set the [`Metadata`] for a file at `path`.
94+
fn setstat(&self, path: &Path, metadata: Metadata) -> RemoteResult<()>;
95+
96+
/// Get the [`File`] metadata for a file.
97+
fn stat(&self, filename: &Path) -> RemoteResult<File>;
98+
99+
/// Creates a symlink at `path` pointing to `target`.
100+
fn symlink(&self, path: &Path, target: &Path) -> RemoteResult<()>;
101+
102+
/// Deletes a file at `path`.
103+
fn unlink(&self, path: &Path) -> RemoteResult<()>;
104+
}
105+
106+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107+
/// Open modes for reading and writing files.
108+
pub enum WriteMode {
109+
Append,
110+
Truncate,
111+
}

0 commit comments

Comments
 (0)