An adversarial review of the request path turned up nine issues, all reproduced against a running server before and after the fix. The most serious was a permanent denial of service. The in-flight request counter was decremented on paths that never incremented it; being unsigned it wrapped to usize::MAX, after which every request was answered with "41 Server unavailable" until the process restarted. A client that completed the TLS handshake and disconnected without sending a request was enough -- a port scan, a health check, a cancelled page load. The counter is replaced by a semaphore permit released on every exit path. Two further remote denials of service: an error from accept() propagated out of main and ended the process, so exhausting the descriptor limit killed the server; and responses were read whole into memory, so cost scaled with concurrent requests times file size. Accept errors are now logged and retried after a backoff, and responses stream in 64 KiB chunks capped at 64 MiB. Peers that open a socket and never send a ClientHello are bounded by a 10s handshake timeout and a connection cap (max_connections, default 512). Every virtual host was served whichever certificate came first in a HashMap, which varies per process, so a host's certificate changed between restarts. Because Gemini clients pin certificates on first use, this trained users to dismiss the mismatch warning that would otherwise reveal interception. Certificates are now selected by SNI with a deterministic fallback, so unknown hosts still complete a handshake and receive 53. Dotfiles inside a content root were public, exposing .git/config and any credentials in it for capsule roots that are git working copies. Path resolution is now in-tree: reject non-plain components, then compare canonical prefixes. This replaces path-security, an unaudited micro-crate in the security boundary that also rejected legitimate filenames containing '%', '~' or '$'. rustls moves 0.21 -> 0.23; the 0.21 branch is end of life. TLS 1.2 and 1.3 only, as before. Malformed requests logged attacker-controlled text at ERROR on every request, letting a client drive log volume; client-caused conditions now log at debug, truncated. Hostname routing accepts the authority forms the specification permits (mixed case, explicit port, userinfo, trailing dot), which previously returned 53. Per-host port and log_level were accepted and silently ignored, and now warn at startup. Adds tests/connection_lifecycle.rs covering the counter underflow, the oversized-response refusal, hidden files and authority normalization. Fixes the test client, which sent host:port as SNI. 42 -> 56 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
No EOL
2.7 KiB
TOML
73 lines
No EOL
2.7 KiB
TOML
# Pollux Gemini Server Configuration
|
|
#
|
|
# This is an example configuration file for the Pollux Gemini server.
|
|
# Copy this file to /etc/pollux/config.toml and customize the values below.
|
|
#
|
|
# The Gemini protocol is specified in RFC 1436: https://tools.ietf.org/rfc/rfc1436.txt
|
|
|
|
# For additional hostnames, add more sections like:
|
|
# ["blog.example.com"]
|
|
# root = "/var/gemini/blog"
|
|
# cert = "/etc/pollux/tls/blog.crt"
|
|
# key = "/etc/pollux/tls/blog.key"
|
|
|
|
# Server network configuration
|
|
#
|
|
# bind_host: IP address or interface to bind the server to
|
|
# - "0.0.0.0" = listen on all interfaces (default)
|
|
# - "127.0.0.1" = localhost only
|
|
# - "::" = IPv6 all interfaces
|
|
# - Specific IP = bind to that address only
|
|
bind_host = "0.0.0.0"
|
|
|
|
# port: TCP port to listen on
|
|
# - Default Gemini port is 1965
|
|
# - Ports below 1024 require root privileges
|
|
# - Choose a different port if 1965 is in use
|
|
port = 1965
|
|
|
|
# Request limiting
|
|
#
|
|
# max_concurrent_requests: Maximum number of requests processed at once
|
|
# - Requests above the limit are answered with "41 Server unavailable"
|
|
# - Must be between 1 and 1000000; the server will not start on 0
|
|
# - Typical values: 100-10000 depending on server capacity
|
|
max_concurrent_requests = 1000
|
|
|
|
# max_connections: Maximum number of connections handled at once
|
|
# - Connections above the limit are dropped immediately, which keeps the
|
|
# listener responsive under a flood of half-open connections
|
|
# - Keep this below the process file-descriptor limit (LimitNOFILE in the
|
|
# systemd unit, or `ulimit -n`)
|
|
# - Must be between 1 and 1000000; defaults to 512 if unset
|
|
max_connections = 512
|
|
|
|
# Logging configuration
|
|
#
|
|
# Logging is controlled by the RUST_LOG environment variable, not this file.
|
|
# In the systemd unit, set for example:
|
|
# Environment=RUST_LOG=info
|
|
# Levels, least to most verbose: error, warn, info, debug, trace
|
|
|
|
# Host configuration
|
|
# Each hostname needs its own section with root, cert, and key settings
|
|
["example.com"]
|
|
# Directory containing your Gemini files (.gmi, .txt, images, etc.)
|
|
# The server will serve files from this directory and its subdirectories.
|
|
# Default index file is 'index.gmi' for directory requests.
|
|
#
|
|
# IMPORTANT: The server needs READ access to this directory.
|
|
# Make sure the service user can read all files here.
|
|
root = "/var/gemini"
|
|
|
|
# TLS certificate and private key files
|
|
# These files are required for TLS encryption (Gemini requires TLS).
|
|
#
|
|
# For self-signed certificates (development/testing):
|
|
cert = "/etc/pollux/tls/cert.pem"
|
|
key = "/etc/pollux/tls/key.pem"
|
|
#
|
|
# Generate self-signed certs with:
|
|
# openssl req -x509 -newkey rsa:4096 -keyout /etc/pollux/tls/key.pem -out /etc/pollux/tls/cert.pem -days 365 -nodes -subj "/CN=example.com"
|
|
#
|
|
# For Let's Encrypt certificates, use paths under /etc/letsencrypt/live/ |