Skip to content

himewel/ftp-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

149 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FTP Server

A multithreaded FTP server written in C that implements the RFC 959 minimum specification, with per-client bandwidth throttling (QoS) and support for both active (PORT) and passive (PASV) data transfers.

Features

  • RFC 959 minimum implementation — USER, QUIT, PORT, TYPE, MODE, STRU, RETR, STOR, NOOP
  • Extended commands — LIST, CWD, CDUP, PWD, MKD, RMD, SYST, PASV
  • Concurrent clients — one POSIX thread per active control connection
  • Dual data modes — active (PORT) and passive (PASV)
  • Per-client QoS — configurable bandwidth limits via config.ini or fair-share defaults
  • Colored logging — control-channel traffic, transfer stats, and connection events

Authentication

Username, password, and permission checks are not enforced. The server assumes all credentials are valid and that clients may access any directory on the host filesystem.

Architecture

High-level overview

flowchart TB
    subgraph Client["FTP Client"]
        CC[Control Connection]
        DC[Data Connection]
    end

    subgraph Server["FTP Server (server.out)"]
        MS[Main Loop<br/>accept + admission]
        CFG[config.ini<br/>per-IP rates]
        QOS[Global bandwidth pool<br/>taxa_servidor / taxa_atual]

        subgraph Threads["Per-client threads"]
            T1[multUser thread 1]
            T2[multUser thread 2]
            TN[multUser thread N]
        end

        subgraph Handlers["Command handlers (server_func.c)"]
            DEC[decode_message]
            AUTH[USER / PASS / QUIT]
            NAV[CWD / CDUP / PWD / MKD / RMD]
            XFER[RETR / STOR / LIST]
            MODE[PORT / PASV / TYPE]
        end
    end

    FS[(Local filesystem)]

    CC <-->|"TCP control (default :2300)"| MS
    MS -->|"pthread_create"| Threads
    MS --> CFG
    MS --> QOS
    Threads --> DEC --> Handlers
    DC <-->|"TCP data (PORT or PASV)"| XFER
    XFER --> FS
    NAV --> FS
Loading

Source layout

File Role
server.c Entry point, socket setup, connection admission, thread spawning
server_func.c Socket helpers, command decoding, FTP command implementations, QoS throttling
server_func.h ConnectionStatus struct and function declarations
config.ini Optional per-IP bandwidth reservations
compile.sh Build script (gcc) and convenience launcher

Control vs. data channels

FTP uses two TCP connections: a persistent control channel for commands and responses, and a short-lived data channel for file and directory transfers.

sequenceDiagram
    participant C as FTP Client
    participant CTL as Control Socket
    participant S as Server Thread
    participant DATA as Data Socket
    participant FS as Filesystem

    C->>CTL: Connect (e.g. port 2300)
    CTL->>C: 220 Service ready for new user

    alt Active mode (PORT)
        C->>CTL: PORT h1,h2,h3,h4,p1,p2
        CTL->>C: 200 Command okay
        C->>CTL: RETR file.txt
        S->>DATA: connect to client IP:port
        CTL->>C: 150 File status okay
        S->>FS: read file
        S->>DATA: stream bytes (rate-limited)
        CTL->>C: 250 Requested file action okay
    else Passive mode (PASV)
        C->>CTL: PASV
        CTL->>C: 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)
        C->>CTL: RETR file.txt
        C->>DATA: connect to server IP:port
        CTL->>C: 150 File status okay
        S->>FS: read file
        S->>DATA: stream bytes (rate-limited)
        CTL->>C: 250 Requested file action okay
    end
Loading

Request handling flow

Each accepted client is handled in its own multUser thread. Commands are parsed once per read loop iteration and dispatched through a switch statement.

flowchart LR
    A[accept on control socket] --> B{clients < MAX_CLIENTS<br/>and bandwidth OK?}
    B -->|No| C[421 Service not available]
    B -->|Yes| D[Reserve client bandwidth]
    D --> E[pthread_create → multUser]
    E --> F[read command]
    F --> G[decode_message]
    G --> H{command type}
    H -->|USER/PASS/CWD/...| I[func_* handler]
    H -->|unknown| J[202 not implemented]
    I --> K[write response]
    J --> K
    K --> L{connection_ok?}
    L -->|Yes| F
    L -->|No QUIT| M[Release bandwidth, close socket]
Loading

QoS and bandwidth allocation

The server maintains a global bandwidth budget (taxa_servidor). Each client receives a reserved rate (taxa_transmissao) at connect time. Transfers throttle by sending at most that many bytes per second, pausing with usleep when the per-second quota is reached.

flowchart TD
    START[Client connects] --> LOOKUP{IP in config.ini?}
    LOOKUP -->|Yes| CUSTOM[Use configured rate]
    LOOKUP -->|No| DEFAULT["Use fair share:<br/>taxa_servidor / MAX_CLIENTS"]
    CUSTOM --> CHECK{"taxa_atual + rate ≤ taxa_servidor?"}
    DEFAULT --> CHECK
    CHECK -->|No| REJECT[421 — reject connection]
    CHECK -->|Yes| ACCEPT[Accept + reserve rate]
    ACCEPT --> XFER[RETR / STOR / LIST transfers]
    XFER --> THROTTLE["Byte-by-byte send/receive<br/>pause after rate bytes/sec"]
    THROTTLE --> DISCONNECT[Client disconnects]
    DISCONNECT --> RELEASE[Release reserved bandwidth]
