Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📂 Porta

A from-scratch FTP server in Rust. RFC 959 control channel, active & passive data channels, virtual users with argon2id auth and — most importantly — a path-traversal jail that confines every user to their home directory. Built on tokio with the protocol implemented by hand; no third-party FTP crate.

Rust 1.95 License Apache 2.0 tokio Single binary From scratch

Status: the FTP core is working and verified end-to-end (login, passive & active data, LIST/MLSD, upload/download/rename/delete, REST resume, path jail). FTPS (TLS) and the web admin panel are on the roadmap below.


Features

Area What's implemented
Control channel RFC 959 command/reply state machine, one async task per connection
Authentication Virtual users (independent of OS accounts), argon2id password hashing
Data channels Passive (PASV/EPSV) and active (PORT/EPRT), NAT masquerade IP
Path jail Every path normalized in virtual space with .. clamped — no traversal escape
File ops RETR, STOR, APPE, DELE, MKD, RMD, RNFR/RNTO, SIZE, MDTM, REST (resume)
Listings LIST/NLST (Unix format) and MLSD (machine-readable)
Permissions Per-user read / write / delete / list flags + storage quota
Security Path-traversal jail, FTP-bounce protection (data target must match control IP), argon2id
Transfer params TYPE A/I, FEAT, OPTS, SIZE, MDTM

🛠 Tech Stack

Language Runtime Auth Storage Protocol

Layer Technology
Language Rust 2021 — std::net types, async via tokio
Async runtime tokio (net, fs, io, sync, signal)
Auth argon2id (RustCrypto argon2)
Storage virtual users + transfer log as JSON (serde) — pure-Rust, no C toolchain
Time chrono (listing/MDTM timestamps)
Protocol FTP (RFC 959) hand-written; FTPS (RFC 4217) on the roadmap

100% pure-Rust dependency set (tokio, argon2, serde, chrono) — builds with CGO-free cargo build, no C compiler required.


Quickstart

Requires Rust 1.95+.

git clone https://github.com/vugarfamiloglu/porta
cd porta

cargo build --release            # single binary at target/release/porta

./target/release/porta seed      # create demo user 'ftpuser' (Ftp2026!) + home
./target/release/porta serve     # control channel on :2121

Connect with any FTP client:

# list, upload, download with curl (passive mode)
curl --user ftpuser:Ftp2026! ftp://127.0.0.1:2121/
curl -T myfile.txt --user ftpuser:Ftp2026! ftp://127.0.0.1:2121/uploads/myfile.txt
curl --user ftpuser:Ftp2026! ftp://127.0.0.1:2121/uploads/myfile.txt

Or point FileZilla at 127.0.0.1, port 2121, user ftpuser, password Ftp2026! (passive mode).

Default ports (development)

Service Dev Standard
Control 2121 21
Passive data range 50000–50100 configurable
Implicit FTPS (roadmap) 9990 990
Web admin (roadmap) 8021 8021

Configure via PORTA_* env vars: PORTA_CONTROL_ADDR, PORTA_PASV_MIN/MAX, PORTA_MASQUERADE (public IP for PASV behind NAT), PORTA_DATA_DIR.

CLI

porta serve                       # run the FTP server
porta seed                        # create the demo user + home
porta passwd <user> <password>    # set a user's password
porta version

Security notes

  • Path-traversal jail — every path (CWD, RETR, STOR, …) is resolved in virtual space with .. clamped at the root, so ../../etc/passwd can never escape a user's home. Covered by unit tests.
  • FTP-bounce protectionPORT/EPRT data targets must match the control connection's IP, blocking the classic FTP-bounce port-scan/relay attack.
  • argon2id password hashing; per-user permissions and quota.
  • ⚠️ Plain FTP sends credentials and data in cleartext — use FTPS (roadmap P3) or restrict to trusted networks for anything sensitive.

Behind NAT

Passive mode needs the passive port range (50000–50100) open in the firewall, and PORTA_MASQUERADE set to the server's public IP so PASV advertises a reachable address. This is the single most common FTP misconfiguration.


Project layout

src/
  main.rs       runtime + control listener + CLI (serve/seed/passwd)
  config.rs     PORTA_* configuration
  log.rs        structured logger + ring buffer (for the web monitor)
  auth.rs       argon2id hashing/verification
  store.rs      virtual users + transfer log (JSON)
  vfs.rs        path-traversal jail  ← security core
  data.rs       active/passive data connection handling
  session.rs    RFC 959 control session + command handlers

Roadmap

  • P1 — FTP core ✅ control channel, passive/active, virtual users, path jail, file ops, listings, REST resume.
  • P2 — hardening ⏳ rate limiting, idle timeout, fail2ban-style ban, ASCII-mode line conversion, anonymous sandbox.
  • P3 — FTPS ⏳ explicit AUTH TLS + implicit (990) via a pure-Rust TLS stack (rustls + RustCrypto provider, to keep the no-C-toolchain build).
  • P4 — web admin ⏳ user CRUD, live sessions, transfer log, web file browser and log monitor.

Testing

cargo test

Unit tests cover argon2 hashing/verification and the path-jail resolver (including ../ escape attempts). The full upload→download round-trip is verified against a real curl FTP client.


License

Apache License 2.0 — see LICENSE.

About

A from-scratch FTP server in Rust (tokio) — RFC 959 control + active/passive data, virtual users, argon2 auth and a path-traversal jail.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages