Skip to content

xsukax/XMail

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 

Repository files navigation

XMail — Federated Encrypted Mail System

Version 3.4.0 · Single-file PHP · AES-256-GCM · Federated · Self-hosted

License: GPL v3 PHP SQLite


Table of Contents


Project Overview

XMail is a self-hosted, federated, end-to-end encrypted mail system delivered as a single PHP file (xmail.php). It is designed for developers, privacy advocates, and organisations who require full control over their messaging infrastructure without relying on third-party mail providers.

Unlike traditional SMTP-based email, XMail uses a custom federation protocol over HTTPS with a cryptographic handshake to verify sender identity before delivery. All message bodies and attachments are encrypted with AES-256-GCM using a per-message password chosen by the sender — the server stores only ciphertext and never has access to the plaintext content.

XMail requires no external dependencies, no package manager, and no database server. Drop a single file onto any PHP-capable web host and you have a fully functional, secure, private mail node that can communicate with every other XMail instance on the internet.


Security and Privacy Benefits

XMail was designed from the ground up with a defence-in-depth security posture. Every layer of the stack — from transport to storage — incorporates hardening measures.

Cryptography

Layer Algorithm Detail
Key derivation PBKDF2-SHA256 100,000 iterations, 16-byte random salt per message
Message encryption AES-256-GCM 12-byte IV, 16-byte authentication tag
Attachment encryption AES-256-GCM Same key as message body; encrypted before disk write
Stored filenames bin2hex(random_bytes(16)) No correlation between original and stored names

The encryption key is never stored on the server. It is derived at runtime from the password supplied by the user in the browser. Even direct database access reveals nothing but opaque ciphertext.

Transport Security

  • HSTS (Strict-Transport-Security: max-age=31536000; includeSubDomains) is sent automatically when the application is served over HTTPS.
  • Federation requests are made over HTTPS first, with HTTP as a fallback, and never to private or reserved IP ranges (SSRF protection).
  • Redirect validation on all outbound federation fetches — redirects to private hosts are blocked, and follow_location is disabled at the stream-context level.

HTTP Security Headers

Every response carries the following headers:

X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; style-src 'self' 'nonce-{random}'; script-src 'self' 'nonce-{random}'
Strict-Transport-Security: max-age=31536000; includeSubDomains  (HTTPS only)

The CSP nonce (random_bytes(18)) is regenerated on every request, preventing inline script injection.

Session and Authentication

  • Sessions use Secure, HttpOnly, and SameSite=Lax cookie attributes.
  • CSRF tokens (48 hex characters, hash_equals comparison) protect every state-changing form — including registration, settings, and all admin actions.
  • Login rate limiting: after 10 failed attempts from a single IP within a 15-minute window, the IP is blocked for a further 15 minutes. Only REMOTE_ADDR is used — X-Forwarded-For and similar headers are ignored to prevent IP spoofing.
  • Passwords are hashed with bcrypt via password_hash() / password_verify().

Federation Security

  • Sender verification handshake: when a message arrives at ?api=receive, the receiving server performs a back-channel POST to ?api=verify_send on the sender's server. The sender's server confirms the token, from-address, and intended recipient before delivery proceeds. This prevents local-sender spoofing — there is no bypass even for messages arriving from the same server.
  • Token binding: outbound tokens are bound to the (token, to_user) pair, preventing token replay across different recipients.
  • Token TTL: tokens expire after 3600 seconds and are deleted on expiry check or successful use.
  • Host Header Injection: BASE_URL is computed once at boot from a sanitised HTTP_HOST value and stored as a constant, preventing host header injection throughout the application.

Storage Security

  • attachments/.htaccess is auto-generated with Deny from all, Options -Indexes, and a PHP execution block so uploaded files can never be served directly or executed.
  • A root-level .htaccess is auto-generated to deny direct access to .db, .sqlite, and .sqlite3 files.
  • CRLF injection in Content-Disposition headers is prevented by using the filename* RFC 5987 parameter exclusively.
  • Attachment MIME types are validated against an explicit allowlist using mime_content_type() on the actual file content, not the client-supplied type.

Features and Advantages

  • Single-file deployment — the entire application is contained in xmail.php. No framework, no Composer, no npm.
  • End-to-end encryption — AES-256-GCM with PBKDF2 key derivation. The server holds only ciphertext.
  • Federated architecture — send encrypted messages to users on any other XMail instance using the user@host/xmail.php address format.
  • Encrypted attachments — files up to 10 MB are encrypted with the message key before being written to disk.
  • EML export (v3.4.0) — decrypted messages (body + attachments) can be exported as standards-compliant RFC 2822 .eml files and opened in Thunderbird, Apple Mail, Outlook, or any compliant mail client.
  • Threaded conversations — replies are grouped by thread UID, preserving conversation context.
  • Draft support — compose and save drafts; resume at any time.
  • CC and BCC — full CC/BCC support on both local and federated sends.
  • Folder management — Inbox, Sent, Drafts, and Trash with bulk-action support.
  • Unread counts — per-folder unread badge in both sidebar and bottom navigation.
  • Responsive UI — works on desktop and mobile; collapsible sidebar and a bottom navigation bar for small screens.
  • Admin panel — user management (create, delete, toggle admin), registration gating (open/closed), and system statistics.
  • SQLite database — zero-configuration, file-based persistence with WAL mode and foreign key enforcement.
  • No external dependencies — requires only pdo_sqlite and openssl PHP extensions.

