From 7843b53cfe731019107a2dd6305dc4c6000294d5 Mon Sep 17 00:00:00 2001 From: Hannes de Jager Date: Wed, 11 Feb 2026 23:14:42 +0100 Subject: [PATCH] Let back-ends depend on an SPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This split moves the backend-facing API surface into a dedicated crate such that unftp-sbe-* and unftp-auth-* back-ends/plugins/extentions only depend on this new crate called `unftp-core`. The name reflects its role as the common dependency and contract for libunftp and all backend implementations, while avoiding lib* or Java-ish SPI-style naming that is uncommon in the Rust ecosystem. This keeps the public surface clear, idiomatic, and consistent with existing unftp-* crates. Why this change: - Backends shouldn’t force libunftp feature flags or crypto provider choices which was becoming awkward. - A stable interface crate lets backends evolve independently of server internals i.e. back-ends don't need to go to a new version if some server implementation bug was fixed. - Smaller dependency graphs for backends reduce compile times and rebuild scope What I did: - Introduced the `unftp-core` crate with auth/storage traits, errors, and helper types - Switched auth and storage backends to depend on `unftp-core`. - Updated examples and docs to use `ServerBuilder` instead of `ServerExt` - `ServerExt` unfortunately had to go cause it caused a dependency for back-ends on libunftp again. --- CHANGELOG.md | 8 + Cargo.toml | 17 +- README.md | 13 +- RELEASE-CHECKLIST.md | 10 +- crates/unftp-auth-jsonfile/Cargo.toml | 12 +- .../examples/jsonfile_auth.rs | 6 +- crates/unftp-auth-jsonfile/src/lib.rs | 24 +-- crates/unftp-auth-jsonfile/tests/main.rs | 2 +- crates/unftp-auth-pam/Cargo.toml | 9 +- crates/unftp-auth-pam/src/lib.rs | 6 +- crates/unftp-auth-rest/Cargo.toml | 13 +- crates/unftp-auth-rest/examples/rest.rs | 6 +- crates/unftp-auth-rest/src/lib.rs | 2 +- crates/unftp-core/Cargo.toml | 29 +++ crates/unftp-core/README.md | 19 ++ .../unftp-core/src}/auth/authenticator.rs | 4 +- crates/unftp-core/src/auth/mod.rs | 70 +++++++ {src => crates/unftp-core/src}/auth/user.rs | 4 +- crates/unftp-core/src/lib.rs | 6 + .../unftp-core/src}/storage/error.rs | 0 crates/unftp-core/src/storage/mod.rs | 167 +++++++++++++++++ .../src}/storage/storage_backend.rs | 6 +- crates/unftp-sbe-fs/Cargo.toml | 10 +- crates/unftp-sbe-fs/README.md | 9 +- crates/unftp-sbe-fs/examples/basic.rs | 6 +- .../unftp-sbe-fs/examples/cap-ftpd-worker.rs | 4 +- crates/unftp-sbe-fs/examples/cap-ftpd.rs | 6 +- crates/unftp-sbe-fs/examples/pooled.rs | 6 +- crates/unftp-sbe-fs/examples/proxyprotocol.rs | 6 +- crates/unftp-sbe-fs/src/ext.rs | 30 --- crates/unftp-sbe-fs/src/lib.rs | 14 +- crates/unftp-sbe-fs/src/tests.rs | 2 +- crates/unftp-sbe-fs/tests/main.rs | 13 +- crates/unftp-sbe-gcs/Cargo.toml | 11 +- crates/unftp-sbe-gcs/README.md | 37 +--- crates/unftp-sbe-gcs/src/ext.rs | 32 ---- crates/unftp-sbe-gcs/src/gcs_client.rs | 2 +- crates/unftp-sbe-gcs/src/lib.rs | 40 +--- crates/unftp-sbe-gcs/src/object_metadata.rs | 4 +- crates/unftp-sbe-gcs/src/response_body.rs | 4 +- crates/unftp-sbe-gcs/src/workload_identity.rs | 2 +- src/auth/anonymous.rs | 5 +- src/auth/mod.rs | 85 +-------- src/auth/pipeline.rs | 11 +- src/lib.rs | 11 +- src/server/controlchan/commands/abor.rs | 14 +- src/server/controlchan/commands/acct.rs | 14 +- src/server/controlchan/commands/ccc.rs | 4 +- src/server/controlchan/commands/pass.rs | 28 ++- src/server/controlchan/commands/rnto.rs | 14 +- src/server/controlchan/commands/user.rs | 27 ++- src/server/controlchan/control_loop.rs | 2 +- src/server/ftpserver.rs | 176 +++++++++--------- src/server/ftpserver/chosen.rs | 6 +- src/server/ftpserver/mode/pooled.rs | 4 +- src/server/ftpserver/mode/proxy.rs | 4 +- src/server/session.rs | 10 +- src/server/switchboard.rs | 4 +- src/storage/mod.rs | 175 +---------------- tests/common.rs | 8 +- 60 files changed, 605 insertions(+), 668 deletions(-) create mode 100644 crates/unftp-core/Cargo.toml create mode 100644 crates/unftp-core/README.md rename {src => crates/unftp-core/src}/auth/authenticator.rs (98%) create mode 100644 crates/unftp-core/src/auth/mod.rs rename {src => crates/unftp-core/src}/auth/user.rs (97%) create mode 100644 crates/unftp-core/src/lib.rs rename {src => crates/unftp-core/src}/storage/error.rs (100%) create mode 100644 crates/unftp-core/src/storage/mod.rs rename {src => crates/unftp-core/src}/storage/storage_backend.rs (98%) delete mode 100644 crates/unftp-sbe-fs/src/ext.rs delete mode 100644 crates/unftp-sbe-gcs/src/ext.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5edca511..8360aca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +### libunftp 0.23.0 + +- **BREAKING**: Introduced `unftp-core` and moved backend-facing auth/storage traits and types there. + Backends now depend on `unftp-core` directly and `libunftp` depends on this core crate. +- **BREAKING**: Updated backend crates to use `unftp-core` and removed `ServerExt` helpers in the + `unftp-sbe-*` crates. Examples now use `ServerBuilder` directly. +- Bump versions for `unftp-auth-*` and `unftp-sbe-*` crates to reflect the API split. + ### libunftp 0.22.0 - [#550](https://github.com/bolcom/libunftp/pull/550) Split Authenticators from the Subject Resolving (UserDetail diff --git a/Cargo.toml b/Cargo.toml index 1f835b38..79c047d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libunftp" -version = "0.22.0" # remember to update html_root_url +version = "0.23.0" # remember to update html_root_url authors = [ "Agoston Horvath ", "Dávid Kosztka ", @@ -13,6 +13,7 @@ authors = [ description = "Extensible, async, cloud orientated FTP(S) server library." documentation = "https://docs.rs/libunftp/" repository = "https://github.com/bolcom/libunftp" +homepage = "https://unftp.rs/" license = "Apache-2.0" readme = "README.md" keywords = ["ftp", "ftps"] @@ -22,6 +23,7 @@ edition = "2024" [workspace] members = [ + "crates/unftp-core", "crates/unftp-auth-jsonfile", "crates/unftp-auth-pam", "crates/unftp-auth-rest", @@ -41,16 +43,19 @@ async-trait = "0.1.88" base64 = "0.22.1" bytes = "1.11.1" chrono = { version = "0.4.43", default-features = false } +derive_more = { version = "2.1.1", features = ["display"] } futures = { version = "0.3.31", default-features = false, features = ["std"] } http-body-util = "0.1.3" hyper = { version = "1.8.1", features = ["client", "http1"] } hyper-rustls = "0.27.7" hyper-util = "0.1.20" lazy_static = "1.5.0" -libunftp = { version = "0.22.0", path = ".", default-features = false } +libunftp = { version = "0.23.0", path = ".", default-features = false } +unftp-core = { version = "0.1.0", path = "crates/unftp-core" } percent-encoding = "2.3.2" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" +thiserror = "2.0.18" tokio = { version = "1.49.0" } tokio-stream = "0.1.18" tokio-util = { version = "0.7.18" } @@ -72,11 +77,11 @@ async-trait.workspace = true bitflags = "2.10.0" bytes.workspace = true chrono = { workspace = true, features = ["clock", "std"] } -derive_more = { version = "2.1.1", features = ["display"] } +derive_more.workspace = true futures-util = { version = "0.3.31", default-features = false, features = ["alloc", "sink"] } getrandom.workspace = true lazy_static.workspace = true -md-5 = "0.10.6" +unftp-core.workspace = true moka = { version = "0.12.13", default-features = false, features = ["sync"] } nix = { version = "0.30.1", default-features = false, features = ["fs"] } prometheus = { version = "0.14.0", default-features = false, optional = true } @@ -84,16 +89,14 @@ proxy-protocol = { version = "0.5.0", optional = true } rustls = { version = "0.23.36", default-features = false } slog = { version = "2.8.2", features = ["max_level_trace", "release_max_level_info"] } slog-stdlog = "4.1.1" -thiserror = "2.0.18" +thiserror.workspace = true tokio = { workspace = true, features = ["macros", "rt", "net", "process", "sync", "io-util", "time"] } tokio-rustls = { version = "0.26.4", default-features = false } tokio-util = { workspace = true, features = ["codec"] } tracing.workspace = true tracing-attributes.workspace = true uuid = { version = "1.20.0", features = ["v4"] } -x509-parser = "0.18.1" dashmap = "6.1.0" -libc = "0.2" [dev-dependencies] libunftp = { path = "." } diff --git a/README.md b/README.md index 83144396..bf4259d7 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,9 @@ To use a specific provider, enable the corresponding feature in your `Cargo.toml ```toml [dependencies] -libunftp = { version = "0.22.0", features = ["ring"] } # Use ring +libunftp = { version = "0.23.0", features = ["ring"] } # Use ring # or -libunftp = { version = "0.22.0", features = ["aws_lc_rs"] } # Use aws-lc-rs (default) +libunftp = { version = "0.23.0", features = ["aws_lc_rs"] } # Use aws-lc-rs (default) ``` The default provider is `aws-lc-rs` for backward compatibility. Choose the provider that best fits your needs: @@ -125,8 +125,8 @@ add. Here we choose the [file system back-end](https://crates.io/crates/unftp-sb ```toml [dependencies] -libunftp = "0.22.0" -unftp-sbe-fs = "0.2" +libunftp = "0.23.0" +unftp-sbe-fs = "0.4.0" tokio = { version = "1", features = ["full"] } ``` @@ -134,12 +134,13 @@ Now you're ready to develop your server! Add the following to `src/main.rs`: ```rust -use unftp_sbe_fs::ServerExt; +use libunftp::ServerBuilder; +use unftp_sbe_fs::Filesystem; #[tokio::main] pub async fn main() { let ftp_home = std::env::temp_dir(); - let server = libunftp::Server::with_fs(ftp_home) + let server = ServerBuilder::new(Box::new(move || Filesystem::new(ftp_home.clone()).unwrap())) .greeting("Welcome to my FTP server") .passive_ports(50000..=65535) .build() diff --git a/RELEASE-CHECKLIST.md b/RELEASE-CHECKLIST.md index 3d5639a8..86cc33ca 100644 --- a/RELEASE-CHECKLIST.md +++ b/RELEASE-CHECKLIST.md @@ -4,7 +4,7 @@ Cargo-edit also covers all the crates in the workspace You can also use `cargo upgrade --dry-run` to just check what is outstanding or this oneliner: `cat Cargo.toml | sed -n '33,56p' | awk '{ print $1 }' | xargs -L1 cargo search --limit=1` -* Update Cargo.toml with the new version number +* Update Cargo.toml with the new version number (including `unftp-core` when releasing core APIs) * Search for the old version number to find references to it in documentation and update those occurrences. Do this for all the crates that will be released * Run `make pr-prep`, ensuring everything is green @@ -20,6 +20,10 @@ or + > Release unftp-core version x.y.x + + or + > Release all * Run `make publish` * Push to GitHub @@ -27,4 +31,8 @@ > libunftp-0.17.1 or > unftp-sbe-fs-0.1.1 + + or + + > unftp-core-0.1.0 * Notify the Telegram channel. diff --git a/crates/unftp-auth-jsonfile/Cargo.toml b/crates/unftp-auth-jsonfile/Cargo.toml index 77d902f0..297c7444 100644 --- a/crates/unftp-auth-jsonfile/Cargo.toml +++ b/crates/unftp-auth-jsonfile/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "unftp-auth-jsonfile" -version = "0.3.7" +version = "0.4.0" description = "An authentication back-end for libunftp that authenticates against credentials in JSON format" authors = [ "Agoston Horvath ", @@ -17,18 +17,13 @@ homepage = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-auth-jso repository = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-auth-jsonfile" readme = "README.md" -[features] -default = ["aws_lc_rs"] -aws_lc_rs = ["libunftp/aws_lc_rs"] -ring = ["libunftp/ring"] - [dependencies] async-trait.workspace = true base64.workspace = true bytes.workspace = true ipnet = "2.11.0" iprange = "0.6.7" -libunftp.workspace = true +unftp-core.workspace = true ring = "0.17.14" serde.workspace = true serde_json.workspace = true @@ -39,9 +34,10 @@ valid = "0.3.1" flate2 = "1.1.9" [dev-dependencies] +libunftp.workspace = true pretty_env_logger = "0.5.0" tokio = { workspace = true, features = ["macros"] } -unftp-sbe-fs = { version = "0.3.0", path = "../unftp-sbe-fs" } +unftp-sbe-fs = { version = "0.4.0", path = "../unftp-sbe-fs" } [lints] workspace = true diff --git a/crates/unftp-auth-jsonfile/examples/jsonfile_auth.rs b/crates/unftp-auth-jsonfile/examples/jsonfile_auth.rs index 05aa9ba5..871437ff 100644 --- a/crates/unftp-auth-jsonfile/examples/jsonfile_auth.rs +++ b/crates/unftp-auth-jsonfile/examples/jsonfile_auth.rs @@ -1,8 +1,9 @@ //! Shows how to use the JSON file authenticator +use libunftp::ServerBuilder; use std::sync::Arc; use unftp_auth_jsonfile::JsonFileAuthenticator; -use unftp_sbe_fs::ServerExt; +use unftp_sbe_fs::Filesystem; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { @@ -11,7 +12,8 @@ async fn main() -> Result<(), Box> { let authenticator = JsonFileAuthenticator::from_file(String::from("credentials.json"))?; let addr = "127.0.0.1:2121"; - let server = libunftp::Server::with_fs(std::env::temp_dir()) + let root = std::env::temp_dir(); + let server = ServerBuilder::new(Box::new(move || Filesystem::new(root.clone()).unwrap())) .authenticator(Arc::new(authenticator)) .build() .unwrap(); diff --git a/crates/unftp-auth-jsonfile/src/lib.rs b/crates/unftp-auth-jsonfile/src/lib.rs index 811a852b..96ab909e 100644 --- a/crates/unftp-auth-jsonfile/src/lib.rs +++ b/crates/unftp-auth-jsonfile/src/lib.rs @@ -150,7 +150,6 @@ use bytes::Bytes; use flate2::read::GzDecoder; use ipnet::Ipv4Net; use iprange::IpRange; -use libunftp::auth::{AuthenticationError, Authenticator, Principal}; use ring::{ digest::SHA256_OUTPUT_LEN, pbkdf2::{PBKDF2_HMAC_SHA256, verify}, @@ -159,6 +158,7 @@ use serde::Deserialize; use std::io::prelude::*; use std::{collections::HashMap, fs, num::NonZeroU32, path::Path, time::Duration}; use tokio::time::sleep; +use unftp_core::auth::{AuthenticationError, Authenticator, Principal}; use valid::{Validate, constraint::Length}; #[derive(Deserialize, Clone, Debug)] @@ -331,7 +331,7 @@ impl JsonFileAuthenticator { } } - fn ip_ok(creds: &libunftp::auth::Credentials, actual_creds: &UserCreds) -> bool { + fn ip_ok(creds: &unftp_core::auth::Credentials, actual_creds: &UserCreds) -> bool { match &actual_creds.allowed_ip_ranges { Some(allowed) => match creds.source_ip { std::net::IpAddr::V4(ref ip) => allowed.contains(ip), @@ -345,7 +345,7 @@ impl JsonFileAuthenticator { #[async_trait] impl Authenticator for JsonFileAuthenticator { #[tracing_attributes::instrument] - async fn authenticate(&self, username: &str, creds: &libunftp::auth::Credentials) -> Result { + async fn authenticate(&self, username: &str, creds: &unftp_core::auth::Credentials) -> Result { let res = if let Some(actual_creds) = self.credentials_map.get(username) { let client_cert = &actual_creds.client_cert; let certificate = &creds.certificate_chain.as_ref().and_then(|x| x.first()); @@ -458,9 +458,9 @@ impl Authenticator for JsonFileAuthenticator { mod test { #[allow(unused_imports)] - use libunftp::auth::ChannelEncryptionState; + use unftp_core::auth::ChannelEncryptionState; #[allow(unused_imports)] - use libunftp::auth::ClientCert; + use unftp_core::auth::ClientCert; #[tokio::test] async fn test_json_auth() { @@ -528,7 +528,7 @@ mod test { json_authenticator .authenticate( "dan", - &libunftp::auth::Credentials { + &unftp_core::auth::Credentials { certificate_chain: None, password: Some("".into()), source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), @@ -544,7 +544,7 @@ mod test { match json_authenticator .authenticate( "dan", - &libunftp::auth::Credentials { + &unftp_core::auth::Credentials { certificate_chain: None, password: Some("".into()), source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(128, 0, 0, 1)), @@ -690,7 +690,7 @@ mod test { json_authenticator .authenticate( "alice", - &libunftp::auth::Credentials { + &unftp_core::auth::Credentials { certificate_chain: Some(vec![ClientCert(client_cert.clone())]), password: Some("has a password".into()), source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), @@ -707,7 +707,7 @@ mod test { match json_authenticator .authenticate( "alice", - &libunftp::auth::Credentials { + &unftp_core::auth::Credentials { certificate_chain: None, password: Some("has a password".into()), source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), @@ -725,7 +725,7 @@ mod test { json_authenticator .authenticate( "bob", - &libunftp::auth::Credentials { + &unftp_core::auth::Credentials { certificate_chain: Some(vec![ClientCert(client_cert.clone())]), password: None, source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), @@ -742,7 +742,7 @@ mod test { match json_authenticator .authenticate( "carol", - &libunftp::auth::Credentials { + &unftp_core::auth::Credentials { certificate_chain: Some(vec![ClientCert(client_cert.clone())]), password: None, source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), @@ -760,7 +760,7 @@ mod test { json_authenticator .authenticate( "dean", - &libunftp::auth::Credentials { + &unftp_core::auth::Credentials { certificate_chain: Some(vec![ClientCert(client_cert.clone())]), password: None, source_ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), diff --git a/crates/unftp-auth-jsonfile/tests/main.rs b/crates/unftp-auth-jsonfile/tests/main.rs index a0353675..3f13e3ed 100644 --- a/crates/unftp-auth-jsonfile/tests/main.rs +++ b/crates/unftp-auth-jsonfile/tests/main.rs @@ -1,8 +1,8 @@ #![allow(missing_docs)] -use libunftp::auth::Authenticator; use std::path::PathBuf; use unftp_auth_jsonfile::JsonFileAuthenticator; +use unftp_core::auth::Authenticator; fn input_file_path(filename: String) -> String { let root_dir = std::env::var("CARGO_MANIFEST_DIR").expect("Could not find CARGO_MANIFEST_DIR in environment"); diff --git a/crates/unftp-auth-pam/Cargo.toml b/crates/unftp-auth-pam/Cargo.toml index 9e1dd232..662cd500 100644 --- a/crates/unftp-auth-pam/Cargo.toml +++ b/crates/unftp-auth-pam/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "unftp-auth-pam" -version = "0.2.8" +version = "0.3.0" description = "An authentication back-end for libunftp that authenticates using PAM" authors = [ "Agoston Horvath ", @@ -18,14 +18,9 @@ homepage = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-auth-pam repository = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-auth-pam" readme = "README.md" -[features] -default = ["aws_lc_rs"] -aws_lc_rs = ["libunftp/aws_lc_rs"] -ring = ["libunftp/ring"] - [dependencies] async-trait.workspace = true -libunftp.workspace = true +unftp-core.workspace = true tracing.workspace = true tracing-attributes.workspace = true diff --git a/crates/unftp-auth-pam/src/lib.rs b/crates/unftp-auth-pam/src/lib.rs index ab94a08d..cb6d8b25 100644 --- a/crates/unftp-auth-pam/src/lib.rs +++ b/crates/unftp-auth-pam/src/lib.rs @@ -2,15 +2,15 @@ //! [`Authenticator`] implementation that authenticates against [`PAM`]. //! -//! [`Authenticator`]: libunftp::auth::Authenticator +//! [`Authenticator`]: unftp_core::auth::Authenticator //! [`PAM`]: https://en.wikipedia.org/wiki/Pluggable_authentication_module use async_trait::async_trait; -use libunftp::auth::{AuthenticationError, Authenticator, Credentials, Principal}; +use unftp_core::auth::{AuthenticationError, Authenticator, Credentials, Principal}; /// [`Authenticator`] implementation that authenticates against [`PAM`]. /// -/// [`Authenticator`]: libunftp::auth::Authenticator +/// [`Authenticator`]: unftp_core::auth::Authenticator /// [`PAM`]: https://en.wikipedia.org/wiki/Pluggable_authentication_module #[derive(Debug)] pub struct PamAuthenticator { diff --git a/crates/unftp-auth-rest/Cargo.toml b/crates/unftp-auth-rest/Cargo.toml index 01cec3a8..6df9505b 100644 --- a/crates/unftp-auth-rest/Cargo.toml +++ b/crates/unftp-auth-rest/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "unftp-auth-rest" -version = "0.2.9" +version = "0.3.0" description = "An authentication back-end for libunftp that consumes an HTTP API to authenticate" authors = [ "Agoston Horvath ", @@ -17,30 +17,25 @@ homepage = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-auth-res repository = "https://github.com/bolcom/libunftp/tree/hannes/crates/unftp-auth-rest" readme = "README.md" -[features] -default = ["aws_lc_rs"] -aws_lc_rs = ["libunftp/aws_lc_rs"] -ring = ["libunftp/ring"] - [dependencies] async-trait.workspace = true http-body-util.workspace = true hyper.workspace = true hyper-rustls.workspace = true hyper-util.workspace = true -libunftp.workspace = true +unftp-core.workspace = true percent-encoding.workspace = true regex = "1.12.3" -serde.workspace = true serde_json.workspace = true tokio = { workspace = true, features = ["rt", "net", "sync", "io-util", "time"] } tracing.workspace = true tracing-attributes.workspace = true [dev-dependencies] +libunftp.workspace = true pretty_env_logger = "0.5.0" tokio = { workspace = true, features = ["macros"] } -unftp-sbe-fs = { version = "0.3.0", path = "../unftp-sbe-fs" } +unftp-sbe-fs = { version = "0.4.0", path = "../unftp-sbe-fs" } [lints] workspace = true diff --git a/crates/unftp-auth-rest/examples/rest.rs b/crates/unftp-auth-rest/examples/rest.rs index e40f5b6f..8803d64b 100644 --- a/crates/unftp-auth-rest/examples/rest.rs +++ b/crates/unftp-auth-rest/examples/rest.rs @@ -1,9 +1,10 @@ //! Shows how to use the REST authenticator +use libunftp::ServerBuilder; use std::env; use std::sync::Arc; use unftp_auth_rest::{Builder, RestAuthenticator}; -use unftp_sbe_fs::ServerExt; +use unftp_sbe_fs::Filesystem; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box> { @@ -23,7 +24,8 @@ async fn main() -> Result<(), Box> { .build()?; let addr = "127.0.0.1:2121"; - let server = libunftp::Server::with_fs(std::env::temp_dir()) + let root = std::env::temp_dir(); + let server = ServerBuilder::new(Box::new(move || Filesystem::new(root.clone()).unwrap())) .authenticator(Arc::new(authenticator)) .build() .unwrap(); diff --git a/crates/unftp-auth-rest/src/lib.rs b/crates/unftp-auth-rest/src/lib.rs index 642025f7..6c581976 100644 --- a/crates/unftp-auth-rest/src/lib.rs +++ b/crates/unftp-auth-rest/src/lib.rs @@ -7,10 +7,10 @@ use http_body_util::BodyExt; use hyper::{Method, Request, http::uri::InvalidUri}; use hyper_util::client::legacy::Client; use hyper_util::rt::TokioExecutor; -use libunftp::auth::{AuthenticationError, Authenticator, Credentials, Principal}; use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode}; use regex::Regex; use serde_json::{Value, json}; +use unftp_core::auth::{AuthenticationError, Authenticator, Credentials, Principal}; /// A [libunftp](https://crates.io/crates/libunftp) `Authenticator` /// implementation that authenticates by consuming a JSON REST API. diff --git a/crates/unftp-core/Cargo.toml b/crates/unftp-core/Cargo.toml new file mode 100644 index 00000000..27133d17 --- /dev/null +++ b/crates/unftp-core/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "unftp-core" +version = "0.1.0" +description = "Core traits and types for unFTP backends" +authors = [ + "Hannes de Jager ", + "Rob klein Gunnewiek ", +] +edition = "2024" +license = "Apache-2.0" +keywords = ["libunftp", "unftp", "ftp", "ftps"] +categories = ["network-programming"] +documentation = "https://docs.rs/unftp-core" +homepage = "https://unftp.rs/" +repository = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-core" +readme = "README.md" + +[dependencies] +async-trait.workspace = true +chrono = { workspace = true, default-features = false, features = ["clock", "std"] } +derive_more.workspace = true +libc = "0.2" +md-5 = "0.10.6" +thiserror.workspace = true +tokio = { workspace = true, features = ["io-util"] } +x509-parser = "0.18.1" + +[lints] +workspace = true diff --git a/crates/unftp-core/README.md b/crates/unftp-core/README.md new file mode 100644 index 00000000..8c04a1b5 --- /dev/null +++ b/crates/unftp-core/README.md @@ -0,0 +1,19 @@ +# unftp-core + +[![Crate Version](https://img.shields.io/crates/v/unftp-core.svg)](https://crates.io/crates/unftp-core) +[![API Docs](https://docs.rs/unftp-core/badge.svg)](https://docs.rs/unftp-core) +[![Crate License](https://img.shields.io/crates/l/unftp-core.svg)](https://crates.io/crates/unftp-core) +[![Follow on Telegram](https://img.shields.io/badge/Follow%20on-Telegram-brightgreen.svg)](https://t.me/unftp) + +When you need to FTP, but don't want to. + +![logo](../../logo.png) + +[**Website**](https://unftp.rs) | [**API Docs**](https://docs.rs/unftp-core) | [**libunftp**](https://github.com/bolcom/libunftp) | [**unFTP**](https://github.com/bolcom/unFTP) + +This crate contains the core traits and types for [unFTP](https://unftp.rs/) backends. + +This crate was split of from `libunftp` and defines an API that authentication and storage backend (extention/plug-in) implementations in +the unFTP ecosystem should implement. The [`libunftp`](https://unftp.rs/libunftp/) crate provides the server implementation and depends on this core crate too. + +Existing backend implementations exists on crates.io ([search for `unftp-`](https://crates.io/search?q=unftp-)). To implement your own follow the [API Documentation](https://docs.rs/unftp-core). diff --git a/src/auth/authenticator.rs b/crates/unftp-core/src/auth/authenticator.rs similarity index 98% rename from src/auth/authenticator.rs rename to crates/unftp-core/src/auth/authenticator.rs index 529ba6d8..6a5c0774 100644 --- a/src/auth/authenticator.rs +++ b/crates/unftp-core/src/auth/authenticator.rs @@ -1,4 +1,4 @@ -//! The service provider interface (SPI) for auth +//! Core authentication interfaces. use crate::BoxError; @@ -42,7 +42,7 @@ pub trait Authenticator: Sync + Send + Debug { /// # Example /// /// ```rust -/// use libunftp::auth::Principal; +/// use unftp_core::auth::Principal; /// /// let principal = Principal { /// username: "alice".to_string(), diff --git a/crates/unftp-core/src/auth/mod.rs b/crates/unftp-core/src/auth/mod.rs new file mode 100644 index 00000000..8bd0b6ce --- /dev/null +++ b/crates/unftp-core/src/auth/mod.rs @@ -0,0 +1,70 @@ +//! Contains the [`Authenticator`] and [`UserDetail`] traits used by unftp backends. +//! +//! Pre-made implementations exist on crates.io (search for `unftp-auth-`) and you can define your +//! own implementation to integrate your FTP(S) server with whatever authentication mechanism you +//! need. For example, to define an `Authenticator` that will randomly decide: +//! +//! 1. Declare dependencies on async-trait, tokio, and unftp-core +//! +//! ```toml +//! async-trait = "0.1.89" +//! tokio = { version = "1.49.0", features = ["macros", "rt"] } +//! unftp-core = { path = "../path/to/unftp-core" } +//! ``` +//! +//! 2. Implement the [`Authenticator`] trait and optionally the [`UserDetail`] and [`UserDetailProvider`] traits: +//! +//! ```no_run +//! use unftp_core::auth::{Authenticator, AuthenticationError, Principal, UserDetail, UserDetailProvider, UserDetailError, Credentials}; +//! use async_trait::async_trait; +//! +//! #[derive(Debug)] +//! struct RandomAuthenticator; +//! +//! #[async_trait] +//! impl Authenticator for RandomAuthenticator { +//! async fn authenticate(&self, _username: &str, _creds: &Credentials) -> Result { +//! Ok(Principal { username: _username.to_string() }) +//! } +//! } +//! +//! #[derive(Debug)] +//! struct RandomUser; +//! +//! impl UserDetail for RandomUser {} +//! +//! impl std::fmt::Display for RandomUser { +//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +//! write!(f, "RandomUser") +//! } +//! } +//! +//! #[derive(Debug)] +//! struct RandomUserDetailProvider; +//! +//! #[async_trait] +//! impl UserDetailProvider for RandomUserDetailProvider { +//! type User = RandomUser; +//! +//! async fn provide_user_detail(&self, _principal: &Principal) -> Result { +//! Ok(RandomUser {}) +//! } +//! } +//! ``` +//! +//! 3. Initialize it with the server in your application: +//! +//! ```no_run +//! # use unftp_core::auth::Principal; +//! # #[tokio::main] +//! # async fn main() { +//! # let _principal = Principal { username: "alice".to_string() }; +//! # } +//! ``` +//! + +mod authenticator; +pub use authenticator::{AuthenticationError, Authenticator, ChannelEncryptionState, ClientCert, Credentials, Principal}; + +mod user; +pub use user::{DefaultUser, DefaultUserDetailProvider, UserDetail, UserDetailError, UserDetailProvider}; diff --git a/src/auth/user.rs b/crates/unftp-core/src/auth/user.rs similarity index 97% rename from src/auth/user.rs rename to crates/unftp-core/src/auth/user.rs index ea59da6b..4bc4648a 100644 --- a/src/auth/user.rs +++ b/crates/unftp-core/src/auth/user.rs @@ -42,7 +42,7 @@ pub trait UserDetail: Send + Sync + Display + Debug { /// # Example /// /// ```rust -/// use libunftp::auth::{Principal, UserDetail, UserDetailProvider, UserDetailError}; +/// use unftp_core::auth::{Principal, UserDetail, UserDetailProvider, UserDetailError}; /// use async_trait::async_trait; /// /// #[derive(Debug)] @@ -163,7 +163,7 @@ impl Display for DefaultUser { /// ```rust /// # #[tokio::main] /// # async fn main() { -/// use libunftp::auth::{DefaultUserDetailProvider, Principal, UserDetailProvider}; +/// use unftp_core::auth::{DefaultUserDetailProvider, Principal, UserDetailProvider}; /// /// let provider = DefaultUserDetailProvider; /// let principal = Principal { diff --git a/crates/unftp-core/src/lib.rs b/crates/unftp-core/src/lib.rs new file mode 100644 index 00000000..9010dbdb --- /dev/null +++ b/crates/unftp-core/src/lib.rs @@ -0,0 +1,6 @@ +//! Core traits and types for unftp backends. + +pub mod auth; +pub mod storage; + +type BoxError = Box; diff --git a/src/storage/error.rs b/crates/unftp-core/src/storage/error.rs similarity index 100% rename from src/storage/error.rs rename to crates/unftp-core/src/storage/error.rs diff --git a/crates/unftp-core/src/storage/mod.rs b/crates/unftp-core/src/storage/mod.rs new file mode 100644 index 00000000..e434b7a8 --- /dev/null +++ b/crates/unftp-core/src/storage/mod.rs @@ -0,0 +1,167 @@ +//! Contains the [`StorageBackend`] trait that can be implemented to create virtual file systems for libunftp. +//! +//! Pre-made implementations exists on crates.io (search for `unftp-sbe-`) and you can define your +//! own implementation to integrate your FTP(S) server with whatever storage mechanism you prefer. +//! +//! To create a new storage back-end: +//! +//! 1. Declare dependencies on the async-trait, tokio, and unftp-core crates: +//! +//! ```toml +//! async-trait = "0.1.89" +//! tokio = { version = "1.49.0", features = ["full"] } +//! unftp-core = { path = "../path/to/unftp-core" } +//! ``` +//! +//! 2. Implement the [`StorageBackend`] trait and optionally the [`Metadata`] trait: +//! +//! ```no_run +//! use async_trait::async_trait; +//! use unftp_core::{ +//! storage::{Fileinfo, Metadata, Result, StorageBackend}, +//! auth::DefaultUser +//! }; +//! use std::{ +//! fmt::Debug, +//! path::{Path, PathBuf}, +//! time::SystemTime +//! }; +//! +//! #[derive(Debug)] +//! pub struct Vfs {} +//! +//! #[derive(Debug)] +//! pub struct Meta { +//! inner: std::fs::Metadata, +//! } +//! +//! impl Vfs { +//! fn new() -> Vfs { Vfs{} } +//! } +//! +//! #[async_trait] +//! impl unftp_core::storage::StorageBackend for Vfs { +//! type Metadata = Meta; +//! +//! async fn metadata + Send + Debug>( +//! &self, +//! user: &DefaultUser, +//! path: P, +//! ) -> Result { +//! unimplemented!() +//! } +//! +//! async fn list + Send + Debug>( +//! &self, +//! user: &DefaultUser, +//! path: P, +//! ) -> Result>> +//! where +//! >::Metadata: Metadata, +//! { +//! unimplemented!() +//! } +//! +//! async fn get + Send + Debug>( +//! &self, +//! user: &DefaultUser, +//! path: P, +//! start_pos: u64, +//! ) -> Result> { +//! unimplemented!() +//! } +//! +//! async fn put< +//! P: AsRef + Send + Debug, +//! R: tokio::io::AsyncRead + Send + Sync + Unpin + 'static, +//! >( +//! &self, +//! user: &DefaultUser, +//! input: R, +//! path: P, +//! start_pos: u64, +//! ) -> Result { +//! unimplemented!() +//! } +//! +//! async fn del + Send + Debug>( +//! &self, +//! user: &DefaultUser, +//! path: P, +//! ) -> Result<()> { +//! unimplemented!() +//! } +//! +//! async fn mkd + Send + Debug>( +//! &self, +//! user: &DefaultUser, +//! path: P, +//! ) -> Result<()> { +//! unimplemented!() +//! } +//! +//! async fn rename + Send + Debug>( +//! &self, +//! user: &DefaultUser, +//! from: P, +//! to: P, +//! ) -> Result<()> { +//! unimplemented!() +//! } +//! +//! async fn rmd + Send + Debug>( +//! &self, +//! user: &DefaultUser, +//! path: P, +//! ) -> Result<()> { +//! unimplemented!() +//! } +//! +//! async fn cwd + Send + Debug>( +//! &self, +//! user: &DefaultUser, +//! path: P, +//! ) -> Result<()> { +//! unimplemented!() +//! } +//! } +//! +//! impl Metadata for Meta { +//! fn len(&self) -> u64 { +//! self.inner.len() +//! } +//! +//! fn is_dir(&self) -> bool { +//! self.inner.is_dir() +//! } +//! +//! fn is_file(&self) -> bool { +//! self.inner.is_file() +//! } +//! +//! fn is_symlink(&self) -> bool { +//! self.inner.file_type().is_symlink() +//! } +//! +//! fn modified(&self) -> Result { +//! self.inner.modified().map_err(|e| e.into()) +//! } +//! +//! fn gid(&self) -> u32 { +//! 0 +//! } +//! +//! fn uid(&self) -> u32 { +//! 0 +//! } +//! } +//! ``` +//! +//! 3. Initialize it with the server in your application. +//! + +mod error; +pub use error::{Error, ErrorKind}; + +mod storage_backend; +pub use storage_backend::{FEATURE_RESTART, FEATURE_SITEMD5, Fileinfo, Metadata, Permissions, Result, StorageBackend}; diff --git a/src/storage/storage_backend.rs b/crates/unftp-core/src/storage/storage_backend.rs similarity index 98% rename from src/storage/storage_backend.rs rename to crates/unftp-core/src/storage/storage_backend.rs index 7bc7bff6..0a2f6817 100644 --- a/src/storage/storage_backend.rs +++ b/crates/unftp-core/src/storage/storage_backend.rs @@ -1,8 +1,7 @@ //! Defines the service provider interface for storage back-end implementors. -use super::error::Error; +use super::error::{Error, ErrorKind}; use crate::auth::UserDetail; -use crate::storage::ErrorKind; use async_trait::async_trait; use chrono::{ Datelike, @@ -240,7 +239,6 @@ pub trait StorageBackend: Send + Sync + Debug { /// Returns some bytes that make up a directory listing that can immediately be sent to the client. #[allow(clippy::type_complexity)] - #[tracing_attributes::instrument] async fn list_fmt

