This project packages SMTP ingress, an optional web test UI, and IT tools.
4.1K
SMTP ingress (Echo Mail with SPF, DKIM, DMARC analysis), an optional web test UI for one-shot recipient addresses, and IT tools (DNS, GeoIP, TLS/certs, mail generators, and more) in a single Alpine-based Python image.
onesystems/tools (latest, version tags e.g. 1.3.0)python -m onesystems_tools (legacy: python echo_maild.py)docker-compose.yml (profiles bridge and host-tls)Designed for public exposure: SSRF-filtered outbound targets, HTTP rate limits, CSP with per-request nonces, trusted-proxy checks, non-root process, no open SMTP relay by default.
test-<token>@… web flowTOOLS_DNS_SERVERS)/api/tools/…/generators), client Autoconfig (SRV + Mozilla XML), Exchange Server Check, Base64/password/hashes, optional LibreSpeed under /speedtest (gated), and morefiles/ (Bootstrap 5.3.3, no CDN required)TOOLS_MATOMO_*) and ad slots (TOOLS_WEB_ADS_*) for trusted HTML onlyno-new-privileges, non-root runtimedocker pull onesystems/tools:latest
Minimal Compose (bridge / port mapping):
services:
tools:
image: onesystems/tools:latest
restart: unless-stopped
ports:
- "25:8025"
# With TOOLS_WEB_ENABLED=true:
# - "8080:8080"
environment:
TOOLS_MAIL_ADDRESSES: [email protected]
TOOLS_MAIL_HOSTNAME: tools.example.com
# TOOLS_WEB_ENABLED: "true"
# WEB_LISTEN_PORT: "8080"
docker-compose.yml)Pick exactly one profile:
bridge — host 25 → container 8025 (classic mapping)host-tls — host network + Caddy (HTTPS) + web on 127.0.0.1:8080 + optional LibreSpeeddocker compose --profile bridge up -d
# or
cp .env.example .env # set secrets if using LibreSpeed
docker compose --profile host-tls up -d
Do not run both profiles on the same host if they compete for port 25.
src/onesystems_tools/
__main__.py # python -m onesystems_tools
config.py / envutil.py
smtp/ # server, security, analysis, outbound
web/ # FastAPI UI, store, middleware
api/ # /api/tools JSON router
services/ # net_guard, rate_limit, geoip, toolkits
echo_maild.py # thin legacy wrapper → same main()
files/ # static assets (css/js/vendor/logo)
speedtest/ # LibreSpeed sidecar Dockerfile + branding
caddy/ # Caddyfile for host-tls profile
tests/ # pytest
Env vars use the TOOLS_* / SMTP_* / WEB_* prefixes (ECHO_* / ECHO_TOOLS_* are no longer read).
Multi-arch example (set VERSION so the image labels / TOOLS_VERSION match the release):
docker buildx create --name multiarch --driver docker-container --use
docker buildx inspect --bootstrap
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg VERSION="1.3.0" \
--build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--tag onesystems/tools:1.3.0 \
--tag onesystems/tools:latest \
.
If VERSION is omitted, the image resolves the version from pyproject.toml at build time (never leaves a bare dev placeholder).
onesystems_tools.web)| Variable | Purpose |
|---|---|
TOOLS_WEB_SUBPATH | Base path for the web test UI (default /echo). |
TOOLS_WEB_ENABLED | true/1: start the HTTP server (with WEB_LISTEN_*). |
TOOLS_WEB_FILES_DIR | Static files directory (default /app/files in the image). |
TOOLS_WEB_LOGO / TOOLS_WEB_FAVICON | Filenames inside the files dir. |
TOOLS_WEB_BRAND | Navbar brand text (default Tools). |
TOOLS_WEB_APP_NAME | application-name / og:site_name. Empty → TOOLS_WEB_BRAND, else Tools. |
TOOLS_WEB_OG_LOCALE | Open Graph og:locale (default en_US). |
TOOLS_WEB_SUBTITLE | Optional subtitle in the compact top bar. |
TOOLS_WEB_FOOTER_COPYRIGHT | Footer text; {YEAR} → current year. Empty → built-in default. |
TOOLS_WEB_PRIVACY_URL / TOOLS_WEB_IMPRINT_URL | Optional footer links. |
TOOLS_WEB_ADS_* | Raw HTML slots: AFTER_NAV, MAIN_TOP, MAIN_BOTTOM, FOOTER (trusted admins only). |
TOOLS_WEB_ADS_ORIGINS | Extra ad-server origins for CSP if auto-detection is not enough. |
TOOLS_CSP_EXTRA_ORIGINS | Origins added to connect-src / frame-src. |
TOOLS_CSP_SCRIPT_SRC / TOOLS_CSP_CONNECT_SRC / TOOLS_CSP_FRAME_SRC | Optional origin allowlists. HTML CSP script-src uses nonce + strict-dynamic. |
TOOLS_IT_TOOLS_ENABLED | true/1: IT tools (HTML + /api/tools/…). false: mail/web test only. |
TOOLS_WEB_CURL_ROOT_IP | Default true: GET / with curl/… UA returns client IP as plain text. |
TOOLS_WEB_META_* | SEO / Open Graph (DESCRIPTION, KEYWORDS, AUTHOR, SITE_URL, …). |
TOOLS_MATOMO_URL / TOOLS_MATOMO_SITE_ID | Optional Matomo. |
TOOLS_MATOMO_COOKIE_DOMAIN | e.g. *.tools.example.com; empty → omit. |
Static files are served under /web-assets/…. Mounting ./files:/app/files replaces the entire directory — include css/, js/, vendor/ or mount single files only.
| Variable | Purpose |
|---|---|
WEB_LISTEN_HOST / WEB_LISTEN_PORT | Bind (default 0.0.0.0 / 8080). Behind a reverse proxy prefer 127.0.0.1. |
WEB_FORWARDED_ALLOW_IPS | Peers whose X-Forwarded-* Uvicorn accepts (default 127.0.0.1,::1). |
TOOLS_TRUSTED_PROXIES | App-level CIDRs trusted for client IP / scheme / host. Empty = never trust X-Forwarded-*. |
UVICORN_LOG_LEVEL | Default warning. |
WEB_GEO_* | Optional HTTP geo allow/deny (MaxMind Country/City MMDB). |
| Variable | Purpose |
|---|---|
TOOLS_WEB_TTL_PENDING | Seconds to wait for mail after minting a token (default 3600). |
TOOLS_WEB_TTL_STORED | Seconds to keep stored reports (default 86400). |
| Variable | Purpose |
|---|---|
TOOLS_MAIL_ADDRESSES | Comma-separated recipient addresses. |
TOOLS_RECEIVER_DOMAIN | Optional explicit domain for analysis. |
TOOLS_MAIL_HOSTNAME | HELO / hostname in messages. |
TOOLS_REPLY_ENABLED | true: classic Echo replies; false (default): ingest + web tests only. |
SMTP_RELAY_HOST / PORT / USER / PASSWORD / SSL | Optional submission relay (e.g. Mailcow). |
TOOLS_DNS_SERVERS | Optional public resolver IPs for lookups. |
SMTP_LISTEN_HOST / SMTP_LISTEN_PORT | SMTP bind (default 0.0.0.0 / 8025). |
SMTP_* | Rate limits, size limits, allow/deny lists, geo — see comments in docker-compose.yml. |
UI under /generators (SPF wizard, DMARC, MTA-STS, TLS-RPT). Live preview via /api/tools/mail-generate and related load endpoints. No extra env vars required beyond TOOLS_IT_TOOLS_ENABLED.
| Variable | Purpose |
|---|---|
TOOLS_GEOIP_DB | Path to GeoLite2-City or Country .mmdb inside the container. |
TOOLS_SUBDOMAIN_CAP / TOOLS_PORTSCAN_CAP / … | Caps — see onesystems_tools.api.router defaults. |
TOOLS_RIPE_TIMEOUT | RIPEstat HTTP timeout (default 12). |
TOOLS_WHOIS_TIMEOUT | WHOIS / RDAP timeout (default 15). |
TOOLS_LOOKUP_CACHE_TTL | In-memory cache TTL for WHOIS/AS/RIPEstat (600; 0 = off). |
services/net_guard)User-supplied hosts/IPs/URLs are checked before any outbound connection (public IPs only; DNS-rebinding mitigated by connecting to the validated IP).
| Variable | Purpose |
|---|---|
TOOLS_ALLOW_PRIVATE_TARGETS | Default false. Opt-in for private/loopback targets (trusted labs only). |
TOOLS_EXTRA_BLOCK_CIDRS | Extra CIDRs always blocked. |
services/rate_limit)| Variable | Default | Purpose |
|---|---|---|
TOOLS_HTTP_API_RATE_PER_MIN | 60 | Sustained /api/… requests per client bucket. |
TOOLS_HTTP_API_BURST | 20 | Burst for /api/…. |
TOOLS_HTTP_UI_RATE_PER_MIN | 300 | Sustained UI / assets. |
TOOLS_HTTP_UI_BURST | 120 | Burst for UI / assets. |
TOOLS_HTTP_IPV6_PREFIX | 64 | IPv6 bucket key. |
TOOLS_HTTP_IPV4_PREFIX | 32 | IPv4 bucket key. |
TOOLS_HTTP_RATE_EXEMPT_CIDRS | (empty) | Networks that skip the limit. |
TOOLS_HTTP_RATE_LOG | true | Log rejected requests. |
Limit hits return 429 with Retry-After.
/speedtest, optional)Optional sidecar (speedtest/Dockerfile) behind Caddy + Tools forward_auth gate (host-tls profile).
| Variable | Purpose |
|---|---|
TOOLS_SPEEDTEST_ENABLED | true: gate active (default false). Needs geo allow and/or deny + MMDB. |
TOOLS_SPEEDTEST_GEO_ALLOW_COUNTRIES | ISO allowlist (e.g. CH,DE,AT,LI). |
TOOLS_SPEEDTEST_GEO_DENY_COUNTRIES | Optional deny list. |
TOOLS_SPEEDTEST_GEO_ALLOW_NON_PUBLIC | Skip geo for private/loopback (default true). |
TOOLS_SPEEDTEST_COOLDOWN_SEC | Pause after a test window (default 300). |
TOOLS_SPEEDTEST_TICKET_TTL_SEC | Session ticket lifetime (default 90). |
TOOLS_SPEEDTEST_MAX_CONCURRENT | Global parallel tickets (default 3). |
TOOLS_SPEEDTEST_TICKET_SECRET | HMAC secret (set a strong value when enabled). |
TOOLS_SPEEDTEST_RESULTS_TTL_HOURS | SQLite retention before purge (default 168). |
TOOLS_SPEEDTEST_DB_PATH | SQLite path in the tools container (same volume as LibreSpeed /database). |
TOOLS_SPEEDTEST_SERVERS_FILE | Optional multi-server JSON path (default via Compose mount). |
TOOLS_SPEEDTEST_STATS_PASSWORD | LibreSpeed /results/stats.php password. |
TOOLS_SPEEDTEST_GDPR_EMAIL | Contact in LibreSpeed privacy text. |
/tools/speedtest (info) → /speedtest/ (LibreSpeed). Override logo with ./speedtest/branding/logo.png../speedtest/servers.json. Missing/empty → local backend only. With 2+ entries a server picker appears..env.example → .env and set TOOLS_SPEEDTEST_TICKET_SECRET / TOOLS_SPEEDTEST_STATS_PASSWORD.TOOLS_GEOIP_DB / the Compose volume at your host path.With TOOLS_WEB_ENABLED=true and HTTP exposed:
http://<host>:8080/<TOOLS_WEB_SUBPATH>/new (default …/echo/new) or /.test-<token>@<domain> from TOOLS_MAIL_ADDRESSES./w/<token> auto-refreshes; /r/<token> shows the report.Outbound connections often use an internal source IP. Plan SPF, PTR, and optional SMTP_RELAY_* accordingly.
| Variable | Example | Notes |
|---|---|---|
TOOLS_REPLY_ENABLED | true | Required — replies are off by default. |
SMTP_RELAY_HOST | mail.example.com | Mailcow hostname. |
SMTP_RELAY_PORT | 587 | STARTTLS (default). Use 465 for SMTPS. |
SMTP_RELAY_USER | [email protected] | Mailbox in Mailcow. |
SMTP_RELAY_PASSWORD | (secret) | Prefer env_file / secrets — do not commit. |
SMTP_RELAY_SSL | true | Optional; port 465 enables SSL automatically. |
Allow sending as the Echo From address and keep SPF/DKIM aligned with Mailcow outbound.
127.0.0.1 at startup are normal (listener check).Connection lost during _handle_client() after QUIT is normal.MAIL FROM:<> DSN-style messages are accepted without reply loops.TOOLS_ALLOW_PRIVATE_TARGETS=true to opt out.X-Forwarded-* only for peers in TOOLS_TRUSTED_PROXIES.strict-dynamic.nobody), no-new-privileges; host-tls adds NET_BIND_SERVICE for port 25.TOOLS_REPLY_ENABLED=false by default — avoid backscatter on public SMTP.TOOLS_WEB_ADS_* as privileged HTML input.WEB_FORWARDED_ALLOW_IPS and TOOLS_TRUSTED_PROXIES.python3 -m venv .venv && .venv/bin/pip install -r requirements.txt -e . pytest
.venv/bin/pytest -q
python -m onesystems_tools
Michael Kleger — OneSystems GmbH
https://www.onesystems.ch · [email protected]
MIT — free for commercial and private use.
Content type
Image
Digest
sha256:d1d9e5a65…
Size
43.1 MB
Last updated
8 days ago
docker pull onesystems/tools