Installation Instructions

Requirements

Requirement Minimum
PHP 8.0 or later
PHP extensions pdo_sqlite, openssl
Web server Apache, Nginx, Caddy, or any server capable of running PHP
HTTPS Strongly recommended for production
Writable directory The directory containing xmail.php must be writable by the web server process

Step 1 — Download the file

Clone the repository or download xmail.php directly:

git clone https://github.com/xsukax/XMail.git
cd XMail

Or download the single file:

curl -O https://raw.githubusercontent.com/xsukax/XMail/main/xmail.php

Step 2 — Deploy to your web root

Copy xmail.php to your web server's document root or a subdirectory:

cp xmail.php /var/www/html/xmail.php

Ensure the directory is writable by the web server so XMail can create xmail.db and the attachments/ directory automatically:

chown www-data:www-data /var/www/html/
chmod 750 /var/www/html/

Step 3 — Verify PHP extensions

php -m | grep -E 'pdo_sqlite|openssl'

Both pdo_sqlite and openssl must appear in the output. If either is missing, see php.ini Configuration.

Step 4 — Configure your web server (Apache example)

<VirtualHost *:443>
    ServerName mail.yourdomain.com
    DocumentRoot /var/www/html

    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/yourdomain.crt
    SSLCertificateKeyFile /etc/ssl/private/yourdomain.key

    <Directory /var/www/html>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

For Nginx:

server {
    listen 443 ssl;
    server_name mail.yourdomain.com;

    ssl_certificate     /etc/ssl/certs/yourdomain.crt;
    ssl_certificate_key /etc/ssl/private/yourdomain.key;

    root /var/www/html;
    index xmail.php;

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Block direct access to the database and attachments
    location ~* \.(db|sqlite|sqlite3)$ { deny all; }
    location /attachments/ { deny all; }
}

Step 5 — Open in browser

Navigate to https://mail.yourdomain.com/xmail.php. On first load, XMail initialises the database, creates the attachments/ directory, and redirects to the first-run setup screen.


Usage Guide

First-Run Setup

On the very first visit XMail detects the absence of an admin account and presents a setup form. Fill in a username and strong password to create the initial administrator account. Subsequent registrations are controlled by the Admin Panel (open or closed).

flowchart TD
    A([Browser: GET /xmail.php]) --> B{Admin account\nexists?}
    B -- No --> C[First-Run Setup Form]
    C --> D[Create Admin User]
    D --> E[Redirect to Inbox]
    B -- Yes --> F{Logged in?}
    F -- No --> G[Login Form]
    G --> H{Credentials valid\n& not rate-limited?}
    H -- Yes --> E
    H -- No --> I[Record failed attempt\nShow error]
    F -- Yes --> E
Loading

Composing and Sending Mail

  1. Click ✉️ Compose in the navigation.
  2. Enter the recipient address in user@host/path format (e.g. alice@mail.example.com/xmail.php).
  3. Optionally add CC and BCC recipients.
  4. Enter a subject and message body.
  5. Set a message password — this is the encryption key. Both the sender and recipient need this password to read the message.
  6. Attach files if needed (up to 10 MB per attachment; JPEG, PNG, PDF, ZIP, DOCX, XLSX, and plain text are accepted).
  7. Click Send or Save Draft.

Important: The message password is never transmitted to the server. Choose a secure password and communicate it to your recipient through a separate channel.

Reading and Decrypting Messages

flowchart LR
    A([Open Inbox]) --> B[Select message]
    B --> C{Thread key\nin session?}
    C -- Yes --> D[Decrypt & display body]
    C -- No --> E[Show 🔒 locked prompt]
    E --> F[Enter message password]
    F --> G{Decryption\nsuccessful?}
    G -- Yes --> D
    G -- No --> H[Show decryption error]
    D --> I{Attachments?}
    I -- Yes --> J[Decrypt & download attachment]
    I -- No --> K([Done])
    J --> K
Loading

Once a thread is successfully decrypted, the session retains the thread key so you do not need to re-enter the password while navigating within the same session.

Federated Messaging

XMail uses a three-step federation protocol to deliver messages cross-instance while verifying sender identity:

sequenceDiagram
    participant Sender as Sender's Browser
    participant SenderServer as Sender's XMail Instance
    participant ReceiverServer as Receiver's XMail Instance

    Sender->>SenderServer: POST compose (to=alice@remote/xmail.php)
    SenderServer->>SenderServer: Generate verify_token, store in outbound_tokens
    SenderServer->>SenderServer: Encrypt message body & attachments
    SenderServer->>ReceiverServer: POST ?api=receive {body_enc, verify_token, ...}
    ReceiverServer->>SenderServer: POST ?api=verify_send {verify_token, from_addr, to_user}
    SenderServer-->>ReceiverServer: {ok: true, from_addr: "..."}
    ReceiverServer->>ReceiverServer: Validate handshake & store ciphertext
    ReceiverServer-->>SenderServer: {ok: true}
    SenderServer-->>Sender: Message delivered / show confirmation
Loading

Cross-instance addresses use the format:

username@hostname/xmail.php

For example: bob@secure.example.org/xmail.php

EML Export

After successfully decrypting a message thread, an Export .eml button appears next to each message. Clicking it downloads a fully standards-compliant RFC 2822 / MIME .eml file containing the decrypted body and all decrypted attachments. The exported file can be opened in:

  • Mozilla Thunderbird
  • Apple Mail
  • Microsoft Outlook
  • GNOME Evolution
  • Any RFC 2822-compliant mail client

The export endpoint (?dl_eml=<mail_id>) requires an active session with the thread key present — it is not accessible without prior decryption in the UI.

Admin Panel

Access the admin panel via ⚙️ More → Admin (visible only to admin-role users).

Capability Description
View statistics Total users, total messages, registration status
Toggle registration Open or close new user registration
Grant / revoke admin Promote or demote any other user
Delete user Permanently removes the user and all their messages

Federation Architecture

graph TD
    subgraph Instance A ["Instance A (mail.alice.net/xmail.php)"]
        UA[User: alice]
        DBA[(xmail.db)]
        ATTA[attachments/]
    end

    subgraph Instance B ["Instance B (mail.bob.org/xmail.php)"]
        UB[User: bob]
        DBB[(xmail.db)]
        ATTB[attachments/]
    end

    subgraph Instance C ["Instance C (mail.carol.io/xmail.php)"]
        UC[User: carol]
        DBC[(xmail.db)]
        ATTC[attachments/]
    end

    UA -- "HTTPS POST ?api=receive" --> UB
    UA -- "HTTPS POST ?api=receive" --> UC
    UB -- "HTTPS POST ?api=receive" --> UA
    UB -- "Back-channel verify_send" --> UA
    UA -- "Back-channel verify_send" --> UB
Loading

Each instance is fully autonomous. There is no central registry, directory server, or shared infrastructure.

Application Workflow

stateDiagram-v2
    [*] --> FirstRun : No admin account
    FirstRun --> Inbox : Admin created
    [*] --> Login : Admin exists
    Login --> Inbox : Authenticated
    Inbox --> Compose : New message
    Inbox --> Thread : Open message
    Thread --> Locked : Key not in session
    Locked --> Thread : Enter password
    Thread --> EMLExport : Export .eml (decrypted only)
    Thread --> Reply : Reply to thread
    Reply --> Compose
    Compose --> Sent : Delivered
    Compose --> Drafts : Save draft
    Drafts --> Compose : Resume draft
    Sent --> [*]
    Inbox --> Trash : Move to trash
    Trash --> Inbox : Restore
    Trash --> [*] : Delete permanently
Loading

php.ini Configuration

XMail requires two PHP extensions: pdo_sqlite and openssl. On most shared and managed hosts these are enabled by default. If they are absent, enable them in your php.ini:

; Required extensions
extension=pdo_sqlite
extension=openssl

; Recommended settings for file upload support
file_uploads = On
upload_max_filesize = 10M
post_max_size = 12M

; Recommended session security
session.cookie_httponly = 1
session.cookie_samesite = Lax
session.use_strict_mode = 1

; Recommended for production
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log

After editing php.ini, restart your PHP-FPM or web server process:

# PHP-FPM
sudo systemctl restart php8.2-fpm

# Apache with mod_php
sudo systemctl restart apache2

Verify the configuration is active:

php -r "echo extension_loaded('pdo_sqlite') ? 'pdo_sqlite OK' : 'pdo_sqlite MISSING'; echo PHP_EOL;"
php -r "echo extension_loaded('openssl')    ? 'openssl OK'    : 'openssl MISSING';    echo PHP_EOL;"

File Structure

After first launch the following files and directories are created automatically:

XMail/
├── xmail.php          # The entire application (source of truth)
├── xmail.db           # SQLite database (auto-created on first run)
├── .htaccess          # Blocks direct access to .db / .sqlite files (auto-created)
└── attachments/       # Encrypted attachment storage (auto-created)
    └── .htaccess      # Blocks direct HTTP access to attachment files (auto-created)

Security note: Ensure xmail.db and the attachments/ directory are not publicly accessible. The auto-generated .htaccess files handle this on Apache. On Nginx, apply the deny all directives shown in the Installation Instructions.


License

This project is licensed under the GNU General Public License v3.0 — see https://www.gnu.org/licenses/gpl-3.0 for the full license text.

About

A self-hosted, federated, zero-knowledge encrypted mail system in a single PHP file. AES-256-GCM encryption, PBKDF2 key derivation, and encrypted attachments ensure the server never sees your message password. No SMTP. No external dependencies. Just drop xmail.php and go.

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages