A ProFTPD authentication module that authenticates users against a MongoDB database, supporting multiple password hashing methods (bcrypt, SHA-256/512, crypt) with user chroot jailing to their home directories.
- âś… MongoDB-based authentication (supports replica sets)
- âś… Multiple password hashing methods (bcrypt, SHA-256, SHA-512, crypt, plain)
- âś… Connection pooling for high-performance concurrent authentication
- âś… Thread-safe password verification using
crypt_r()on Linux - âś… Strict input validation prevents uid/gid exploits (no uid 0 attacks)
- âś… BSON type safety - handles string and numeric uid/gid formats
- âś… Startup configuration validation - fails fast with clear error messages
- âś… Automatic user chroot jailing to home directory
- âś… Per-user uid/gid from MongoDB
- âś… Configurable field names (flexible MongoDB schema)
- âś… Custom error messages for clients
- âś… Debug logging for troubleshooting
- âś… Dynamic loading as DSO module (no ProFTPD recompilation needed)
- âś… FTP and SFTP support
This module has been hardened for production use with the following security measures:
- Thread-safe authentication prevents race conditions during concurrent logins
- Connection pooling (max 10 connections) prevents resource exhaustion
- Input validation on all uid/gid values prevents privilege escalation attacks
- BSON type checking prevents type confusion vulnerabilities
- Compiled with security flags: stack protection, buffer overflow detection, RELRO, immediate binding
- Startup validation ensures configuration is correct before accepting connections
Recommended deployment practices:
- Use bcrypt for password hashing (not plain text)
- Store uid/gid values >= 1 (never 0 for root)
- Use MongoDB authentication and SSL/TLS for connections
- Enable debug logging initially to verify correct operation
- Monitor logs for authentication failures
- CHANGELOG.md - Version history and release notes
- SECURITY_IMPROVEMENTS.md - Detailed technical security improvements
- MIGRATION_GUIDE.md - Upgrading from v1.0 to v1.1
- AI_DISCLOSURE.md - Full transparency about AI assistance in development
- proftpd.conf.sample - Complete configuration example
⚠️ CRITICAL: ProFTPD DSO modules must be compiled using theprxstool (ProFTPD Extension Tool) with libtool. Direct gcc compilation will create a shared library that ProFTPD cannot load, resulting in "Invalid argument" errors. Additionally, theLoadModuledirective must reference the module asmod_auth_mongodb.c(not.so) - this is a ProFTPD convention where the configuration references the source filename.
Debian/Ubuntu:
sudo apt-get install libmongoc-dev libbson-dev proftpd-dev pkg-config build-essentialRHEL/CentOS:
sudo yum install mongo-c-driver-devel proftpd-devel pkgconfig gccVerify Prerequisites:
# Check ProFTPD has DSO support
proftpd -V | grep "DSO support"
# Verify prxs tool is available
which prxs
# Check MongoDB C driver
pkg-config --modversion libmongoc-1.0If prxs is not found, ProFTPD must be rebuilt with --enable-dso.
./configure \
--sysconfdir=/etc/proftpd \
--disable-ident \
--enable-dso \
--with-modules=mod_tls:mod_sftp
make
sudo make installWhy prxs is Required:
ProFTPD DSO modules have a specific internal structure that prxs creates using libtool. Direct gcc compilation (even with -shared -fPIC) creates a standard shared library that lacks this structure. When ProFTPD attempts to load a module compiled without prxs, it fails with "Invalid argument" because the module doesn't conform to ProFTPD's DSO requirements.
Option A: Using Makefile (recommended):
# Build the module (uses prxs internally)
make
# Verify build succeeded
ls -l .libs/mod_auth_mongodb.so
# Install to /usr/local/libexec/
sudo make install
# Verify installation
ls -l /usr/local/libexec/mod_auth_mongodb.*Option B: Using prxs directly:
# Compile with prxs
prxs -c \
-I /usr/include/libmongoc-1.0 \
-I /usr/include/libbson-1.0 \
-l mongoc-1.0 \
-l bson-1.0 \
-l rt \
mod_auth_mongodb.c
# Install
sudo prxs -i mod_auth_mongodb.la
# Verify
ls -l /usr/local/libexec/mod_auth_mongodb.soBuild Output:
A successful prxs build creates:
.libs/mod_auth_mongodb.so- The actual shared librarymod_auth_mongodb.la- Libtool metadata file (required for installation)mod_auth_mongodb.lo- Libtool object filemod_auth_mongodb.a- Static library
Clean Build:
make clean
# Or manually: prxs -d mod_auth_mongodb.cEdit your proftpd.conf (see proftpd.conf.sample for complete example):
# CRITICAL: Use .c extension (ProFTPD convention), NOT .so
LoadModule mod_auth_mongodb.c
# MongoDB connection
AuthMongoConnectionString "mongodb://user:pass@host:27017/?authSource=admin"
AuthMongoDatabaseName "authentication"
AuthMongoAuthCollectionName "users"
# Field mappings (adjust to match your MongoDB schema)
AuthMongoDocumentFieldUsername "username"
AuthMongoDocumentFieldPassword "password"
AuthMongoDocumentFieldUid "uid"
AuthMongoDocumentFieldGid "gid"
AuthMongoDocumentFieldPath "home_directory"
# Password hashing (bcrypt recommended)
AuthMongoPasswordHashMethod bcrypt
# Optional: Enable debug logging for initial setup
AuthMongoDebugLogging yes
# Use only MongoDB authentication
AuthOrder mod_auth_mongodb.c# Test syntax
sudo proftpd -t
# Look for successful module initialization:
# "mod_auth_mongodb/1.1.1: Module initialized"
# If you see "Invalid argument" error:
# 1. Verify LoadModule uses .c extension (not .so)
# 2. Confirm module was compiled with prxs
# 3. Check module exists: ls -l /usr/local/libexec/mod_auth_mongodb.so# Restart ProFTPD
sudo systemctl restart proftpd
# Check logs for successful startup
sudo tail -f /var/log/proftpd/system.log
# Test connection
ftp localhost
# Or with SFTP:
sftp -P 2222 username@localhostAll directives are configured in proftpd.conf. See proftpd.conf.sample for a fully documented example.
| Directive | Required | Description | Example |
|---|---|---|---|
AuthMongoConnectionString |
âś… | MongoDB connection URI (supports replica sets) | "mongodb://user:pass@host:27017/?authSource=admin" |
AuthMongoDatabaseName |
âś… | Database name containing user collection | "authentication" |
AuthMongoAuthCollectionName |
âś… | Collection name with user documents | "users" |
AuthMongoDocumentFieldUsername |
âś… | Field name for username | "username" |
AuthMongoDocumentFieldPassword |
âś… | Field name for password/hash | "password" |
AuthMongoDocumentFieldUid |
âś… | Field name for user ID (string) | "uid" |
AuthMongoDocumentFieldGid |
âś… | Field name for group ID (string) | "gid" |
AuthMongoDocumentFieldPath |
âś… | Field name for home directory path | "home_directory" |
AuthMongoPasswordHashMethod |
❌ | Hash method: plain, bcrypt, crypt, sha256, sha512 |
bcrypt (default: plain) |
AuthMongoNoAuthString |
❌ | Error message for failed authentication | "Your username/password is incorrect" |
AuthMongoNoConnectionString |
❌ | Error message for connection failures | "Failed to connect to Authentication Server" |
AuthMongoDebugLogging |
❌ | Enable debug logging (yes/no) |
yes (default: no) |
Single server:
mongodb://user:pass@localhost:27017/?authSource=admin
Replica set with authentication:
mongodb://user:pass@mongo1.vm.lan:27017,mongo2.vm.lan:27017,mongo3.vm.lan:27017/?replicaSet=rs0&authSource=admin&authMechanism=SCRAM-SHA-256&serverSelectionTimeoutMS=5000&connectTimeoutMS=10000
No authentication:
mongodb://localhost:27017/
These values would be configured in proftpd.conf.
AuthMongoConnectionString: (connection string url to mondo db) AuthMongoNoAuthString: "Your username/password is incorrect" AuthMongoNoConnectionString: "Failed to connect to Authentication Server"
AuthMongoAuthCollectionName: ( name of the collection to authenticate against ) AuthMongoDocumentFieldUsername: (name of the document field to authenticate against) AuthMongoDocumentFieldPassword: (name of the document field to authenticate against) AuthMongoDocumentFieldUid: ( name of the document field to get the uid) AuthMongoDocumentFieldGid: ( name of the document field to get the gid) AuthMongoDocumentFieldPath: ( name of the document field to get the path to the user data) AuthMongoPasswordHashMethod: ( password hash method: plain, bcrypt, crypt, sha256, sha512 )
mongodb://username:password@mongo1.vm.lan:27017,mongo2.vm.lan:27017,mongo3.vm.lan:27017/?retryWrites=true&loadBalanced=false&replicaSet=rs0&readPreference=primary&serverSelectionTimeoutMS=5000&connectTimeoutMS=10000&authSource=admin&authMechanism=SCRAM-SHA-256
Your MongoDB collection should contain documents with the following structure:
With bcrypt (Recommended):
{
"_id": ObjectId("..."),
"username": "testuser",
"password": "$2b$10$N9qo8uLOickgx2ZMRZoMye.Ik3JSk5U5p7L6Km1hp7RmWfXfGOIJy",
"uid": "1001",
"gid": "1001",
"home_directory": "/home/testuser"
}With plain text (Not recommended):
{
"_id": ObjectId("..."),
"username": "testuser",
"password": "testpassword",
"uid": "1001",
"gid": "1001",
"home_directory": "/home/testuser"
}Important Notes:
passwordformat depends onAuthMongoPasswordHashMethod:- bcrypt: Hash from Node.js bcrypt (e.g.,
$2b$10$...) - RECOMMENDED - sha512: SHA-512 crypt hash (e.g.,
$6$...) - sha256: SHA-256 crypt hash (e.g.,
$5$...) - crypt: Traditional Unix crypt hash
- plain: Plain text password (not secure)
- bcrypt: Hash from Node.js bcrypt (e.g.,
uidandgidcan be stored as strings (e.g.,"1001") or integers (e.g.,1001)- Must be >= 1 (values < 1 are rejected to prevent root access)
- Validated at runtime with range checking
home_directoryshould be an absolute path (string)- All fields must be present for authentication to succeed
Using Node.js (bcrypt):
const bcrypt = require("bcrypt");
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash("yourpassword", salt);
console.log(hash);
// Output: $2b$10$N9qo8uLOickgx2ZMRZoMye.Ik3JSk5U5p7L6Km1hp7RmWfXfGOIJy- Client connects to ProFTPD (FTP port 21 or SFTP port 2222)
- Client provides username and password
- Module queries MongoDB using connection from pool (reused for efficiency)
- Field validation - checks BSON types and validates uid/gid ranges (>= 1)
- Password verification using thread-safe
crypt_r()with configured hash method - On success:
- ProFTPD retrieves validated uid, gid, and home_directory from MongoDB
- Switches process to user's uid/gid
- Chroots user to their home directory (user sees it as
/) - User is jailed - cannot navigate above home directory
- Connection is returned to pool for reuse
- On failure:
- Custom error message sent to client
- Connection rejected
- Connection returned to pool
- Connection pooling: Maintains up to 10 reusable MongoDB connections
- Query result caching: Caches user data for 5 seconds to avoid duplicate queries
- ProFTPD calls
getpwnam()thenauth()for the same user - Cache hit eliminates second MongoDB query (50% reduction)
- Automatic cache invalidation on authentication failure
- ProFTPD calls
- Thread-safe: Supports concurrent authentication requests without race conditions
- Fail-fast validation: Tests MongoDB connectivity at server startup
- Efficient resource management: All connections returned to pool after use
Expected performance:
- First auth: ~5-10ms (MongoDB query + cache population)
- Second auth (cached): ~0.1ms (cache lookup only)
- Cache TTL: 5 seconds (balances performance vs. security)
- User isolation: Each user runs with their own uid/gid and is chrooted
- Password hashing: Supports bcrypt and other strong hashing methods
- Thread-safe authentication: Uses
crypt_r()to prevent race conditions (Linux) - UID/GID validation: Strict range checking prevents uid 0 (root) attacks
- BSON type safety: Validates field types before conversion to prevent type confusion
- Input sanitization: Safe parsing of all numeric values with error checking
- Connection pooling: Reuses connections efficiently (max 10 concurrent)
- Startup validation: Tests MongoDB connectivity at server start
- No system users needed: All user data comes from MongoDB
- Replica set support: High availability with MongoDB clusters
- Configurable timeouts: Prevents hanging on MongoDB connection issues Using command line (SHA-256):
Issues and pull requests welcome! This module is production-ready with security hardening.
Please note: Future contributions may also use AI assistance tools. All contributions undergo human review regardless of development method.
Copyright (c) 2025. This module is provided as-is for use with ProFTPD.
This is the most common issue and has specific causes:
Cause 1: Wrong file extension in LoadModule directive
# Check your proftpd.conf
grep LoadModule /etc/proftpd/proftpd.conf | grep mongodb
# WRONG - will fail
LoadModule mod_auth_mongodb.so
# CORRECT - will work
LoadModule mod_auth_mongodb.c
# Fix it
sudo sed -i 's/mod_auth_mongodb.so/mod_auth_mongodb.c/' /etc/proftpd/proftpd.conf
sudo proftpd -tCause 2: Module compiled with direct gcc instead of prxs
# Rebuild with prxs
make clean
make
sudo make install
# Verify .la file exists (created only by prxs)
ls -l /usr/local/libexec/mod_auth_mongodb.laCause 3: ProFTPD built without DSO support
# Check for DSO support
proftpd -V | grep "DSO support"
# If not present, rebuild ProFTPD:
./configure --enable-dso --sysconfdir=/etc/proftpd --with-modules=mod_tls:mod_sftp
make
sudo make installCause 4: Module file missing or in wrong location
# Check module exists
ls -l /usr/local/libexec/mod_auth_mongodb.so
# Check ProFTPD's module directory
proftpd -V | grep "Shared Module Directory"
# If module is in wrong location, reinstall
sudo prxs -i mod_auth_mongodb.laSet AuthMongoDebugLogging yes to see detailed authentication flow in logs.
# Watch logs in real-time
sudo tail -f /var/log/proftpd/system.logVerify your connection string works:
mongosh "mongodb://user:pass@host:27017/database?authSource=admin"# Test configuration syntax
sudo proftpd -t
# Should see: "mod_auth_mongodb/1.1.1: Module initialized"
# List loaded modules
proftpd -l | grep authMongoDB connection failures:
# Test MongoDB connectivity
mongosh "mongodb://user:pass@host:27017/dbname?authSource=admin"
# Check firewall
telnet mongo-host 27017
# Verify credentials and authSource
# Common mistake: wrong authSource (should match where user is defined)Authentication always fails:
# Enable debug logging
AuthMongoDebugLogging yes
# Check field names match your MongoDB schema
mongosh
use authentication
db.users.findOne({username: "testuser"})
# Verify AuthMongoPasswordHashMethod matches password format:
# - bcrypt hashes start with $2a$, $2b$, or $2y$
# - sha512 hashes start with $6$
# - sha256 hashes start with $5$Permission errors (can't access home directory):
# Create home directory
sudo mkdir -p /home/testuser
# Set ownership (uid:gid from MongoDB)
sudo chown 1001:1001 /home/testuser
# Set permissions
sudo chmod 755 /home/testuser
# Verify
ls -ld /home/testuserUID/GID validation errors in logs:
# UIDs and GIDs must be >= 1 (never 0 for security)
# Check MongoDB documents:
mongosh
db.users.find({}, {username: 1, uid: 1, gid: 1})
# Values can be strings ("1001") or numbers (1001)
# Both are accepted, but must be >= 1prxs not found:
# Install ProFTPD development package
sudo apt-get install proftpd-dev # Debian/Ubuntu
sudo yum install proftpd-devel # RHEL/CentOS
# Or build ProFTPD with DSO support
./configure --enable-dso
make && sudo make installMongoDB C driver not found:
# Install MongoDB C driver
sudo apt-get install libmongoc-dev libbson-dev # Debian/Ubuntu
sudo yum install mongo-c-driver-devel # RHEL/CentOS
# Verify
pkg-config --modversion libmongoc-1.0For issues:
- Enable debug logging:
AuthMongoDebugLogging yes - Check logs:
sudo tail -f /var/log/proftpd/system.log - Verify MongoDB connection:
mongosh "mongodb://..." - Test configuration:
sudo proftpd -t - Review documentation:
proftpd.conf.samplehas complete examples - Check build method: Module must be compiled with
prxs - Verify LoadModule: Must use
.cextension, not.so - Open GitHub issue only after completing steps 1-7
- BUILD.md - Detailed build troubleshooting guide
- proftpd.conf.sample - Complete configuration with all directives
- CHANGELOG.md - Version history and changes
- ProFTPD DSO Guide: http://www.proftpd.org/docs/howto/DSO.html
- prxs manual:
man prxs