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.
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.
| 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 |
| 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-freecargo build, no C compiler required.
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 :2121Connect 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.txtOr point FileZilla at 127.0.0.1, port 2121, user ftpuser, password
Ftp2026! (passive mode).
| 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.
porta serve # run the FTP server
porta seed # create the demo user + home
porta passwd <user> <password> # set a user's password
porta version
- Path-traversal jail — every path (
CWD,RETR,STOR, …) is resolved in virtual space with..clamped at the root, so../../etc/passwdcan never escape a user's home. Covered by unit tests. - FTP-bounce protection —
PORT/EPRTdata 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.
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.
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
- 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.
cargo testUnit 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.
Apache License 2.0 — see LICENSE.