Loading

Installation and usage

Build and run

Make the build script executable, then run it:

chmod +x compile.sh
./compile.sh [interface] [port] [max_connections] [server_bandwidth_bytes]

The script compiles with GCC:

gcc server*.c -o server.out -lpthread

You can also compile and run the binary directly:

gcc server*.c -o server.out -lpthread
./server.out [interface] [port] [max_connections] [server_bandwidth_bytes]

Defaults

Parameter Default Description
Interface lo (loopback) Network interface to bind
Port 2300 Control-channel TCP port
Max connections 20 Simultaneous client threads
Server bandwidth 2000000 (2 MB) Total bytes/sec budget across all clients

Ports ≤ 1024 are rejected (insufficient privileges); the server falls back to port 2300.

Start the server before the client

Always start the server first. Connecting before the server is listening produces:

421 Service not available, closing control connection.

Examples

Build, specify interface, port, and connection limit:

$ ./compile.sh wlp2s0 2300 5
Compilando...
Gotcha!
--------------------------------------------------------------------------------
Info: Interface selecionada: wlp2s0.
Info: Utilizando interface selecionada.
Info: Porta selecionada: 2300.
Info: Número máximo de clientes conectados simultaneamente: 5.
Info: Utilizando limite total da taxa de transmissão padrão: 2000000.
Info: Socket criado com sucesso.
Info: Arquivo de configuração não encontrado, utilizando taxa padrão para todos os usuários.
Info: Rodando servidor em: 192.168.1.166:2300.
--------------------------------------------------------------------------------

Run with all defaults (loopback, port 2300, 20 connections):

$ ./server.out
--------------------------------------------------------------------------------
Info: Interface não informada, utilizando interface padrão: lo.
Info: Porta padrão selecionada: 2300.
Info: Número máximo de clientes conectados simultaneamente não informado, utilizando valor padrão: 20.
Info: Utilizando limite total da taxa de transmissão padrão: 2000000.
Info: Socket criado com sucesso.
Info: Arquivo de configuração não encontrado, utilizando taxa padrão para todos os usuários.
Info: Rodando servidor em: 127.0.0.1:2300.
--------------------------------------------------------------------------------

On client connect:

--------------------------------------------------------------------------------
Info: Conexão estabelecida com: 127.0.0.1:48830.
Info: Número de conexões atualmente: 1.
Info: Taxa de transmissão reservada/Taxa máxima definida: 100000/2000000.
Send: 220 Service ready for new user.
--------------------------------------------------------------------------------

RFC 959 minimum implementation

Per RFC 959, servers must support at least:

Category Required values
TYPE ASCII Non-print (A)
MODE Stream
STRUCTURE File, Record
Commands USER, QUIT, PORT, TYPE, MODE, STRU, RETR, STOR, NOOP

Default transfer parameters:

Parameter Default
TYPE ASCII Non-print
MODE Stream
STRU File

Implemented commands

Minimum set (fully implemented)

Command Description
USER Accept username (no validation)
PASS Accept password (no validation)
QUIT Close control connection
PORT Active data mode — client provides IP and port
PASV Passive data mode — server listens on ephemeral port
TYPE Set transfer type (ASCII A only)
RETR Download a file
STOR Upload a file
NOOP No-operation keepalive

Additional commands

Command Description
LIST Directory listing via ls -l
CWD Change working directory
CDUP Change to parent directory
PWD Print working directory
MKD Create directory
RMD Remove directory
SYST Report system type (215 UNIX system type.)

Any other RFC 959 command receives:

202 Command not implemented, superfluous at this site.

QoS configuration

Without config.ini

If config.ini is missing, every client receives an equal share of the server bandwidth:

default_rate = server_bandwidth / max_connections

For the defaults: 2,000,000 / 20 = 100,000 B/s per client.

With config.ini

Create a config.ini file in the working directory. Each line maps a client IP to a reserved transfer rate:

172.16.14.88 1 M
172.16.14.55 2 M
192.168.1.166 500 K
127.0.0.1 10 B

Format: <ip_address> <number> <unit>

Unit letter Meaning
G / g Gigabytes per second
M / m Megabytes per second
K / k Kilobytes per second
B / b Bytes per second

All bandwidth values are stored and enforced in bytes per second, even when log messages omit the unit.

Connections are rejected if a client's reserved rate would cause taxa_atual + client_rate to exceed taxa_servidor. Only clients with a guaranteed slice of the total budget are admitted.

Transfer logging

During RETR, STOR, or LIST, the server logs transfer size, elapsed time, and effective throughput:

--------------------------------------------------------------------------------
Recv: RETR config.ini
Info: Arquivo selecionado: config.ini.
Send: 150 File status okay; about to open data connection.
Info: Tamanho do arquivo: 73B.
Info: Tempo de processamento: 7.001747s.
Info: Taxa de processamento: 10.43B/s.
Send: 250 Requested file action okay, completed.
--------------------------------------------------------------------------------

Reported rates can differ from what an FTP client displays because timing includes non-network work (counter updates, scheduling). Larger discrepancies are common on STOR, where clients often send data in bursts while the server processes it byte-by-byte with throttling.

Requirements

  • GCC
  • POSIX threads (libpthread)
  • Linux networking headers (netinet, arpa/inet, etc.)

About

Multithreaded FTP server in C with RFC 959 support, active/passive transfers, and per-client bandwidth throttling (QoS)

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Contributors