ftpd FTP Server
The ftpd utility is a lightweight File Transfer Protocol (FTP) server implementation
for Apache NuttX, conforming to the core protocol specification defined in RFC 959,
with modern extensions for IPv6 networking (RFC 2428) and file metadata inspection
(RFC 3659).
It provides embedded microcontrollers with bidirectional network file transfer capabilities, allowing remote desktop computers, automated update utilities, and monitoring tools to inspect, upload, download, and delete files on the NuttX Virtual File System (VFS), SD cards, or flash partitions over local Ethernet or Wi-Fi links.
Note
Security Consideration: Standard FTP (RFC 959) transmits usernames, passwords,
and file contents in unencrypted plaintext across the network. On untrusted or public
networks, credentials and transferred data can be intercepted by network sniffers.
When deploying ftpd in production environments:
Restrict FTP traffic to isolated, trusted local networks or private maintenance VLANs.
Where possible, enforce strong passwords via the encrypted password subsystem (
CONFIG_FTPD_LOGIN_PASSWDwithCONFIG_FSUTILS_PASSWD).Confine guest accounts to dedicated subdirectories using home directory isolation (
FTPD_ACCOUNTFLAG_GUEST).For mission-critical external transfers, prefer encrypted protocols such as SSH/SFTP or HTTPS.
Operational Architecture
The NuttX FTP server operates using a multi-threaded daemon model:
Master Server Socket: A listening TCP socket bound to the FTP control port (default port 21) accepts incoming client connections.
Worker Pthreads: When a remote FTP client connects,
ftpd_session()accepts the TCP connection and spawns a dedicated worker thread (with stack size governed byCONFIG_FTPD_WORKERSTACKSIZE) to handle control session interactions.Dual Connection Model: The server maintains a persistent control connection for ASCII commands and reply codes (e.g.,
USER,PASS,PASV), and establishes transient data connections on demand for directory listings (LIST,NLST) and file transfers (RETR,STOR).VFS Integration: Directory navigation, file creation, reading, and writing map directly to standard POSIX file system system calls (
open(),read(),write(),stat(),opendir(),unlink(),rename()).
Key Features
RFC 959 Core Protocol Support: Full support for standard FTP file retrieval, storage, and directory management.
Active and Passive Modes: Supports both Active (
PORT/EPRT) and Passive (PASV/EPSV) data connections, ensuring seamless operation across client firewalls and NAT gateways.IPv4 and IPv6 Dual-Stack: Implements RFC 2428 commands (
EPRTandEPSV) to support bothAF_INETandAF_INET6address families.Resumable Transfers: Supports the
REST(restart marker) command to resume interrupted file transfers without retransmitting previously transferred blocks.Extended File Metadata: Implements RFC 3659 commands (
SIZEandMDTM) to report file byte lengths and last-modification timestamps.User Account Management: In-memory user database supporting multiple concurrent accounts with configurable privileges (administrator, system, or guest) and directory isolation.
Encrypted Password Integration: Optional integration with
apps/fsutils/passwdto authenticate users against encrypted password databases.
Supported FTP Commands
The NuttX FTP server implements a comprehensive command subset:
Command |
Description & Protocol Standard |
|---|---|
|
Specify client username (RFC 959). |
|
Specify client password (RFC 959). |
|
Terminate the control connection (RFC 959). |
|
Return server system type (reports |
|
Set transfer type: ASCII ( |
|
Set transfer mode: Stream ( |
|
Set file structure: File ( |
|
Print working directory (RFC 959). |
|
Change working directory (RFC 959). |
|
Change to parent directory (RFC 959). |
|
Create a directory (RFC 959). |
|
Remove an empty directory (RFC 959). |
|
Delete a file (RFC 959). |
|
Rename file from / rename file to sequence (RFC 959). |
|
Initiate active IPv4 data connection (RFC 959). |
|
Request passive IPv4 data connection (RFC 959). |
|
Initiate extended active data connection (IPv4/IPv6) (RFC 2428). |
|
Request extended passive data connection (IPv4/IPv6) (RFC 2428). |
|
Transmit detailed directory listing (RFC 959). |
|
Transmit filename-only directory list (RFC 959). |
|
Download a file from the server (RFC 959). |
|
Upload a file to the server (RFC 959). |
|
Append uploaded data to an existing file (RFC 959). |
|
Set byte offset for resuming a transfer (RFC 959). |
|
Return the exact byte size of a file (RFC 3659). |
|
Return the last modification timestamp of a file (RFC 3659). |
|
Abort the active data transfer (RFC 959). |
|
No operation; keeps connection alive (RFC 959). |
|
Display supported commands (RFC 959). |
Requirements & Dependencies
To enable the FTP server in your NuttX build, ensure the following subsystem dependencies are configured:
Core Networking:
CONFIG_NET=yTCP Protocol:
CONFIG_NET_TCP=yPOSIX Pthreads:
CONFIG_DISABLE_PTHREAD=n(required for multi-session handling)File System:
CONFIG_NFILE_DESCRIPTORS > 0(with mounted storage, e.g., ROMFS, FAT, LittleFS)Client Library:
CONFIG_NETUTILS_FTPD=yPassword Support (Optional):
CONFIG_FTPD_LOGIN_PASSWD=yandCONFIG_FSUTILS_PASSWD=yStandalone Example Daemon (Optional):
CONFIG_EXAMPLES_FTPD=y
Configuration Options
The behavior and resource consumption of the FTP server can be configured via Kconfig:
Kconfig Option |
Default |
Description |
|---|---|---|
|
|
Enables compilation of the FTP server library in |
|
|
Stack size in bytes allocated for each FTP session worker pthread. |
|
|
Validates login credentials against an encrypted password database via
|
|
|
Vendor identification string reported in server greeting banners. |
|
|
Server product name string reported in the |
|
|
Size in bytes of the command line buffer for incoming FTP instructions. |
|
|
Buffer size in bytes used for I/O chunks during data socket transfers. |
|
|
Builds the standalone |
|
|
Default TCP port used by the standalone example daemon. |
Running the FTP Daemon from NSH
When CONFIG_EXAMPLES_FTPD=y is enabled, the FTP server can be started and stopped
interactively from the NuttShell.
Starting the Daemon
The standalone daemon requires an address family parameter specifying either -4 (IPv4)
or -6 (IPv6):
nsh> ftpd -4
Initializing the network
Starting the FTP daemon
FTP daemon [3] started
Adding accounts:
1. Root account: USER=root PASSWORD=<configured-password> HOME=(none)
2. User account: USER=ftp PASSWORD=(none) HOME=(none)
3. User account: USER=anonymous PASSWORD=(none) HOME=(none)
Warning
Mandatory Credential Hardening:
The standalone example in apps/examples/ftpd is intended for local hardware
testing. Before enabling ftpd in any networked or production environment,
change the demonstration credentials to strong, unique passwords or authenticate
against an encrypted password database via CONFIG_FTPD_LOGIN_PASSWD. Never
deploy publicly known default passwords.
The daemon initializes the network interface if not already active, opens a listening
socket on CONFIG_EXAMPLES_FTPD_PORT (default port 21), registers the configured accounts,
and awaits incoming connections in a background task.
Stopping the Daemon
To cleanly terminate the running FTP daemon:
nsh> ftpd_stop
Stopping the FTP daemon, pid=3
Connecting from an External FTP Client
Once the daemon is running, connect using standard command-line FTP clients, curl,
or graphical transfer tools like FileZilla:
Using Command-Line FTP
Authenticate using the configured root account and password:
$ ftp 192.168.1.150
Connected to 192.168.1.150.
220 NuttX FTP Server ready.
Name (192.168.1.150:user): root
331 Password required for root.
Password: <your-password>
230 User logged in.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pwd
257 "/" is current directory.
ftp> ls
200 PORT command successful.
150 Opening data connection for directory list.
drwxr-xr-x 1 root root 0 Jan 01 00:00 dev
drwxr-xr-x 1 root root 0 Jan 01 00:00 etc
drwxr-xr-x 1 root root 0 Jan 01 00:00 mnt
226 Transfer complete.
ftp> put firmware.bin /mnt/sdcard/firmware.bin
200 PORT command successful.
150 Opening data connection for firmware.bin.
226 Transfer complete. 524288 bytes sent in 2.1 secs.
ftp> quit
221 Goodbye.
Alternatively, connect anonymously without a password using the anonymous account:
$ ftp 192.168.1.150
Connected to 192.168.1.150.
220 NuttX FTP Server ready.
Name (192.168.1.150:user): anonymous
230 User logged in.
ftp> ls
200 PORT command successful.
150 Opening data connection for directory list.
drwxr-xr-x 1 root root 0 Jan 01 00:00 mnt
226 Transfer complete.
ftp> quit
221 Goodbye.
Using cURL
Files can also be transferred programmatically from external scripts using curl
(for interactive use, omit the password to receive a prompt; for scripts, load credentials
from a protected source such as ~/.netrc or environment variables):
# Download a file from NuttX VFS using root credentials (prompts for password):
curl -u root ftp://192.168.1.150/mnt/sdcard/log.txt -o downloaded_log.txt
# Upload a file to NuttX VFS using root credentials (prompts for password):
curl -u root -T config.ini ftp://192.168.1.150/mnt/sdcard/config.ini
# Download anonymously without credentials:
curl ftp://192.168.1.150/mnt/sdcard/log.txt -o downloaded_log.txt
C API Reference
The public C interface for ftpd is declared in apps/include/netutils/ftpd.h.
Types & Constants
/* Opaque FTP session handle */
typedef FAR void *FTPD_SESSION;
/* User Account Flags */
#define FTPD_ACCOUNTFLAG_NONE (0) /* Standard unprivileged user */
#define FTPD_ACCOUNTFLAG_ADMIN (1 << 0) /* Administrative privileges */
#define FTPD_ACCOUNTFLAG_SYSTEM (1 << 1) /* System root access */
#define FTPD_ACCOUNTFLAG_GUEST (1 << 2) /* Restricted guest access */
Functions
Creating the Server Instance
FTPD_SESSION ftpd_open(int port, sa_family_t family);
Allocates and initializes the FTP server instance, binding the listening socket to the designated port and address family.
port: TCP listen port (typically 21, or any unreserved port).family: Socket address family (AF_INETfor IPv4 orAF_INET6for IPv6).Returns: A non-
NULLFTPD_SESSIONhandle on success, orNULLon failure.
Adding User Accounts
int ftpd_adduser(FTPD_SESSION handle, uint8_t accountflags,
FAR const char *user, FAR const char *passwd,
FAR const char *home);
Registers an account in the FTP server’s user database.
handle: Server handle returned byftpd_open().accountflags: Bitmask ofFTPD_ACCOUNTFLAG_*defining privilege level.user: Username string. PassNULLto allow anonymous login without a username.passwd: Password string. PassNULLto permit login without a password.home: Initial home directory path for the user (e.g.,"/mnt/sdcard").Returns:
0(OK) on success, or a negated errno value (e.g.,-ENOMEM) on failure.
Driving the Server Session Loop
int ftpd_session(FTPD_SESSION handle, int timeout);
Listens for an incoming client connection on the server socket.
When a connection arrives, it accepts the connection and spawns a worker thread to service the FTP session.
handle: Server handle returned byftpd_open().timeout: Connection wait timeout in milliseconds.Returns:
0(OK) on connection acceptance and worker thread creation,-ETIMEDOUTif the timeout elapsed with no incoming connection (normal loop condition), or a negative errno value on failure.
Destroying the Server Instance
void ftpd_close(FTPD_SESSION handle);
Closes listening sockets, terminates active sessions, and deallocates all internal resources and account records associated with the handle.
Application Example
Below is a complete, standalone example demonstrating how an embedded task initializes an FTP server daemon with multiple accounts, drives the connection loop, and provides a signal-safe, thread-safe shutdown path:
#include <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netutils/ftpd.h>
/* Signal-safe termination flag modified exclusively by signal handlers */
static volatile sig_atomic_t g_ftp_stop = 0;
/* PID of the running FTP server daemon */
static pid_t g_server_pid = -1;
/* Signal handler to request graceful shutdown (async-signal-safe) */
static void ftp_signal_handler(int signo)
{
g_ftp_stop = 1;
}
/* Thread-safe stop API callable from other tasks/threads via POSIX kill */
int stop_my_ftp_server(void)
{
if (g_server_pid > 0)
{
return kill(g_server_pid, SIGTERM);
}
return -ESRCH;
}
int start_my_ftp_server(void)
{
FTPD_SESSION handle;
int ret;
/* Initialize PID and clear stop flag BEFORE installing signal handlers */
g_server_pid = getpid();
g_ftp_stop = 0;
/* Install signal handlers to allow clean shutdown via kill or Ctrl+C */
signal(SIGINT, ftp_signal_handler);
signal(SIGTERM, ftp_signal_handler);
/* 1. Open the FTP server listening on standard port 21 (IPv4) */
handle = ftpd_open(21, AF_INET);
if (handle == NULL)
{
fprintf(stderr, "ERROR: Failed to open FTP server on port 21\n");
g_server_pid = -1;
return -EIO;
}
/* 2. Configure user accounts (replace placeholders with strong credentials) */
/* Admin account: full access starting at filesystem root */
ret = ftpd_adduser(handle, FTPD_ACCOUNTFLAG_ADMIN,
"admin", "<strong-admin-password>", "/");
if (ret < 0)
{
fprintf(stderr, "Failed to add admin user: %d\n", ret);
ftpd_close(handle);
g_server_pid = -1;
return ret;
}
/* Guest account: unprivileged access restricted to /mnt/sdcard */
ret = ftpd_adduser(handle, FTPD_ACCOUNTFLAG_GUEST,
"guest", "<strong-guest-password>", "/mnt/sdcard");
if (ret < 0)
{
fprintf(stderr, "Failed to add guest user: %d\n", ret);
ftpd_close(handle);
g_server_pid = -1;
return ret;
}
printf("FTP server running on port 21. Waiting for connections...\n");
/* 3. Server connection loop: runs until g_ftp_stop is set */
while (!g_ftp_stop)
{
/* Wait up to 5000 ms for incoming connection requests */
ret = ftpd_session(handle, 5000);
if (ret == 0)
{
printf("FTP client connected and session worker spawned.\n");
}
else if (ret != -ETIMEDOUT)
{
fprintf(stderr, "ftpd_session error: %d\n", ret);
break;
}
}
/* 4. Cleanup and shutdown upon loop exit */
printf("Shutting down FTP server...\n");
ftpd_close(handle);
g_server_pid = -1;
return OK;
}
Embedded Design & Resource Tuning
Stack Sizing for Worker Threads: Each active FTP client connection creates a worker thread. Ensure
CONFIG_FTPD_WORKERSTACKSIZEis sized appropriately for the underlying filesystem driver (e.g., FAT filesystems or LittleFS typically require at least 2048 to 3072 bytes of stack space).Buffer Sizing for Network vs. Flash Throughput:
CONFIG_FTPD_DATABUFFERSIZEcontrols the chunk size read and written during file transfers (default: 512 bytes). Increasing this buffer to match the storage block size (e.g., 1024 or 2048 bytes) improves throughput on high-speed Ethernet targets at the expense of RAM.Passive Mode on NAT / Firewalled Networks: Embedded devices positioned behind firewalls should instruct remote clients to use Passive Mode (
PASVorEPSV), ensuring the data connection is initiated by the client toward the embedded server rather than requiring the server to initiate inbound connections to client ports.