Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
17 changes: 10 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <ahorvath@bol.com>",
"Dávid Kosztka <dkosztka@bol.com>",
Expand All @@ -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"]
Expand All @@ -22,6 +23,7 @@ edition = "2024"
[workspace]

members = [
"crates/unftp-core",
"crates/unftp-auth-jsonfile",
"crates/unftp-auth-pam",
"crates/unftp-auth-rest",
Expand All @@ -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" }
Expand All @@ -72,28 +77,26 @@ 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 }
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 = "." }
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -125,21 +125,22 @@ 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"] }
```

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()
Expand Down
10 changes: 9 additions & 1 deletion RELEASE-CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,11 +20,19 @@

or

> Release unftp-core version x.y.x

or

> Release all
* Run `make publish`
* Push to GitHub
* Create the release in GitHub using tag format {component}-{version} e.g.
> libunftp-0.17.1
or
> unftp-sbe-fs-0.1.1

or

> unftp-core-0.1.0
* Notify the Telegram channel.
12 changes: 4 additions & 8 deletions crates/unftp-auth-jsonfile/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <ahorvath@bol.com>",
Expand All @@ -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
Expand All @@ -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
6 changes: 4 additions & 2 deletions crates/unftp-auth-jsonfile/examples/jsonfile_auth.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
Expand All @@ -11,7 +12,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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();
Expand Down
24 changes: 12 additions & 12 deletions crates/unftp-auth-jsonfile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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)]
Expand Down Expand Up @@ -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),
Expand All @@ -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<Principal, AuthenticationError> {
async fn authenticate(&self, username: &str, creds: &unftp_core::auth::Credentials) -> Result<Principal, AuthenticationError> {
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());
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)),
Expand All @@ -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)),
Expand Down Expand Up @@ -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)),
Expand All @@ -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)),
Expand All @@ -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)),
Expand All @@ -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)),
Expand All @@ -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)),
Expand Down
2 changes: 1 addition & 1 deletion crates/unftp-auth-jsonfile/tests/main.rs
Original file line number Diff line number Diff line change
@@ -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");
Expand Down
9 changes: 2 additions & 7 deletions crates/unftp-auth-pam/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <ahorvath@bol.com>",
Expand All @@ -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

Expand Down
6 changes: 3 additions & 3 deletions crates/unftp-auth-pam/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading
Loading