(&self, user: &User, path: P) -> std::result::Result>, Error> where P: AsRef + Send + Debug, @@ -259,7 +257,6 @@ pub trait StorageBackend: Send + Sync + Debug { } /// Returns directory listing as a vec of strings used for multi line response in the control channel. - #[tracing_attributes::instrument] async fn list_vec

(&self, user: &User, path: P) -> std::result::Result, Error> where P: AsRef + Send + Debug, @@ -274,7 +271,6 @@ pub trait StorageBackend: Send + Sync + Debug { /// Returns some bytes that make up a NLST directory listing (only the basename) that can /// immediately be sent to the client. #[allow(clippy::type_complexity)] - #[tracing_attributes::instrument] async fn nlst

(&self, user: &User, path: P) -> std::result::Result>, std::io::Error> where P: AsRef + Send + Debug, diff --git a/crates/unftp-sbe-fs/Cargo.toml b/crates/unftp-sbe-fs/Cargo.toml index 900b9cd4..98a65fab 100644 --- a/crates/unftp-sbe-fs/Cargo.toml +++ b/crates/unftp-sbe-fs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "unftp-sbe-fs" -version = "0.3.1" +version = "0.4.0" description = "A storage back-end for libunftp, storing files on local disk" authors = [ "Agoston Horvath ", @@ -18,19 +18,13 @@ homepage = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-sbe-fs" repository = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-sbe-fs" readme = "README.md" -[features] -ring = ["libunftp/ring"] -aws_lc_rs = ["libunftp/aws_lc_rs"] -default = ["aws_lc_rs"] - [dependencies] async-trait.workspace = true cfg-if = "1.0" cap-std = "4.0" futures.workspace = true lazy_static.workspace = true -libunftp.workspace = true -path_abs = "0.5.1" +unftp-core.workspace = true tokio = { workspace = true, features = ["rt", "net", "sync", "io-util", "time", "fs"] } tokio-stream.workspace = true tracing.workspace = true diff --git a/crates/unftp-sbe-fs/README.md b/crates/unftp-sbe-fs/README.md index e2dba498..fc93b5eb 100644 --- a/crates/unftp-sbe-fs/README.md +++ b/crates/unftp-sbe-fs/README.md @@ -21,8 +21,8 @@ Add the libunftp and tokio crates to your project's dependencies in `Cargo.toml` ```toml [dependencies] -libunftp = "0.22.0" -unftp-sbe-fs = "0.2.6" +libunftp = "0.23.0" +unftp-sbe-fs = "0.4.0" tokio = { version = "1", features = ["full"] } ``` @@ -30,12 +30,13 @@ Now you're ready to develop your server! Add the following to `src/main.rs`: ```rust -use unftp_sbe_fs::ServerExt; +use libunftp::ServerBuilder; +use unftp_sbe_fs::Filesystem; #[tokio::main] pub async fn main() { let ftp_home = std::env::temp_dir(); - let server = libunftp::Server::with_fs(ftp_home) + let server = ServerBuilder::new(Box::new(move || Filesystem::new(ftp_home.clone()).unwrap())) .greeting("Welcome to my FTP server") .passive_ports(50000..=65535) .build() diff --git a/crates/unftp-sbe-fs/examples/basic.rs b/crates/unftp-sbe-fs/examples/basic.rs index c8974602..c8639c0c 100644 --- a/crates/unftp-sbe-fs/examples/basic.rs +++ b/crates/unftp-sbe-fs/examples/basic.rs @@ -1,13 +1,15 @@ //! The most basic usage -use unftp_sbe_fs::ServerExt; +use libunftp::ServerBuilder; +use unftp_sbe_fs::Filesystem; #[tokio::main(flavor = "current_thread")] async fn main() { pretty_env_logger::init(); let addr = "127.0.0.1:2121"; - let server = libunftp::Server::with_fs(std::env::temp_dir()).build().unwrap(); + let root = std::env::temp_dir(); + let server = ServerBuilder::new(Box::new(move || Filesystem::new(root.clone()).unwrap())).build().unwrap(); println!("Starting ftp server on {}", addr); server.listen(addr).await.unwrap(); diff --git a/crates/unftp-sbe-fs/examples/cap-ftpd-worker.rs b/crates/unftp-sbe-fs/examples/cap-ftpd-worker.rs index 0eaf7548..34b62ef5 100644 --- a/crates/unftp-sbe-fs/examples/cap-ftpd-worker.rs +++ b/crates/unftp-sbe-fs/examples/cap-ftpd-worker.rs @@ -23,9 +23,9 @@ mod auth { }; use async_trait::async_trait; - use libunftp::auth::{AuthenticationError, Authenticator, Principal, UserDetail, UserDetailError, UserDetailProvider}; use serde::Deserialize; use tokio::time::sleep; + use unftp_core::auth::{AuthenticationError, Authenticator, Principal, UserDetail, UserDetailError, UserDetailProvider}; #[derive(Debug)] pub struct User { @@ -116,7 +116,7 @@ mod auth { #[async_trait] impl Authenticator for JsonFileAuthenticator { #[tracing_attributes::instrument] - async fn authenticate(&self, username: &str, creds: &libunftp::auth::Credentials) -> Result { + async fn authenticate(&self, username: &str, creds: &unftp_core::auth::Credentials) -> Result { let res = if let Some(actual_creds) = self.credentials_map.get(username) { let pass_check_result = match &creds.password { Some(given_password) => { diff --git a/crates/unftp-sbe-fs/examples/cap-ftpd.rs b/crates/unftp-sbe-fs/examples/cap-ftpd.rs index 883f2e68..0ed26601 100644 --- a/crates/unftp-sbe-fs/examples/cap-ftpd.rs +++ b/crates/unftp-sbe-fs/examples/cap-ftpd.rs @@ -1,7 +1,8 @@ //! A server that jails each connected session with Capsicum. use std::{ffi::OsString, path::Path, str::FromStr}; -use unftp_sbe_fs::ServerExt; +use libunftp::ServerBuilder; +use unftp_sbe_fs::Filesystem; #[tokio::main(flavor = "current_thread")] async fn main() { @@ -18,7 +19,8 @@ async fn main() { let args: Vec = std::env::args().collect(); let helper = Path::new(&args[0]).parent().unwrap().join("cap-ftpd-worker"); let helper_args = vec![OsString::from_str(auth_file).unwrap()]; - let server = libunftp::Server::with_fs(std::env::temp_dir()) + let root = std::env::temp_dir(); + let server = ServerBuilder::new(Box::new(move || Filesystem::new(root.clone()).unwrap())) .connection_helper(helper.into(), helper_args) .build() .unwrap(); diff --git a/crates/unftp-sbe-fs/examples/pooled.rs b/crates/unftp-sbe-fs/examples/pooled.rs index 32acf78c..03f30412 100644 --- a/crates/unftp-sbe-fs/examples/pooled.rs +++ b/crates/unftp-sbe-fs/examples/pooled.rs @@ -1,13 +1,15 @@ //! Showing how to use pooled mode. -use unftp_sbe_fs::ServerExt; +use libunftp::ServerBuilder; +use unftp_sbe_fs::Filesystem; #[tokio::main] async fn main() { pretty_env_logger::init(); let addr = "127.0.0.1:2121"; - let server = libunftp::Server::with_fs(std::env::temp_dir()) + let root = std::env::temp_dir(); + let server = ServerBuilder::new(Box::new(move || Filesystem::new(root.clone()).unwrap())) .pooled_listener_mode() .passive_ports(5000..=5005) .build() diff --git a/crates/unftp-sbe-fs/examples/proxyprotocol.rs b/crates/unftp-sbe-fs/examples/proxyprotocol.rs index bea34d8a..faedc366 100644 --- a/crates/unftp-sbe-fs/examples/proxyprotocol.rs +++ b/crates/unftp-sbe-fs/examples/proxyprotocol.rs @@ -1,13 +1,15 @@ //! Showing how to use proxy protocol mode. -use unftp_sbe_fs::ServerExt; +use libunftp::ServerBuilder; +use unftp_sbe_fs::Filesystem; #[tokio::main] async fn main() { pretty_env_logger::init(); let addr = "127.0.0.1:2121"; - let server = libunftp::Server::with_fs(std::env::temp_dir()) + let root = std::env::temp_dir(); + let server = ServerBuilder::new(Box::new(move || Filesystem::new(root.clone()).unwrap())) .proxy_protocol_mode(2121) .passive_ports(5000..=5005) .build() diff --git a/crates/unftp-sbe-fs/src/ext.rs b/crates/unftp-sbe-fs/src/ext.rs deleted file mode 100644 index ed10d94d..00000000 --- a/crates/unftp-sbe-fs/src/ext.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::Filesystem; -use libunftp::auth::DefaultUser; -use libunftp::{Server, ServerBuilder}; -use std::path::PathBuf; - -/// Extension trait purely for construction convenience. -pub trait ServerExt { - /// Create a new `Server` with the given filesystem root. - /// - /// # Example - /// - /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; - /// - /// let server = Server::with_fs("/srv/ftp"); - /// ``` - fn with_fs + Send + 'static>(path: P) -> ServerBuilder { - let p = path.into(); - libunftp::ServerBuilder::new(Box::new(move || { - let p = &p.clone(); - match Filesystem::new(p) { - Ok(fs) => fs, - Err(e) => panic!("Cannot open file system root {}: {}", p.display(), e), - } - })) - } -} - -impl ServerExt for Server {} diff --git a/crates/unftp-sbe-fs/src/lib.rs b/crates/unftp-sbe-fs/src/lib.rs index 673eef61..4e4b0ab7 100644 --- a/crates/unftp-sbe-fs/src/lib.rs +++ b/crates/unftp-sbe-fs/src/lib.rs @@ -4,12 +4,13 @@ //! //! ```no_run -//! use unftp_sbe_fs::ServerExt; +//! use libunftp::ServerBuilder; +//! use unftp_sbe_fs::Filesystem; //! //! #[tokio::main] //! pub async fn main() { //! let ftp_home = std::env::temp_dir(); -//! let server = libunftp::Server::with_fs(ftp_home) +//! let server = ServerBuilder::new(Box::new(move || Filesystem::new(ftp_home.clone()).unwrap())) //! .greeting("Welcome to my FTP server") //! .passive_ports(50000..=65535) //! .build() @@ -19,17 +20,12 @@ //! } //! ``` -mod ext; -pub use ext::ServerExt; - mod cap_fs; use async_trait::async_trait; use cfg_if::cfg_if; use futures::{future::TryFutureExt, stream::TryStreamExt}; use lazy_static::lazy_static; -use libunftp::auth::UserDetail; -use libunftp::storage::{Error, ErrorKind, Fileinfo, Metadata, Permissions, Result, StorageBackend}; use std::{ fmt::Debug, io, @@ -38,6 +34,8 @@ use std::{ time::SystemTime, }; use tokio::io::AsyncSeekExt; +use unftp_core::auth::UserDetail; +use unftp_core::storage::{Error, ErrorKind, Fileinfo, Metadata, Permissions, Result, StorageBackend}; #[cfg(unix)] use cap_std::fs::{MetadataExt, PermissionsExt}; @@ -104,7 +102,7 @@ impl StorageBackend for Filesystem { } fn supported_features(&self) -> u32 { - libunftp::storage::FEATURE_RESTART | libunftp::storage::FEATURE_SITEMD5 + unftp_core::storage::FEATURE_RESTART | unftp_core::storage::FEATURE_SITEMD5 } #[tracing_attributes::instrument] diff --git a/crates/unftp-sbe-fs/src/tests.rs b/crates/unftp-sbe-fs/src/tests.rs index 1e513630..a8ba1ed5 100644 --- a/crates/unftp-sbe-fs/src/tests.rs +++ b/crates/unftp-sbe-fs/src/tests.rs @@ -1,8 +1,8 @@ use super::*; -use libunftp::auth::DefaultUser; use pretty_assertions::assert_eq; use std::{fs::File, io::prelude::*}; use tokio::runtime::Runtime; +use unftp_core::auth::DefaultUser; #[test] fn fs_strip_prefixes() { diff --git a/crates/unftp-sbe-fs/tests/main.rs b/crates/unftp-sbe-fs/tests/main.rs index ba18a94a..70f7cc96 100644 --- a/crates/unftp-sbe-fs/tests/main.rs +++ b/crates/unftp-sbe-fs/tests/main.rs @@ -1,14 +1,15 @@ #![allow(missing_docs)] use async_ftp::{FtpStream, types::Result}; -use libunftp::{ServerBuilder, auth::DefaultUser, options::FtpsRequired}; +use libunftp::{ServerBuilder, options::FtpsRequired}; use pretty_assertions::assert_eq; use rstest::{fixture, rstest}; use std::fmt::Debug; use std::path::PathBuf; use std::str; use std::sync::atomic::{AtomicU16, Ordering}; -use unftp_sbe_fs::{Filesystem, ServerExt}; +use unftp_core::auth::DefaultUser; +use unftp_sbe_fs::Filesystem; fn ensure_login_required(r: Result) { let err = r.unwrap_err().to_string(); @@ -32,6 +33,10 @@ struct Harness { addr: String, } +fn fs_builder(root: PathBuf) -> ServerBuilder { + ServerBuilder::new(Box::new(move || Filesystem::new(root.clone()).unwrap())) +} + async fn custom_server_harness(s: S) -> Harness where S: Fn(PathBuf) -> ServerBuilder, @@ -53,7 +58,7 @@ where #[fixture] async fn harness() -> Harness { - custom_server_harness(libunftp::Server::with_fs).await + custom_server_harness(fs_builder).await } #[rstest] @@ -172,7 +177,7 @@ struct FtpsRequireWorksConfig { #[awt] #[tokio::test] async fn ftps_require_works(config: FtpsRequireWorksConfig) { - let s = |path| libunftp::Server::with_fs(path).ftps_required(config.mode_control_chan, config.mode_data_chan); + let s = |path| fs_builder(path).ftps_required(config.mode_control_chan, config.mode_data_chan); let h = custom_server_harness(s).await; let mut ftp_stream = async_ftp::FtpStream::connect(h.addr).await.unwrap(); let result = ftp_stream.login(config.username, "blah").await; diff --git a/crates/unftp-sbe-gcs/Cargo.toml b/crates/unftp-sbe-gcs/Cargo.toml index 750f787b..cc34ff85 100644 --- a/crates/unftp-sbe-gcs/Cargo.toml +++ b/crates/unftp-sbe-gcs/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "unftp-sbe-gcs" description = "A storage back-end for libunftp, storing files in Google Cloud Storage (GCS)" -version = "0.2.9" +version = "0.3.0" authors = [ "Agoston Horvath ", "Dávid Kosztka ", @@ -17,11 +17,6 @@ homepage = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-sbe-gcs" repository = "https://github.com/bolcom/libunftp/tree/master/crates/unftp-sbe-gcs" readme = "README.md" -[features] -default = ["aws_lc_rs"] -aws_lc_rs = ["libunftp/aws_lc_rs"] -ring = ["libunftp/ring"] - [dependencies] async-trait.workspace = true base64.workspace = true @@ -32,14 +27,13 @@ http-body-util.workspace = true hyper.workspace = true hyper-rustls.workspace = true hyper-util.workspace = true -libunftp.workspace = true +unftp-core.workspace = true mime = "0.3.17" percent-encoding.workspace = true serde.workspace = true serde_json.workspace = true time = "0.3.47" tokio = { workspace = true, features = ["rt", "net", "sync", "io-util", "time", "fs"] } -tokio-stream.workspace = true tokio-util = { workspace = true, features = ["codec", "compat"] } tracing.workspace = true tracing-attributes.workspace = true @@ -48,6 +42,7 @@ yup-oauth2 = { version = "12.1.2", default-features = false, features = ["hyper- [dev-dependencies] async_ftp = "6.0.0" clap = { version = "4.5.57", features = ["env"] } +libunftp.workspace = true lazy_static.workspace = true more-asserts = "0.3.1" path_abs = "0.5.1" diff --git a/crates/unftp-sbe-gcs/README.md b/crates/unftp-sbe-gcs/README.md index f19f229c..31f42893 100644 --- a/crates/unftp-sbe-gcs/README.md +++ b/crates/unftp-sbe-gcs/README.md @@ -15,46 +15,27 @@ Add the needed dependencies to Cargo.toml: ```toml [dependencies] -libunftp = "0.22.0" -unftp-sbe-gcs = "0.2.7" +libunftp = "0.23.0" +unftp-sbe-gcs = "0.3.0" tokio = { version = "1", features = ["full"] } ``` And add to src/main.rs: ```rust - use libunftp::Server; -use unftp_sbe_gcs::{ServerExt, options::AuthMethod}; -use std::path::PathBuf; - -#[tokio::main] -pub async fn main() { - let server = Server::with_gcs("my-bucket", PathBuf::from("/unftp"), AuthMethod::WorkloadIdentity(None)) - .greeting("Welcome to my FTP server") - .passive_ports(50000..=65535) - .build() - .unwrap(); - - server.listen("127.0.0.1:2121").await; -} - ``` - -The above example uses the `ServerExt` extension trait. You can also call one of the other constructors of `Server` e.g. - - ```rust - use libunftp::Server; +use libunftp::ServerBuilder; use unftp_sbe_gcs::{CloudStorage, options::AuthMethod}; use std::path::PathBuf; #[tokio::main] pub async fn main() { - let server = libunftp::Server::new( - Box::new(move || CloudStorage::with_bucket_root("my-bucket", PathBuf::from("/ftp-root"), AuthMethod::WorkloadIdentity(None))) + let server = ServerBuilder::new( + Box::new(move || CloudStorage::with_bucket_root("my-bucket", PathBuf::from("/unftp"), AuthMethod::WorkloadIdentity(None))) ) - .greeting("Welcome to my FTP server") - .passive_ports(50000..=65535) - .build() - .unwrap(); + .greeting("Welcome to my FTP server") + .passive_ports(50000..=65535) + .build() + .unwrap(); server.listen("127.0.0.1:2121").await; } diff --git a/crates/unftp-sbe-gcs/src/ext.rs b/crates/unftp-sbe-gcs/src/ext.rs deleted file mode 100644 index 4a5c99e0..00000000 --- a/crates/unftp-sbe-gcs/src/ext.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::CloudStorage; -use crate::options::AuthMethod; -use libunftp::auth::DefaultUser; -use libunftp::{Server, ServerBuilder}; -use std::path::PathBuf; - -/// Extension trait purely for construction convenience. -pub trait ServerExt { - /// Creates a new `Server` with a GCS storage back-end - /// - /// # Example - /// - /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_gcs::{ServerExt, options::AuthMethod}; - /// use std::path::PathBuf; - /// - /// let server = Server::with_gcs("my-bucket", PathBuf::from("/unftp"), AuthMethod::WorkloadIdentity(None)) - /// .build(); - /// ``` - fn with_gcs(bucket: Str, root: PathBuf, auth: AuthHow) -> ServerBuilder - where - Str: Into, - AuthHow: Into, - { - let s = bucket.into(); - let a = auth.into(); - libunftp::ServerBuilder::new(Box::new(move || CloudStorage::with_bucket_root(s.clone(), root.clone(), a.clone()))) - } -} - -impl ServerExt for Server {} diff --git a/crates/unftp-sbe-gcs/src/gcs_client.rs b/crates/unftp-sbe-gcs/src/gcs_client.rs index 1ad72cb7..91df0996 100644 --- a/crates/unftp-sbe-gcs/src/gcs_client.rs +++ b/crates/unftp-sbe-gcs/src/gcs_client.rs @@ -7,7 +7,6 @@ use hyper::{Method, Request, Response, Uri, body::Body, header}; use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; use hyper_util::client::legacy::{Client, connect::HttpConnector}; use hyper_util::rt::TokioExecutor; -use libunftp::storage::{Error, ErrorKind}; use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode}; use serde::de::DeserializeOwned; use std::fmt; @@ -21,6 +20,7 @@ use tokio::io::AsyncRead; use tokio::sync::RwLock; use tokio_util::compat::FuturesAsyncReadCompatExt; use tokio_util::io::ReaderStream; +use unftp_core::storage::{Error, ErrorKind}; use yup_oauth2::{CustomHyperClientBuilder, ServiceAccountAuthenticator}; use crate::{ diff --git a/crates/unftp-sbe-gcs/src/lib.rs b/crates/unftp-sbe-gcs/src/lib.rs index 6fd75f16..0cabef97 100644 --- a/crates/unftp-sbe-gcs/src/lib.rs +++ b/crates/unftp-sbe-gcs/src/lib.rs @@ -9,43 +9,22 @@ //! //! ```toml //! [dependencies] -//! libunftp = "0.22.0" -//! unftp-sbe-gcs = "0.2.8" +//! libunftp = "0.23.0" +//! unftp-sbe-gcs = "0.3.0" //! tokio = { version = "1", features = ["full"] } //! ``` //! //! And add to src/main.rs: //! //! ```no_run -//! use libunftp::Server; -//! use unftp_sbe_gcs::{ServerExt, options::AuthMethod}; -//! use std::path::PathBuf; -//! -//! #[tokio::main] -//! pub async fn main() { -//! let server = Server::with_gcs("my-bucket", PathBuf::from("/unftp"), AuthMethod::WorkloadIdentity(None)) -//! .greeting("Welcome to my FTP server") -//! .passive_ports(50000..=65535) -//! .build() -//! .unwrap(); -//! -//! server.listen("127.0.0.1:2121").await; -//! } -//! ``` -//! -//! This example uses the `ServerExt` extension trait. You can also call one of the other -//! constructors of `Server` e.g. -//! -//! ```no_run //! use libunftp::ServerBuilder; //! use unftp_sbe_gcs::{CloudStorage, options::AuthMethod}; //! use std::path::PathBuf; -//! use std::sync::Arc; //! //! #[tokio::main] //! pub async fn main() { -//! let server = libunftp::ServerBuilder::new( -//! Box::new(move || CloudStorage::with_bucket_root("my-bucket", PathBuf::from("/ftp-root"), AuthMethod::WorkloadIdentity(None))) +//! let server = ServerBuilder::new( +//! Box::new(move || CloudStorage::with_bucket_root("my-bucket", PathBuf::from("/unftp"), AuthMethod::WorkloadIdentity(None))) //! ) //! .greeting("Welcome to my FTP server") //! .passive_ports(50000..=65535) @@ -59,27 +38,22 @@ // FIXME: error mapping from GCS/hyper is minimalistic, mostly PermanentError. Do proper mapping and better reporting (temporary failures too!) -mod ext; mod gcs_client; pub mod object_metadata; pub mod options; mod response_body; mod workload_identity; -pub use ext::ServerExt; - use async_trait::async_trait; use gcs_client::GcsClient; -use libunftp::{ - auth::UserDetail, - storage::{Error, ErrorKind, Fileinfo, Metadata, StorageBackend}, -}; use object_metadata::ObjectMetadata; use options::AuthMethod; use std::{ fmt::Debug, path::{Path, PathBuf}, }; +use unftp_core::auth::UserDetail; +use unftp_core::storage::{Error, ErrorKind, Fileinfo, Metadata, StorageBackend}; /// A [`StorageBackend`] that uses Cloud storage from Google. /// cloned for each controlchan! @@ -130,7 +104,7 @@ impl StorageBackend for CloudStorage { type Metadata = ObjectMetadata; fn supported_features(&self) -> u32 { - libunftp::storage::FEATURE_SITEMD5 + unftp_core::storage::FEATURE_SITEMD5 } #[tracing_attributes::instrument] diff --git a/crates/unftp-sbe-gcs/src/object_metadata.rs b/crates/unftp-sbe-gcs/src/object_metadata.rs index 1dcd20c1..1bfd5df1 100644 --- a/crates/unftp-sbe-gcs/src/object_metadata.rs +++ b/crates/unftp-sbe-gcs/src/object_metadata.rs @@ -1,8 +1,8 @@ //! The Metadata for the CloudStorage -use libunftp::storage::Error; -use libunftp::storage::Metadata; use std::time::SystemTime; +use unftp_core::storage::Error; +use unftp_core::storage::Metadata; /// The struct that implements the Metadata trait for the CloudStorage #[derive(Clone, Debug)] diff --git a/crates/unftp-sbe-gcs/src/response_body.rs b/crates/unftp-sbe-gcs/src/response_body.rs index 5d7e3eab..2c0dd6ff 100644 --- a/crates/unftp-sbe-gcs/src/response_body.rs +++ b/crates/unftp-sbe-gcs/src/response_body.rs @@ -1,12 +1,12 @@ use super::ObjectMetadata; use base64::Engine; use chrono::prelude::*; -use libunftp::storage::{Error, ErrorKind, Fileinfo}; use serde::{Deserialize, de}; use std::fmt::{Display, Write}; use std::path::PathBuf; use std::str::FromStr; use std::time::SystemTime; +use unftp_core::storage::{Error, ErrorKind, Fileinfo}; #[derive(Deserialize, Debug)] pub(crate) struct ResponseBody { @@ -142,7 +142,7 @@ impl Item { #[cfg(test)] mod test { use super::*; - use libunftp::storage::Metadata; + use unftp_core::storage::Metadata; #[test] fn to_metadata() { diff --git a/crates/unftp-sbe-gcs/src/workload_identity.rs b/crates/unftp-sbe-gcs/src/workload_identity.rs index 7d2cbf08..640d83b9 100644 --- a/crates/unftp-sbe-gcs/src/workload_identity.rs +++ b/crates/unftp-sbe-gcs/src/workload_identity.rs @@ -5,7 +5,7 @@ use http_body_util::{BodyExt, Empty}; use hyper::http::header; use hyper::{Method, Request, body::Bytes}; -use libunftp::storage::{Error, ErrorKind}; +use unftp_core::storage::{Error, ErrorKind}; use crate::gcs_client::HttpClientEmpty; diff --git a/src/auth/anonymous.rs b/src/auth/anonymous.rs index 8764a207..b702c145 100644 --- a/src/auth/anonymous.rs +++ b/src/auth/anonymous.rs @@ -1,7 +1,7 @@ //! This module provides an anonymous authenticator -use crate::auth::*; use async_trait::async_trait; +use unftp_core::auth::{AuthenticationError, Authenticator, Credentials, Principal}; /// [`Authenticator`] implementation that simply allows everyone. /// @@ -10,7 +10,8 @@ use async_trait::async_trait; /// ```rust /// # #[tokio::main] /// # async fn main() { -/// use libunftp::auth::{Authenticator, AnonymousAuthenticator, Principal}; +/// use unftp_core::auth::{Authenticator, Principal}; +/// use libunftp::auth::AnonymousAuthenticator; /// /// let my_auth = AnonymousAuthenticator{}; /// assert_eq!(my_auth.authenticate("Finn", &"I ❤️ PB".into()).await.unwrap().username, "Finn"); diff --git a/src/auth/mod.rs b/src/auth/mod.rs index ae113106..63e8c4fe 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -1,86 +1,13 @@ -//! Contains the [`Authenticator`] and [`UserDetail`] -//! traits that are used to extend libunftp's authentication and user detail storage capabilities. -//! -//! Pre-made implementations exists on crates.io (search for `unftp-auth-`) and you can define your -//! own implementation to integrate your FTP(S) server with whatever authentication mechanism you -//! need. For example, to define an `Authenticator` that will randomly decide: -//! -//! 1. Declare a dependency on the async-trait crate -//! -//! ```toml -//! async-trait = "0.1.50" -//! ``` -//! -//! 2. Implement the [`Authenticator`] trait and optionally the [`UserDetail`] and [`UserDetailProvider`] traits: -//! -//! ```no_run -//! use libunftp::auth::{Authenticator, AuthenticationError, Principal, UserDetail, UserDetailProvider, UserDetailError, Credentials}; -//! use async_trait::async_trait; -//! use unftp_sbe_fs::Filesystem; -//! -//! #[derive(Debug)] -//! struct RandomAuthenticator; -//! -//! #[async_trait] -//! impl Authenticator for RandomAuthenticator { -//! async fn authenticate(&self, _username: &str, _creds: &Credentials) -> Result { -//! Ok(Principal { username: _username.to_string() }) -//! } -//! } -//! -//! #[derive(Debug)] -//! struct RandomUser; -//! -//! impl UserDetail for RandomUser {} -//! -//! impl std::fmt::Display for RandomUser { -//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -//! write!(f, "RandomUser") -//! } -//! } -//! -//! #[derive(Debug)] -//! struct RandomUserDetailProvider; -//! -//! #[async_trait] -//! impl UserDetailProvider for RandomUserDetailProvider { -//! type User = RandomUser; -//! -//! async fn provide_user_detail(&self, _principal: &Principal) -> Result { -//! Ok(RandomUser {}) -//! } -//! } -//! ``` -//! -//! 3. Initialize it with the server: -//! -//! ``` -//! # // Make it compile -//! # type RandomAuthenticator = libunftp::auth::AnonymousAuthenticator; -//! # type RandomUserDetailProvider = libunftp::auth::DefaultUserDetailProvider; -//! # type RandomUser = libunftp::auth::DefaultUser; -//! let server = libunftp::ServerBuilder::with_authenticator( -//! Box::new(move || { unftp_sbe_fs::Filesystem::new("/srv/ftp").unwrap() }), -//! std::sync::Arc::new(RandomAuthenticator{}) -//! ) -//! .user_detail_provider(std::sync::Arc::new(libunftp::auth::DefaultUserDetailProvider)) -//! .build() -//! .unwrap(); -//! ``` -//! -//! [`Server`]: ../struct.Server.html -//! [`Authenticator`]: trait.Authenticator.html -//! [`UserDetail`]: trait.UserDetail.html +//! Authentication helpers and built-in implementations. //! +//! Core authentication traits and types live in `unftp-core`. pub mod anonymous; pub use anonymous::AnonymousAuthenticator; -pub(crate) mod authenticator; -#[allow(unused_imports)] -pub use authenticator::{AuthenticationError, Authenticator, ChannelEncryptionState, ClientCert, Credentials, Principal}; - -mod user; -pub use user::{DefaultUser, DefaultUserDetailProvider, UserDetail, UserDetailError, UserDetailProvider}; +pub use unftp_core::auth::{ + AuthenticationError, Authenticator, ChannelEncryptionState, ClientCert, Credentials, DefaultUser, DefaultUserDetailProvider, Principal, UserDetail, + UserDetailError, UserDetailProvider, +}; mod pipeline; pub(crate) use pipeline::AuthenticationPipeline; diff --git a/src/auth/pipeline.rs b/src/auth/pipeline.rs index 8b005fb5..2147e56a 100644 --- a/src/auth/pipeline.rs +++ b/src/auth/pipeline.rs @@ -1,11 +1,8 @@ //! Authentication pipeline that combines authentication and user detail retrieval. -use super::{ - authenticator::{AuthenticationError, Authenticator, Credentials}, - user::{UserDetail, UserDetailProvider}, -}; use std::fmt::Debug; use std::sync::Arc; +use unftp_core::auth::{AuthenticationError, Authenticator, Credentials, UserDetail, UserDetailProvider}; /// Combines an [`Authenticator`] and a [`UserDetailProvider`] to provide a complete /// authentication flow that returns a full [`UserDetail`] implementation. @@ -47,9 +44,9 @@ where // Use the provider to convert Principal to User self.user_provider.provide_user_detail(&principal).await.map_err(|e| match e { - super::user::UserDetailError::UserNotFound { .. } => AuthenticationError::BadUser, - super::user::UserDetailError::Generic(msg) => AuthenticationError::new(msg), - super::user::UserDetailError::ImplPropagated(msg, source) => AuthenticationError::ImplPropagated(msg, source), + unftp_core::auth::UserDetailError::UserNotFound { .. } => AuthenticationError::BadUser, + unftp_core::auth::UserDetailError::Generic(msg) => AuthenticationError::new(msg), + unftp_core::auth::UserDetailError::ImplPropagated(msg, source) => AuthenticationError::ImplPropagated(msg, source), }) } diff --git a/src/lib.rs b/src/lib.rs index 3e04cd14..fea2fd7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![doc(html_root_url = "https://docs.rs/libunftp/0.22.0")] +#![doc(html_root_url = "https://docs.rs/libunftp/0.23.0")] //! libunftp is an extensible, async, cloud orientated FTP(S) server library. //! @@ -17,19 +17,20 @@ //! //! ```toml //! [dependencies] -//! libunftp = "0.22.0" -//! unftp-sbe-fs = "0.3.0" +//! libunftp = "0.23.0" +//! unftp-sbe-fs = "0.4.0" //! tokio = { version = "1", features = ["full"] } //! ``` //! Now you're ready to develop your server! Add the following to src/main.rs: //! //! ```no_run -//! use unftp_sbe_fs::ServerExt; +//! use libunftp::ServerBuilder; +//! use unftp_sbe_fs::Filesystem; //! //! #[tokio::main] //! pub async fn main() { //! let ftp_home = std::env::temp_dir(); -//! let server = libunftp::Server::with_fs(ftp_home) +//! let server = ServerBuilder::new(Box::new(move || Filesystem::new(ftp_home.clone()).unwrap())) //! .greeting("Welcome to my FTP server") //! .passive_ports(50000..=65535) //! .build() diff --git a/src/server/controlchan/commands/abor.rs b/src/server/controlchan/commands/abor.rs index e5855690..85c63060 100644 --- a/src/server/controlchan/commands/abor.rs +++ b/src/server/controlchan/commands/abor.rs @@ -9,16 +9,14 @@ // connection is not to be closed by the server, but the data // connection must be closed. -use crate::auth::UserDetail; -use crate::{ - server::controlchan::{ - Reply, ReplyCode, - error::ControlChanError, - handler::{CommandContext, CommandHandler}, - }, - storage::{Metadata, StorageBackend}, +use crate::server::controlchan::{ + Reply, ReplyCode, + error::ControlChanError, + handler::{CommandContext, CommandHandler}, }; use async_trait::async_trait; +use unftp_core::auth::UserDetail; +use unftp_core::storage::{Metadata, StorageBackend}; #[derive(Debug)] pub struct Abor; diff --git a/src/server/controlchan/commands/acct.rs b/src/server/controlchan/commands/acct.rs index b9dc7220..98b50a40 100644 --- a/src/server/controlchan/commands/acct.rs +++ b/src/server/controlchan/commands/acct.rs @@ -17,16 +17,14 @@ // (pending receipt of the ACCounT command) or discards the // command, respectively. -use crate::auth::UserDetail; -use crate::{ - server::controlchan::{ - Reply, ReplyCode, - error::ControlChanError, - handler::{CommandContext, CommandHandler}, - }, - storage::{Metadata, StorageBackend}, +use crate::server::controlchan::{ + Reply, ReplyCode, + error::ControlChanError, + handler::{CommandContext, CommandHandler}, }; use async_trait::async_trait; +use unftp_core::auth::UserDetail; +use unftp_core::storage::{Metadata, StorageBackend}; #[derive(Debug)] pub struct Acct; diff --git a/src/server/controlchan/commands/ccc.rs b/src/server/controlchan/commands/ccc.rs index 65447101..c5bf0628 100644 --- a/src/server/controlchan/commands/ccc.rs +++ b/src/server/controlchan/commands/ccc.rs @@ -1,10 +1,10 @@ //! The RFC 2228 Clear Command Channel (`CCC`) command -use crate::auth::UserDetail; use crate::server::controlchan::error::ControlChanError; use crate::server::controlchan::handler::{CommandContext, CommandHandler}; use crate::server::{Reply, ReplyCode}; -use crate::storage::{Metadata, StorageBackend}; +use unftp_core::auth::UserDetail; +use unftp_core::storage::{Metadata, StorageBackend}; use async_trait::async_trait; diff --git a/src/server/controlchan/commands/pass.rs b/src/server/controlchan/commands/pass.rs index 2136411f..cb58de93 100644 --- a/src/server/controlchan/commands/pass.rs +++ b/src/server/controlchan/commands/pass.rs @@ -10,24 +10,22 @@ // therefore the responsibility of the user-FTP process to hide // the sensitive password information. -use crate::{ - auth::{ChannelEncryptionState, UserDetail}, - server::{ - chancomms::ControlChanMsg, - controlchan::{ - Reply, ReplyCode, - error::ControlChanError, - handler::{CommandContext, CommandHandler}, - }, - failed_logins::LockState, - password, - session::SessionState, +use crate::server::{ + chancomms::ControlChanMsg, + controlchan::{ + Reply, ReplyCode, + error::ControlChanError, + handler::{CommandContext, CommandHandler}, }, - storage::{Metadata, StorageBackend}, + failed_logins::LockState, + password, + session::SessionState, }; use async_trait::async_trait; use std::{sync::Arc, time::Duration}; use tokio::{sync::mpsc::Sender, time::sleep}; +use unftp_core::auth::{ChannelEncryptionState, UserDetail}; +use unftp_core::storage::{Metadata, StorageBackend}; #[derive(Debug)] pub struct Pass { @@ -69,7 +67,7 @@ where // without this, the REST authenticator hangs when // performing a http call through Hyper let session2clone = args.session.clone(); - let creds = crate::auth::Credentials { + let creds = unftp_core::auth::Credentials { password: Some(pass), source_ip: session.source.ip(), certificate_chain: session.cert_chain.clone(), @@ -134,7 +132,7 @@ where ControlChanMsg::AuthFailed } } - Err(crate::auth::AuthenticationError::BadUser) => { + Err(unftp_core::auth::AuthenticationError::BadUser) => { slog::warn!(logger, "PASS: Login attempt for unknown user {}", username); ControlChanMsg::AuthFailed } diff --git a/src/server/controlchan/commands/rnto.rs b/src/server/controlchan/commands/rnto.rs index 8c949735..04eb9ae6 100644 --- a/src/server/controlchan/commands/rnto.rs +++ b/src/server/controlchan/commands/rnto.rs @@ -1,17 +1,15 @@ //! The RFC 959 Rename To (`RNTO`) command use crate::server::ControlChanMsg; -use crate::storage::{Metadata, StorageBackend}; -use crate::{ - auth::UserDetail, - server::controlchan::{ - Reply, ReplyCode, - error::ControlChanError, - handler::{CommandContext, CommandHandler}, - }, +use crate::server::controlchan::{ + Reply, ReplyCode, + error::ControlChanError, + handler::{CommandContext, CommandHandler}, }; use async_trait::async_trait; use std::{path::PathBuf, sync::Arc}; +use unftp_core::auth::UserDetail; +use unftp_core::storage::{Metadata, StorageBackend}; #[derive(Debug)] pub struct Rnto { diff --git a/src/server/controlchan/commands/user.rs b/src/server/controlchan/commands/user.rs index b55ad3d8..b775db26 100644 --- a/src/server/controlchan/commands/user.rs +++ b/src/server/controlchan/commands/user.rs @@ -1,18 +1,16 @@ -use crate::{ - auth::{AuthenticationError, ChannelEncryptionState, Credentials, UserDetail}, - server::{ - controlchan::{ - Reply, ReplyCode, - error::ControlChanError, - handler::{CommandContext, CommandHandler}, - }, - session::SessionState, +use crate::server::{ + controlchan::{ + Reply, ReplyCode, + error::ControlChanError, + handler::{CommandContext, CommandHandler}, }, - storage::{Metadata, StorageBackend}, + session::SessionState, }; use async_trait::async_trait; use bytes::Bytes; use std::sync::Arc; +use unftp_core::auth::{AuthenticationError, ChannelEncryptionState, Credentials, UserDetail}; +use unftp_core::storage::{Metadata, StorageBackend}; #[derive(Debug)] pub struct User { @@ -95,12 +93,9 @@ where #[cfg(test)] mod tests { - use crate::auth::{AuthenticationError, Authenticator, ClientCert, Credentials, DefaultUser, DefaultUserDetailProvider, Principal, UserDetail}; use crate::server::controlchan::handler::CommandHandler; use crate::server::session::SharedSession; use crate::server::{Command, ControlChanMsg, Reply, ReplyCode, Session, SessionState}; - use crate::storage::{Fileinfo, Result}; - use crate::storage::{Metadata, StorageBackend}; use async_trait::async_trait; use bytes::Bytes; use pretty_assertions::assert_eq; @@ -112,6 +107,8 @@ mod tests { use tokio::io::AsyncRead; use tokio::sync::Mutex; use tokio::sync::mpsc; + use unftp_core::auth::{AuthenticationError, Authenticator, ClientCert, Credentials, DefaultUser, DefaultUserDetailProvider, Principal, UserDetail}; + use unftp_core::storage::{Fileinfo, Metadata, Result, StorageBackend}; #[derive(Debug)] struct Auth { @@ -241,7 +238,7 @@ mod tests { { fn test

(session_arc: SharedSession, auther: Arc, user_provider: Arc

) -> super::CommandContext where - P: crate::auth::UserDetailProvider + Send + Sync + 'static, + P: unftp_core::auth::UserDetailProvider + Send + Sync + 'static, { let (tx, _) = mpsc::channel::(1); let auth_pipeline = Arc::new(crate::auth::AuthenticationPipeline::new(auther, user_provider)); @@ -267,7 +264,7 @@ mod tests { struct Test { short_auth: bool, auth_ok: bool, - cert: Option>, + cert: Option>, expected_reply: ReplyCode, expected_state: SessionState, } diff --git a/src/server/controlchan/control_loop.rs b/src/server/controlchan/control_loop.rs index f8962564..501db6c5 100644 --- a/src/server/controlchan/control_loop.rs +++ b/src/server/controlchan/control_loop.rs @@ -251,7 +251,7 @@ where let s: &ServerConnection = stream.get_ref().1; if let Some(certs) = s.peer_certificates() { let mut session = shared_session.lock().await; - session.cert_chain = Some(certs.iter().map(|c| crate::auth::ClientCert(c.as_ref().to_vec())).collect()); + session.cert_chain = Some(certs.iter().map(|c| unftp_core::auth::ClientCert(c.as_ref().to_vec())).collect()); } Box::new(stream) } diff --git a/src/server/ftpserver.rs b/src/server/ftpserver.rs index 22492e63..b0cb3cbc 100644 --- a/src/server/ftpserver.rs +++ b/src/server/ftpserver.rs @@ -12,24 +12,23 @@ use super::{ shutdown, tls::FtpsConfig, }; +use crate::auth::anonymous::AnonymousAuthenticator; +use crate::notification::{DataListener, PresenceListener, nop::NopListener}; +use crate::options::ActivePassiveMode; +use crate::options::{FailedLoginsPolicy, FtpsClientAuth, TlsFlags}; +use crate::server::shutdown::Notifier; use crate::server::switchboard::Switchboard; -use crate::{ - auth::{Authenticator, DefaultUser, DefaultUserDetailProvider, UserDetail, UserDetailProvider, anonymous::AnonymousAuthenticator}, - notification::{DataListener, PresenceListener, nop::NopListener}, - options::ActivePassiveMode, - options::{FailedLoginsPolicy, FtpsClientAuth, TlsFlags}, - server::shutdown::Notifier, - server::tls, - storage::{Metadata, StorageBackend}, -}; +use crate::server::tls; use options::{DEFAULT_GREETING, DEFAULT_IDLE_SESSION_TIMEOUT_SECS, PassiveHost}; #[cfg(feature = "experimental")] use rustls::ServerConfig; use slog::*; use std::{ffi::OsString, fmt::Debug, future::Future, net::SocketAddr, ops::RangeInclusive, path::PathBuf, pin::Pin, sync::Arc, time::Duration}; +use unftp_core::auth::{Authenticator, DefaultUser, DefaultUserDetailProvider, UserDetail, UserDetailProvider}; +use unftp_core::storage::{Metadata, StorageBackend}; -/// An instance of an FTP(S) server. It aggregates an [`Authenticator`](crate::auth::Authenticator) -/// implementation that will be used for authentication, and a [`StorageBackend`](crate::storage::StorageBackend) +/// An instance of an FTP(S) server. It aggregates an [`Authenticator`](unftp_core::auth::Authenticator) +/// implementation that will be used for authentication, and a [`StorageBackend`](unftp_core::storage::StorageBackend) /// implementation that will be used as the virtual file system. /// /// The server can be started with the [`listen`](crate::Server::listen()) method. @@ -37,19 +36,21 @@ use std::{ffi::OsString, fmt::Debug, future::Future, net::SocketAddr, ops::Range /// # Example /// /// ```rust -/// use libunftp::Server; -/// use unftp_sbe_fs::ServerExt; +/// use libunftp::ServerBuilder; +/// use unftp_sbe_fs::Filesystem; /// use tokio::runtime::Runtime; /// /// let mut rt = Runtime::new().unwrap(); /// rt.spawn(async { -/// let server = Server::with_fs("/srv/ftp").build().unwrap(); +/// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/srv/ftp").unwrap())) +/// .build() +/// .unwrap(); /// server.listen("127.0.0.1:2121").await.unwrap() /// }); /// ``` /// -/// [`Authenticator`]: crate::auth::Authenticator -/// [`StorageBackend`]: storage/trait.StorageBackend.html +/// [`Authenticator`]: unftp_core::auth::Authenticator +/// [`StorageBackend`]: unftp_core::storage::StorageBackend pub struct Server where Storage: StorageBackend, @@ -176,11 +177,12 @@ where /// # Example /// /// ```rust - /// use libunftp::{auth::DefaultUserDetailProvider, Server}; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_core::auth::DefaultUserDetailProvider; + /// use unftp_sbe_fs::Filesystem; /// use std::sync::Arc; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .user_detail_provider(Arc::new(DefaultUserDetailProvider)) /// .build(); /// ``` @@ -285,12 +287,12 @@ where /// # Example /// /// ```rust - /// use libunftp::{auth, auth::AnonymousAuthenticator, Server}; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::{auth, ServerBuilder}; + /// use unftp_sbe_fs::Filesystem; /// use std::sync::Arc; /// /// // Use it in a builder-like pattern: - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .authenticator(Arc::new(auth::AnonymousAuthenticator{})) /// .build(); /// ``` @@ -310,10 +312,10 @@ where /// /// ```rust /// use libunftp::options::ActivePassiveMode; - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .active_passive_mode(ActivePassiveMode::ActiveAndPassive) /// .build(); /// ``` @@ -364,10 +366,10 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .ftps("/srv/unftp/server.certs", "/srv/unftp/server.key"); /// ``` pub fn ftps>(mut self, certs_file: P, key_file: P) -> Self { @@ -386,10 +388,10 @@ where /// ```rust /// # let config = Default::default(); /// use rustls::ServerConfig; - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .ftps_manual(config); /// ``` #[cfg(feature = "experimental")] @@ -404,11 +406,11 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// use libunftp::options::FtpsClientAuth; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .ftps("/srv/unftp/server.certs", "/srv/unftp/server.key") /// .ftps_client_auth(FtpsClientAuth::Require) /// .ftps_trust_store("/srv/unftp/trusted.pem"); @@ -438,10 +440,10 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .ftps("/srv/unftp/server.certs", "/srv/unftp/server.key") /// .ftps_client_auth(true) /// .ftps_trust_store("/srv/unftp/trusted.pem"); @@ -461,11 +463,11 @@ where /// This example enables only TLS v1.3 and allows TLS session resumption with tickets. /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// use libunftp::options::TlsFlags; /// - /// let mut server = Server::with_fs("/tmp") + /// let mut server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .greeting("Welcome to my FTP Server") /// .ftps("/srv/unftp/server.certs", "/srv/unftp/server.key") /// .ftps_tls_flags(TlsFlags::V1_3 | TlsFlags::RESUMPTION_TICKETS); @@ -480,16 +482,16 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// /// // Use it in a builder-like pattern: - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .greeting("Welcome to my FTP Server") /// .build(); // /// // Or instead if you prefer: - /// let mut server = Server::with_fs("/tmp"); + /// let mut server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())); /// server.greeting("Welcome to my FTP Server").build(); /// ``` pub fn greeting(mut self, greeting: &'static str) -> Self { @@ -502,14 +504,14 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// /// // Use it in a builder-like pattern: - /// let mut server = Server::with_fs("/tmp").idle_session_timeout(600); + /// let mut server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())).idle_session_timeout(600); /// /// // Or instead if you prefer: - /// let mut server = Server::with_fs("/tmp"); + /// let mut server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())); /// server.idle_session_timeout(600); /// ``` pub fn idle_session_timeout(mut self, secs: u64) -> Self { @@ -528,14 +530,14 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// /// // Use it in a builder-like pattern: - /// let mut builder = Server::with_fs("/tmp").metrics(); + /// let mut builder = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())).metrics(); /// /// // Or instead if you prefer: - /// let mut builder = Server::with_fs("/tmp"); + /// let mut builder = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())); /// builder.metrics(); /// ``` pub fn metrics(mut self) -> Self { @@ -565,21 +567,21 @@ where /// Using a fixed IP specified as a numeric array: /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .passive_host([127,0,0,1]) /// .build(); /// ``` /// Or the same but more explicitly: /// /// ```rust - /// use libunftp::{Server,options}; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::{ServerBuilder, options}; + /// use unftp_sbe_fs::Filesystem; /// use std::net::Ipv4Addr; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .passive_host(options::PassiveHost::Ip(Ipv4Addr::new(127, 0, 0, 1))) /// .build(); /// ``` @@ -587,10 +589,10 @@ where /// To determine the passive IP from the incoming control connection: /// /// ```rust - /// use libunftp::{Server,options}; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::{ServerBuilder, options}; + /// use unftp_sbe_fs::Filesystem; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .passive_host(options::PassiveHost::FromConnection) /// .build(); /// ``` @@ -598,10 +600,10 @@ where /// Get the IP by resolving a DNS name: /// /// ```rust - /// use libunftp::{Server,options}; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::{ServerBuilder, options}; + /// use unftp_sbe_fs::Filesystem; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .passive_host("ftp.myserver.org") /// .build(); /// ``` @@ -624,15 +626,15 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// /// // Use it in a builder-like pattern: - /// let builder = Server::with_fs("/tmp") + /// let builder = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .passive_ports(49152..=65535); /// /// // Or instead if you prefer: - /// let mut builder = Server::with_fs("/tmp"); + /// let mut builder = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())); /// builder.passive_ports(49152..=65535); /// ``` pub fn passive_ports(mut self, range: RangeInclusive) -> Self { @@ -649,11 +651,11 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// /// // Use it in a builder-like pattern: - /// let mut server = Server::with_fs("/tmp") + /// let mut server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .pooled_listener_mode() /// .build(); /// ``` @@ -682,11 +684,11 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// /// // Use it in a builder-like pattern: - /// let mut server = Server::with_fs("/tmp") + /// let mut server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .proxy_protocol_mode(2121) /// .build(); /// ``` @@ -705,10 +707,10 @@ where /// /// ```rust /// use std::time::Duration; - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .shutdown_indicator(async { /// // Shut the server down after 10 seconds. /// tokio::time::sleep(Duration::from_secs(10)).await; @@ -734,12 +736,12 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; + /// use libunftp::ServerBuilder; /// use libunftp::options::SiteMd5; - /// use unftp_sbe_fs::ServerExt; + /// use unftp_sbe_fs::Filesystem; /// /// // Use it in a builder-like pattern: - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .sitemd5(SiteMd5::None) /// .build(); /// ``` @@ -792,13 +794,13 @@ where /// # Examples /// /// ```rust - /// use libunftp::Server; + /// use libunftp::ServerBuilder; /// use libunftp::options::{FailedLoginsPolicy,FailedLoginsBlock}; - /// use unftp_sbe_fs::ServerExt; + /// use unftp_sbe_fs::Filesystem; /// /// // With default policy /// let server = - /// Server::with_fs("/tmp") + /// ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .failed_logins_policy(FailedLoginsPolicy::default()) /// .build(); /// @@ -806,7 +808,7 @@ where /// // longer block (maximum 3 attempts, 5 minutes, IP based /// // blocking) /// use std::time::Duration; - /// let server = Server::with_fs("/tmp") + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/tmp").unwrap())) /// .failed_logins_policy(FailedLoginsPolicy::new(3, Duration::from_secs(300), FailedLoginsBlock::IP)) /// .build(); /// ``` @@ -827,13 +829,13 @@ where /// # Example /// /// ```rust - /// use libunftp::Server; - /// use unftp_sbe_fs::ServerExt; + /// use libunftp::ServerBuilder; + /// use unftp_sbe_fs::Filesystem; /// use tokio::runtime::Runtime; /// /// let mut rt = Runtime::new().unwrap(); /// rt.spawn(async { - /// let server = Server::with_fs("/srv/ftp").build().unwrap(); + /// let server = ServerBuilder::new(Box::new(|| Filesystem::new("/srv/ftp").unwrap())).build().unwrap(); /// server.listen("127.0.0.1:2121").await /// }); /// // ... diff --git a/src/server/ftpserver/chosen.rs b/src/server/ftpserver/chosen.rs index f81b96ba..c7458ade 100644 --- a/src/server/ftpserver/chosen.rs +++ b/src/server/ftpserver/chosen.rs @@ -2,16 +2,16 @@ use crate::notification::{DataListener, PresenceListener}; use crate::options::ActivePassiveMode; -use crate::storage::Metadata; use crate::{ - auth::{AuthenticationPipeline, Authenticator, UserDetail, UserDetailProvider}, + auth::AuthenticationPipeline, options::{FtpsRequired, PassiveHost, SiteMd5}, server::controlchan, server::tls::FtpsConfig, - storage::StorageBackend, }; use std::ops::RangeInclusive; use std::{sync::Arc, time::Duration}; +use unftp_core::auth::{Authenticator, UserDetail, UserDetailProvider}; +use unftp_core::storage::{Metadata, StorageBackend}; // Holds the options the libunftp user opted for. pub struct OptionsHolder diff --git a/src/server/ftpserver/mode/pooled.rs b/src/server/ftpserver/mode/pooled.rs index 0497429b..4def9cd1 100644 --- a/src/server/ftpserver/mode/pooled.rs +++ b/src/server/ftpserver/mode/pooled.rs @@ -1,14 +1,14 @@ use crate::ServerError; -use crate::auth::UserDetail; use crate::server::chancomms::{SwitchboardReceiver, SwitchboardSender}; use crate::server::controlchan; use crate::server::ftpserver::error::ListenerError; use crate::server::ftpserver::listen_prebound::PreboundListener; use crate::server::switchboard::SocketAddrPair; -use crate::storage::StorageBackend; use std::net::SocketAddr; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::mpsc::{Sender, channel}; +use unftp_core::auth::UserDetail; +use unftp_core::storage::StorageBackend; fn spawn_data_acceptors(listeners: Vec, tx: Sender>) { for listener in listeners.into_iter() { diff --git a/src/server/ftpserver/mode/proxy.rs b/src/server/ftpserver/mode/proxy.rs index 62d3c94e..e52774b5 100644 --- a/src/server/ftpserver/mode/proxy.rs +++ b/src/server/ftpserver/mode/proxy.rs @@ -1,12 +1,12 @@ use crate::ServerError; -use crate::auth::UserDetail; use crate::server::chancomms::{SwitchboardReceiver, SwitchboardSender}; use crate::server::controlchan; use crate::server::ftpserver::listen_prebound::PreboundListener; use crate::server::proxy_protocol::{ProxyHeaderReceived, spawn_proxy_header_parsing}; -use crate::storage::StorageBackend; use tokio::io::AsyncWriteExt; use tokio::sync::mpsc::{Receiver, Sender, channel}; +use unftp_core::auth::UserDetail; +use unftp_core::storage::StorageBackend; impl PreboundListener where diff --git a/src/server/session.rs b/src/server/session.rs index aaedd1fe..24c9c2f2 100644 --- a/src/server/session.rs +++ b/src/server/session.rs @@ -2,15 +2,11 @@ //! implements the handling for the *data* channel. use super::{chancomms::ControlChanMsg, tls::FtpsConfig}; -use crate::auth::UserDetail; +use crate::metrics; use crate::server::chancomms::DataChanCmd; use crate::server::failed_logins::FailedLoginsCache; use crate::server::switchboard::SocketAddrPair; use crate::server::switchboard::SwitchboardKey; -use crate::{ - metrics, - storage::{Metadata, StorageBackend}, -}; use std::{ fmt::{Debug, Formatter}, net::SocketAddr, @@ -18,6 +14,8 @@ use std::{ sync::Arc, }; use tokio::sync::mpsc::{Receiver, Sender}; +use unftp_core::auth::UserDetail; +use unftp_core::storage::{Metadata, StorageBackend}; // TraceId is an identifier used to correlate logs statements together. #[derive(PartialEq, Eq, Copy, Clone)] @@ -111,7 +109,7 @@ where // busy so that it doesn't time out while the session is still in progress. pub data_busy: bool, // The client certificate chain if it was received. - pub cert_chain: Option>, + pub cert_chain: Option>, // The failed logins cache can monitor successive failed logins and apply a policy to deter brute force attacks. pub failed_logins: Option>, // An optional functor that can bind a socket diff --git a/src/server/switchboard.rs b/src/server/switchboard.rs index 18826ee5..debc0ecf 100644 --- a/src/server/switchboard.rs +++ b/src/server/switchboard.rs @@ -1,13 +1,13 @@ -use crate::auth::UserDetail; use crate::server::session::{Session, SharedSession}; use crate::server::shutdown::Notifier; -use crate::storage::StorageBackend; use dashmap::{DashMap, Entry}; use std::net::{IpAddr, SocketAddr}; use std::ops::RangeInclusive; use std::sync::{Arc, Weak}; use std::time::Duration; use tokio::sync::Mutex; +use unftp_core::auth::UserDetail; +use unftp_core::storage::StorageBackend; /// Identifies a passive listening port entry in the Switchboard that is associated with a specific /// session. The key is constructed out of the external source IP of the client and the passive listening port that has diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 7e6f9346..4de538b1 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,174 +1,3 @@ -//! Contains the [`StorageBackend`] trait that can be implemented to -//! create virtual file systems for libunftp. -//! -//! Pre-made implementations exists on crates.io (search for `unftp-sbe-`) and you can define your -//! own implementation to integrate your FTP(S) server with whatever storage mechanism you prefer. -//! -//! To create a new storage back-end: -//! -//! 1. Declare dependencies on the async-trait and libunftp crates -//! -//! ```toml -//! async-trait = { workspace = true } -//! libunftp = { workspace = true } -//! ``` -//! -//! 2. Implement the [`StorageBackend`] trait and optionally the [`Metadata`] trait: -//! -//! ```no_run -//! use async_trait::async_trait; -//! use libunftp::storage::{Fileinfo, Metadata, Result, StorageBackend}; -//! use std::fmt::Debug; -//! use std::path::{Path, PathBuf}; -//! use libunftp::auth::DefaultUser; -//! use std::time::SystemTime; -//! -//! #[derive(Debug)] -//! pub struct Vfs {} -//! -//! #[derive(Debug)] -//! pub struct Meta { -//! inner: std::fs::Metadata, -//! } -//! -//! impl Vfs { -//! fn new() -> Vfs { Vfs{} } -//! } -//! -//! #[async_trait] -//! impl libunftp::storage::StorageBackend for Vfs { -//! type Metadata = Meta; -//! -//! async fn metadata + Send + Debug>( -//! &self, -//! user: &DefaultUser, -//! path: P, -//! ) -> Result { -//! unimplemented!() -//! } -//! -//! async fn list + Send + Debug>( -//! &self, -//! user: &DefaultUser, -//! path: P, -//! ) -> Result>> -//! where -//! >::Metadata: Metadata, -//! { -//! unimplemented!() -//! } -//! -//! async fn get + Send + Debug>( -//! &self, -//! user: &DefaultUser, -//! path: P, -//! start_pos: u64, -//! ) -> Result> { -//! unimplemented!() -//! } -//! -//! async fn put< -//! P: AsRef + Send + Debug, -//! R: tokio::io::AsyncRead + Send + Sync + Unpin + 'static, -//! >( -//! &self, -//! user: &DefaultUser, -//! input: R, -//! path: P, -//! start_pos: u64, -//! ) -> Result { -//! unimplemented!() -//! } -//! -//! async fn del + Send + Debug>( -//! &self, -//! user: &DefaultUser, -//! path: P, -//! ) -> Result<()> { -//! unimplemented!() -//! } -//! -//! async fn mkd + Send + Debug>( -//! &self, -//! user: &DefaultUser, -//! path: P, -//! ) -> Result<()> { -//! unimplemented!() -//! } -//! -//! async fn rename + Send + Debug>( -//! &self, -//! user: &DefaultUser, -//! from: P, -//! to: P, -//! ) -> Result<()> { -//! unimplemented!() -//! } -//! -//! async fn rmd + Send + Debug>( -//! &self, -//! user: &DefaultUser, -//! path: P, -//! ) -> Result<()> { -//! unimplemented!() -//! } -//! -//! async fn cwd + Send + Debug>( -//! &self, -//! user: &DefaultUser, -//! path: P, -//! ) -> Result<()> { -//! unimplemented!() -//! } -//! } -//! -//! impl Metadata for Meta { -//! fn len(&self) -> u64 { -//! self.inner.len() -//! } -//! -//! fn is_dir(&self) -> bool { -//! self.inner.is_dir() -//! } -//! -//! fn is_file(&self) -> bool { -//! self.inner.is_file() -//! } -//! -//! fn is_symlink(&self) -> bool { -//! self.inner.file_type().is_symlink() -//! } -//! -//! fn modified(&self) -> Result { -//! self.inner.modified().map_err(|e| e.into()) -//! } -//! -//! fn gid(&self) -> u32 { -//! 0 -//! } -//! -//! fn uid(&self) -> u32 { -//! 0 -//! } -//! } -//! ``` -//! -//! 3. Initialize it with the [`Server`](crate::Server): -//! -//! ```no_run -//! # use unftp_sbe_fs::Filesystem; -//! # struct Vfs{}; -//! # impl Vfs { fn new() -> Filesystem { Filesystem::new("/").unwrap() } } -//! let vfs_provider = Box::new(|| Vfs::new()); -//! let server = libunftp::ServerBuilder::new(vfs_provider) -//! .build() -//! .unwrap(); -//! ``` -//! -//! [`Server`]: ../struct.Server.html +//! Re-exports storage traits/types from unftp-core. -pub(crate) mod error; -pub use error::{Error, ErrorKind}; - -pub(crate) mod storage_backend; -pub use storage_backend::{FEATURE_RESTART, FEATURE_SITEMD5, Fileinfo, Metadata, Permissions, Result, StorageBackend}; +pub use unftp_core::storage::{Error, ErrorKind, FEATURE_RESTART, FEATURE_SITEMD5, Fileinfo, Metadata, Permissions, Result, StorageBackend}; diff --git a/tests/common.rs b/tests/common.rs index 8e49e93a..551d9c21 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -2,14 +2,15 @@ use async_trait::async_trait; use lazy_static::*; -use libunftp::auth::{AuthenticationError, Authenticator, Credentials, Principal}; +use libunftp::ServerBuilder; use libunftp::options::{FailedLoginsBlock, FailedLoginsPolicy}; use std::io::Error; use std::net::SocketAddr; use std::sync::Arc; use tokio::net::TcpStream; use tokio::sync::Mutex; -use unftp_sbe_fs::ServerExt; +use unftp_core::auth::{AuthenticationError, Authenticator, Credentials, Principal}; +use unftp_sbe_fs::Filesystem; lazy_static! { static ref CONSUMERS: Arc> = Arc::new(Mutex::new(0)); @@ -17,7 +18,8 @@ lazy_static! { pub async fn run_with_auth() { let addr = "127.0.0.1:2150"; - let server = libunftp::Server::with_fs(std::env::temp_dir()) + let root = std::env::temp_dir(); + let server = ServerBuilder::new(Box::new(move || Filesystem::new(root.clone()).unwrap())) .authenticator(Arc::new(TestAuthenticator {})) .greeting("Welcome test") .failed_logins_policy(FailedLoginsPolicy::new(3, std::time::Duration::new(5, 0), FailedLoginsBlock::User))