From 310700407e124d6997fda43cd3398ca35681733d Mon Sep 17 00:00:00 2001 From: nahakubuilder Date: Sat, 15 Aug 2026 06:22:02 +0100 Subject: [PATCH] add docker setup --- .dockerignore | 9 +++ README.md | 118 +++++++++++++++++++++++++++-- docker-deploy/.env.example | 9 +++ docker-deploy/Dockerfile | 50 ++++++++++++ docker-deploy/Dockerfile.rspamd | 47 ++++++++++++ docker-deploy/README.md | 98 ++++++++++++++++++++++++ docker-deploy/docker-compose.yml | 51 +++++++++++++ docker-deploy/entrypoint-rspamd.sh | 19 +++++ internal/config/config.go | 12 +-- internal/config/config_test.go | 8 +- main.go | 8 +- 11 files changed, 410 insertions(+), 19 deletions(-) create mode 100644 .dockerignore create mode 100644 docker-deploy/.env.example create mode 100644 docker-deploy/Dockerfile create mode 100644 docker-deploy/Dockerfile.rspamd create mode 100644 docker-deploy/README.md create mode 100644 docker-deploy/docker-compose.yml create mode 100755 docker-deploy/entrypoint-rspamd.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a177203 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +.claude +graphify-out +tests +server_data +ssl_certs +*.db +*.crt +*.key diff --git a/README.md b/README.md index 773954b..db38425 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,89 @@ -# Deploying mailgoserver +# mailgoserver + +A self-hosted email server in one static Go binary: SMTP (send + receive, direct-to-MX +delivery, DKIM signing), IMAP mailbox storage, an admin web dashboard, and a self-service +webmail portal. It's a Go port of PyMTA-server — same feature set, one process instead of +a Python venv + separate services. + +## What it does + +- **SMTP MTA** — accepts mail for domains you configure, relays outbound mail directly + to the recipient's MX (no smarthost needed), signs outgoing mail with DKIM, and + enforces per-domain sender authentication or IP whitelisting so it can't be used as an + open relay. +- **IMAP mailbox storage** — real, retrievable mailboxes (Thunderbird, Outlook, any IMAP + client) with AES-encrypted-at-rest message storage, per-mailbox quotas, send-as + aliases, and filter rules (move to folder, forward, discard, based on from/subject/ + body conditions with AND/OR logic). +- **Spam filtering** — a built-in heuristic score always runs; optionally point it at an + [rspamd](https://rspamd.com) instance for a lot more signal (see `docker-deploy/` for + a container that bundles rspamd for you). +- **Webmail portal** (`/webmail`) — inbox/folders, HTML compose with attachments, + drafts, search, keyboard shortcuts, conversation grouping, recipient autocomplete, + filter-rule management, and PGP (OpenPGP encrypt/decrypt/sign/verify) and S/MIME + (sign/verify) support per mailbox. +- **Admin dashboard** (`/pymta-manager`) — manage domains, senders, mailboxes, DKIM + keys, IP whitelisting, TLS (self-signed or Let's Encrypt via DNS-01: Cloudflare, + Route53, DigitalOcean, Google Cloud DNS), and review email + auth logs. +- **Security hardening built in** — CSRF protection, security headers (CSP, X-Frame- + Options, etc.), Cloudflare-aware trusted-proxy IP resolution, login rate limiting and + account lockout, TOTP + WebAuthn/passkey MFA (admin and mailbox owners), and automatic + temporary IP blacklisting for SMTP/IMAP brute-force or relay-abuse attempts with an + admin-visible Blacklist page and dashboard attack-count tiles. + +## Quick start + +### Docker (fastest) + +```bash +cd docker-deploy +docker compose --profile standalone up -d --build +``` + +See [`docker-deploy/README.md`](docker-deploy/README.md) for the rspamd-bundled variant +and full details (ports, volumes, first-boot config). + +### From source + +```bash +go build -o mailgoserver . +./mailgoserver +``` + +One static binary — no venv, no `pip install`, no gunicorn. Requires Go 1.26+ to build; +the binary itself has no runtime dependencies (pure-Go SQLite driver, no cgo). + +## What to expect on first run + +There's no config to write up front. The first time it starts in a given working +directory, it generates: + +- `settings.ini` — every setting with an inline comment explaining it (SMTP/IMAP ports, + hostname, TLS, DKIM key size, mailbox quotas, MFA enforcement, rate limits, and so + on). Regenerated only if missing — it's never overwritten or merged into on later + runs, so edits stick. +- A self-signed TLS certificate (used until you either supply your own or enable Let's + Encrypt from the admin dashboard). +- An empty SQLite database, with one seeded admin account: username `admin`, password + `Password123!`. Logging in with it **immediately forces** a username + password + change before anything else in the dashboard is reachable — the default credentials + can never be left in place. +- A mailstore master key and a CSRF-signing app secret, both generated once and reused + on every subsequent start — back these up like any other secret (losing the mailstore + master key makes all stored mail unrecoverable, even for admins). + +From there: log into `/pymta-manager`, add a domain (and complete its DNS ownership +verification), add a mailbox or sender, and you're sending/receiving. The bare `/` root +redirects to the webmail login (`/webmail/login`) by default, since most visitors are +mailbox owners, not admins — there's a "Login as Admin" link from there to +`/pymta-manager`. + +Default ports (all configurable in `settings.ini`): SMTP `25`, direct-TLS SMTP `465`, +IMAP `143`, direct-TLS IMAP `993` — the real standard mail ports, so binding them +directly needs root or `setcap` (see below), which the Docker deployment already +handles for you. Admin/webmail HTTP `5000` / HTTPS `5001` stay deliberately +non-privileged; put a reverse proxy or your own `80`/`443` mapping in front of those if +you want the dashboard on standard web ports too. ## Build @@ -7,15 +92,20 @@ cd mailgoserver go build -o mailgoserver . ``` -One static binary — no venv, no `pip install`, no gunicorn. +## Bind ports 25/143/465/993 without root -## Bind ports 25/587 without root +The default SMTP/IMAP ports are the real standard ones now, so running the binary +directly (not via Docker) needs one of: ```bash sudo setcap 'cap_net_bind_service=+ep' ./mailgoserver ``` -Same purpose as `script_setup_py_environment.sh`'s `setcap` step on the Python venv, applied to the compiled binary instead. +or run it as root, or via the systemd unit below (which grants the capability instead +of running as root). Same purpose as `script_setup_py_environment.sh`'s `setcap` step +on the Python venv, applied to the compiled binary instead. **Not needed for the Docker +deployment** — those containers run as root, so binding 25/143/465/993 directly just +works with no extra setup. ## systemd (unified process) @@ -56,6 +146,12 @@ and terminates its own TLS for the web UI — mailgoserver listens on the same h by default, so the existing nginx config works unmodified. The SMTP TLS listener still consumes `ssl_certs/server.crt`/`server.key`, same as before. +## Docker + +See [`docker-deploy/`](docker-deploy/) — a standalone image and one that bundles the +latest rspamd in the same container, both via a single `docker-compose.yml` using +Compose profiles. + ## Admin dashboard login First run seeds one account: username `admin`, password `Password123!`. Logging in @@ -70,10 +166,20 @@ Optional second factors, enabled per-account from **Account** in the sidebar: `rp_origin = https://mail.example.com`). The defaults (`localhost` / `http://localhost:5000`) only work for local testing — WebAuthn requires either HTTPS or the literal host `localhost`, so passkeys need the nginx+TLS setup above - to work behind a real domain. + (or the Docker deployment) to work behind a real domain. + +## Abuse protection + +Failed SMTP/IMAP auth attempts (bad passwords, denied relay attempts) are counted per +source IP; once a configurable threshold is hit within a window, that IP is temporarily +blocked at the listener level — before the SMTP/IMAP banner is even sent — with the +block duration doubling on repeat offenses up to a cap. Manage active blocks and exempt +trusted IPs from this check entirely from **Blacklist** in the admin sidebar (a global- +admin-only page) — this is separate from the IP whitelist used to authorize +unauthenticated relay for a domain. Tunable in `settings.ini`'s `[Security]` section. --- ## License -See [LICENSE](LICENSE). \ No newline at end of file +See [LICENSE](LICENSE). diff --git a/docker-deploy/.env.example b/docker-deploy/.env.example new file mode 100644 index 0000000..cd70645 --- /dev/null +++ b/docker-deploy/.env.example @@ -0,0 +1,9 @@ +# Copy to .env (in this docker-deploy/ folder) and adjust if the defaults below clash +# with something else on the host, or if you want to run both the "standalone" and +# "with-rspamd" profiles side by side (give each its own set of host ports). +SMTP_PORT=25 +SMTP_TLS_PORT=465 +IMAP_PORT=143 +IMAP_TLS_PORT=993 +WEB_HTTP_PORT=5000 +WEB_HTTPS_PORT=5001 diff --git a/docker-deploy/Dockerfile b/docker-deploy/Dockerfile new file mode 100644 index 0000000..a39c47a --- /dev/null +++ b/docker-deploy/Dockerfile @@ -0,0 +1,50 @@ +# syntax=docker/dockerfile:1 +# +# Standalone mailgoserver image — SMTP + IMAP + admin dashboard + webmail, no rspamd. +# The built-in heuristic spam score (internal/mailstore/spam.go) always runs regardless; +# this is for anyone who doesn't want rspamd's extra dependency. See Dockerfile.rspamd +# for the bundled variant. +# +# Build from the repo root: +# docker build -f docker-deploy/Dockerfile -t mailgoserver . + +FROM golang:1.26-bookworm AS build +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +# modernc.org/sqlite and every other dependency here are pure Go (no cgo), so a fully +# static binary is just CGO_ENABLED=0 — no libc/gcc needed in the runtime image. +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/mailgoserver . + +FROM debian:bookworm-slim +# ca-certificates: outbound direct-to-MX delivery verifies remote STARTTLS certs. +# tzdata: [Server] time_zone (e.g. "Europe/London") needs the real IANA zone +# database — time.LoadLocation silently falls back to UTC without it. +# curl: used by the HEALTHCHECK below. +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates tzdata curl \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build /out/mailgoserver /usr/local/bin/mailgoserver + +# Everything persistent — the auto-generated settings.ini, the SQLite DB, encrypted +# mailbox storage, DKIM/mailstore master keys, TLS certs, the app secret — lives under +# whatever directory the process is started from (see config.Load / main.go's +# os.Getwd()). One volume here covers all of it; no need to enumerate subpaths. +WORKDIR /app/data +VOLUME ["/app/data"] + +# Defaults from internal/config/config.go's generated settings.ini: SMTP 25, direct-TLS +# SMTP 465, IMAP 143, direct-TLS IMAP 993 (the actual standard ports — binding them +# needs no setcap/capability here since this container runs as root), admin/webmail +# HTTP 5000, HTTPS 5001 (deliberately non-privileged; put a reverse proxy or the host's +# own 80/443 in front if you want those too). +EXPOSE 25 465 143 993 5000 5001 + +# --host 0.0.0.0 is required: the binary's own default is 127.0.0.1, which would only +# be reachable from inside this container, never through a published port. +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -fs http://127.0.0.1:5000/health || exit 1 + +ENTRYPOINT ["mailgoserver", "--host", "0.0.0.0"] diff --git a/docker-deploy/Dockerfile.rspamd b/docker-deploy/Dockerfile.rspamd new file mode 100644 index 0000000..272217c --- /dev/null +++ b/docker-deploy/Dockerfile.rspamd @@ -0,0 +1,47 @@ +# syntax=docker/dockerfile:1 +# +# mailgoserver + the latest rspamd, bundled in one container. Both processes share this +# container's network namespace, so rspamd's default "normal" worker +# (127.0.0.1:11333, the /checkv2 scanning API — see internal/mailstore/rspamd.go) is +# already exactly what [Rspamd] url defaults to in settings.ini. Just set +# [Rspamd] enabled = true after first boot (see docker-deploy/README.md) and restart. +# +# Build from the repo root: +# docker build -f docker-deploy/Dockerfile.rspamd -t mailgoserver-rspamd . + +FROM golang:1.26-bookworm AS build +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/mailgoserver . + +FROM debian:bookworm-slim +# rspamd is installed from its own APT repo (rspamd.com), not Debian's bundled +# package, which tends to lag several releases behind — this is genuinely the latest +# stable release, matching what was asked for. +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates tzdata curl gnupg lsb-release \ + && mkdir -p /usr/share/keyrings \ + && curl -fsSL https://rspamd.com/apt-stable/gpg.key | gpg --dearmor -o /usr/share/keyrings/rspamd.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/rspamd.gpg] https://rspamd.com/apt-stable/ $(lsb_release -cs) main" \ + > /etc/apt/sources.list.d/rspamd.list \ + && apt-get update && apt-get install -y --no-install-recommends rspamd \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build /out/mailgoserver /usr/local/bin/mailgoserver +COPY docker-deploy/entrypoint-rspamd.sh /usr/local/bin/entrypoint-rspamd.sh +RUN chmod +x /usr/local/bin/entrypoint-rspamd.sh + +# /app/data: same as the standalone image (settings.ini, DB, mailstore, keys, certs). +# /var/lib/rspamd: rspamd's own Bayes/fuzzy-hash storage, kept persistent separately so +# rebuilding the image doesn't reset spam-learning state. +WORKDIR /app/data +VOLUME ["/app/data", "/var/lib/rspamd"] + +EXPOSE 25 465 143 993 5000 5001 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD curl -fs http://127.0.0.1:5000/health || exit 1 + +ENTRYPOINT ["/usr/local/bin/entrypoint-rspamd.sh"] diff --git a/docker-deploy/README.md b/docker-deploy/README.md new file mode 100644 index 0000000..9f7f2cb --- /dev/null +++ b/docker-deploy/README.md @@ -0,0 +1,98 @@ +# Docker deployment + +Two independent images, both built from the same source tree: + +- **`Dockerfile`** — mailgoserver only. +- **`Dockerfile.rspamd`** — mailgoserver + the latest [rspamd](https://rspamd.com) in the + *same* container, already wired together (see below). Use this one if you want + stronger spam filtering than the built-in heuristic score alone. + +`docker-compose.yml` defines both as Compose **profiles** so a plain `docker compose up` +can't accidentally start both at once: + +```bash +cd docker-deploy + +# mailserver only +docker compose --profile standalone up -d --build + +# mailserver + rspamd, bundled +docker compose --profile with-rspamd up -d --build +``` + +Either way, the app itself now binds the real standard mail ports by default — 25 +(SMTP), 465 (direct-TLS SMTP), 143 (IMAP), 993 (direct-TLS IMAP) — and the admin/webmail +UI on its usual non-privileged 5000/5001 (HTTP/HTTPS); put your own reverse proxy or a +`80:5000`/`443:5001` port mapping in front if you want those on 80/443 too. Binding the +low mail ports needs no extra capability here since the container runs as root. Copy +`.env.example` to `.env` in this folder to change any host-side port — useful if +something else on the host already owns 25/143/etc., or if you want to run both +profiles side by side. + +## What happens on first boot + +There's no baked-in config. On first start, the binary generates a fresh +`settings.ini` with defaults (mirroring `internal/config/config.go`), a self-signed TLS +certificate, DKIM/mailstore master keys, and an empty SQLite database — all inside the +`/app/data` volume, so it survives container restarts/rebuilds. The web UI seeds one +admin account: username `admin`, password `Password123!`, and forces an immediate +username + password change on first login — see the main [README](../README.md) for +the full first-login walkthrough. + +**Before using this for real mail**, exec into the container (or edit the volume from +the host) and update `settings.ini`: + +```bash +docker exec -it mailgoserver sh -c 'vi /app/data/settings.ini' +docker restart mailgoserver +``` + +At minimum, set `[Server] HOSTNAME` / `helo_hostname` to your real mail domain, and if +you'll use passkeys, `[Auth] rp_id` / `rp_origin` to match the exact domain the admin +dashboard is reached at (`rp_id` can't be `localhost` once you're on a real domain — +see the main README's WebAuthn note). There's no environment-variable override +mechanism — `settings.ini` in the volume is the one source of config truth. + +## Enabling rspamd (the `with-rspamd` profile) + +The bundled rspamd's default config already listens on `127.0.0.1:11333` for scanning +requests — exactly what `[Rspamd] url` defaults to in `settings.ini`, and since both +processes share the container's network namespace, no networking setup is needed. All +that's left is turning it on: + +```ini +[Rspamd] +enabled = true +url = http://127.0.0.1:11333 +reject_score = 15 +``` + +...then restart the container. The built-in heuristic spam score +(`internal/mailstore/spam.go`) always runs regardless of this setting — rspamd is +additive, not a replacement, and if it's ever unreachable, mail still flows on the +heuristic score alone (rspamd errors are logged, never fatal to delivery). + +This bundle intentionally skips Redis — rspamd runs fine without it for SPF/DKIM/RBL/ +regexp-based scoring, but Bayes learning and greylisting need it. Add a `redis` service +to `docker-compose.yml` and point rspamd's `redis.conf` at it if you need those. + +## Persistence + +| Volume | What's in it | +|---|---| +| `mailserver-data` / `mailserver-rspamd-data` | `settings.ini`, the SQLite DB, encrypted mailbox storage, DKIM/mailstore master keys, TLS certs, the CSRF app secret — everything mailgoserver itself owns. | +| `rspamd-data` (rspamd profile only) | rspamd's own Bayes/fuzzy-hash storage, so spam-learning state survives image rebuilds. | + +Back up the `*-data` volume like you would the equivalent bare-metal `server_data/` +directory — losing the mailstore master key makes all stored mail unrecoverable, same +as a non-Docker install. + +## Logs / health + +```bash +docker compose --profile standalone logs -f # or --profile with-rspamd +docker inspect --format '{{.State.Health.Status}}' mailgoserver +``` + +Both images expose `GET /health` (used by the container `HEALTHCHECK`), matching the +JSON the admin dashboard's own health check reads. diff --git a/docker-deploy/docker-compose.yml b/docker-deploy/docker-compose.yml new file mode 100644 index 0000000..234ff3f --- /dev/null +++ b/docker-deploy/docker-compose.yml @@ -0,0 +1,51 @@ +# Two independent setups, selected with --profile so they don't both start by accident: +# +# docker compose --profile standalone up -d --build # mailserver only +# docker compose --profile with-rspamd up -d --build # mailserver + rspamd, same container +# +# Both default to the standard mail ports on the host (25/465/143/993) — the app itself +# now binds those directly, no port remapping needed — plus the app's own non-privileged +# web ports (5000/5001; put a reverse proxy or your own 80/443 mapping in front of those +# if you want the dashboard on standard web ports too). Only run one profile at a time +# unless you've overridden the host ports for one of them (see .env.example) — they'd +# otherwise both try to bind the same host ports. +services: + mailserver: + build: + context: .. + dockerfile: docker-deploy/Dockerfile + container_name: mailgoserver + restart: unless-stopped + profiles: ["standalone"] + ports: + - "${SMTP_PORT:-25}:25" + - "${SMTP_TLS_PORT:-465}:465" + - "${IMAP_PORT:-143}:143" + - "${IMAP_TLS_PORT:-993}:993" + - "${WEB_HTTP_PORT:-5000}:5000" + - "${WEB_HTTPS_PORT:-5001}:5001" + volumes: + - mailserver-data:/app/data + + mailserver-rspamd: + build: + context: .. + dockerfile: docker-deploy/Dockerfile.rspamd + container_name: mailgoserver-rspamd + restart: unless-stopped + profiles: ["with-rspamd"] + ports: + - "${SMTP_PORT:-25}:25" + - "${SMTP_TLS_PORT:-465}:465" + - "${IMAP_PORT:-143}:143" + - "${IMAP_TLS_PORT:-993}:993" + - "${WEB_HTTP_PORT:-5000}:5000" + - "${WEB_HTTPS_PORT:-5001}:5001" + volumes: + - mailserver-rspamd-data:/app/data + - rspamd-data:/var/lib/rspamd + +volumes: + mailserver-data: + mailserver-rspamd-data: + rspamd-data: diff --git a/docker-deploy/entrypoint-rspamd.sh b/docker-deploy/entrypoint-rspamd.sh new file mode 100755 index 0000000..72cceda --- /dev/null +++ b/docker-deploy/entrypoint-rspamd.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Starts rspamd in the background, then hands off to mailgoserver as the container's +# main (foreground) process. +# +# ponytail: no supervisor / restart-on-crash for rspamd here — if it dies mid-run, mail +# keeps flowing without the extra scoring rather than the container crashing (the +# built-in heuristic score in internal/mailstore/spam.go always runs regardless, and +# CheckRspamd swallows connection errors rather than rejecting mail over them — see +# internal/mailstore/rspamd.go). Add s6-overlay or supervisord if unattended rspamd +# uptime matters more than that. +rspamd -f & + +# Give rspamd's normal worker a moment to bind :11333 before the first message can +# possibly arrive — purely cosmetic, mailgoserver degrades gracefully either way. +sleep 1 + +exec mailgoserver --host 0.0.0.0 "$@" diff --git a/internal/config/config.go b/internal/config/config.go index 10709f1..78444dd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,9 +25,11 @@ var defaults = []struct { {"Server", []defaultKV{ {"", "", "Server configuration for SMTP ports and hostname"}, {"", "", "Plain SMTP port for internal/whitelisted IPs"}, - {"SMTP_PORT", "4025", ""}, - {"", "", "TLS SMTP port for authenticated users"}, - {"SMTP_TLS_PORT", "40465", ""}, + {"", "", "(standard port 25 — the process needs CAP_NET_BIND_SERVICE or root to bind"}, + {"", "", "it directly; not an issue in the Docker deployment, which runs as root)"}, + {"SMTP_PORT", "25", ""}, + {"", "", "Direct/implicit-TLS SMTP port for authenticated users (like SMTPS, not STARTTLS)"}, + {"SMTP_TLS_PORT", "465", ""}, {"", "", "Server hostname for HELO/EHLO identification"}, {"HOSTNAME", "mail.example.com", ""}, {"", "", "Override HELO hostname"}, @@ -112,9 +114,9 @@ var defaults = []struct { {"IMAP", []defaultKV{ {"", "", "IMAP server configuration for mailbox retrieval (Thunderbird, etc.)"}, {"", "", "Plain IMAP port (STARTTLS not offered, matching the SMTP plain-port design)"}, - {"IMAP_PORT", "1143", ""}, + {"IMAP_PORT", "143", ""}, {"", "", "Implicit-TLS IMAP port (IMAPS)"}, - {"IMAP_TLS_PORT", "1993", ""}, + {"IMAP_TLS_PORT", "993", ""}, }}, {"Mailstore", []defaultKV{ {"", "", "Local mailbox storage configuration"}, diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 81fc36b..957b087 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -17,8 +17,8 @@ func TestGenerateAndLoadRoundTrip(t *testing.T) { if _, err := os.Stat(path); err != nil { t.Fatalf("settings.ini was not written: %v", err) } - if got := cfg.Section("Server").Key("SMTP_PORT").String(); got != "4025" { - t.Errorf("SMTP_PORT = %q, want 4025", got) + if got := cfg.Section("Server").Key("SMTP_PORT").String(); got != "25" { + t.Errorf("SMTP_PORT = %q, want 25", got) } if got := cfg.Section("Attachments").Key("attachments_path").String(); got == "" { t.Error("Attachments.attachments_path default is missing (the approved bug fix)") @@ -29,8 +29,8 @@ func TestGenerateAndLoadRoundTrip(t *testing.T) { if err != nil { t.Fatalf("Load (existing): %v", err) } - if got := cfg2.Section("Server").Key("SMTP_PORT").String(); got != "4025" { - t.Errorf("second Load: SMTP_PORT = %q, want 4025", got) + if got := cfg2.Section("Server").Key("SMTP_PORT").String(); got != "25" { + t.Errorf("second Load: SMTP_PORT = %q, want 25", got) } } diff --git a/main.go b/main.go index 6b1a089..e5e5be4 100644 --- a/main.go +++ b/main.go @@ -138,8 +138,8 @@ func main() { var smtpRunning atomic.Bool runSMTP := func() { - smtpPort := cfg.Section("Server").Key("SMTP_PORT").MustInt(4025) - smtpTLSPort := cfg.Section("Server").Key("SMTP_TLS_PORT").MustInt(40465) + smtpPort := cfg.Section("Server").Key("SMTP_PORT").MustInt(25) + smtpTLSPort := cfg.Section("Server").Key("SMTP_TLS_PORT").MustInt(465) banner := smtpserver.ResolveBanner(cfg, heloHostname) // Bind IPv4-only ("0.0.0.0"), matching both listeners in server_runner.py @@ -175,8 +175,8 @@ func main() { } runIMAP := func() { - imapPort := cfg.Section("IMAP").Key("IMAP_PORT").MustInt(1143) - imapTLSPort := cfg.Section("IMAP").Key("IMAP_TLS_PORT").MustInt(1993) + imapPort := cfg.Section("IMAP").Key("IMAP_PORT").MustInt(143) + imapTLSPort := cfg.Section("IMAP").Key("IMAP_TLS_PORT").MustInt(993) plainServer := imapserver.NewPlainServer(imapBackend) tlsServer := imapserver.NewTLSServer(imapBackend, tlsConfig)