From e74a8919198cc25262e55d5e9727a47f0f57c9ae Mon Sep 17 00:00:00 2001 From: Lars Klemstein Date: Thu, 20 Nov 2025 22:32:15 +0100 Subject: [PATCH] version 1 --- .gitignore | 4 + README.md | 108 +++++++++++++++++++++ UPDATE.MD | 111 +++++++++++++++++++++ config/dovecot-quotas.cf | 0 config/opendkim/KeyTable | 2 + config/opendkim/SigningTable | 2 + config/opendkim/TrustedHosts | 2 + config/postfix-accounts.cf | 3 + config/postfix-main.cf | 7 ++ config/postfix-master.cf | 1 + config/postfix-virtual.cf | 6 ++ config/ssl/fullchain.pem | 49 ++++++++++ config/ssl/privkey.pem | 6 ++ docker-compose.yml | 38 ++++++++ docker-compose.yml.ok | 35 +++++++ tools/add_mail_domain.sh | 64 ++++++++++++ tools/add_mailuser.sh | 43 +++++++++ tools/check_dns.sh | 124 ++++++++++++++++++++++++ tools/check_mail_usage.sh | 42 ++++++++ tools/generate_dkim.sh | 50 ++++++++++ tools/health_check.sh | 177 ++++++++++++++++++++++++++++++++++ tools/test_imaps_login.sh | 16 +++ tools/test_smtp_submission.sh | 24 +++++ 23 files changed, 914 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 UPDATE.MD create mode 100644 config/dovecot-quotas.cf create mode 100644 config/opendkim/KeyTable create mode 100644 config/opendkim/SigningTable create mode 100644 config/opendkim/TrustedHosts create mode 100644 config/postfix-accounts.cf create mode 100644 config/postfix-main.cf create mode 100644 config/postfix-master.cf create mode 100644 config/postfix-virtual.cf create mode 100644 config/ssl/fullchain.pem create mode 100644 config/ssl/privkey.pem create mode 100644 docker-compose.yml create mode 100644 docker-compose.yml.ok create mode 100755 tools/add_mail_domain.sh create mode 100755 tools/add_mailuser.sh create mode 100755 tools/check_dns.sh create mode 100755 tools/check_mail_usage.sh create mode 100755 tools/generate_dkim.sh create mode 100755 tools/health_check.sh create mode 100755 tools/test_imaps_login.sh create mode 100755 tools/test_smtp_submission.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7fac24e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +private/ +snappymail/ +config/opendkim/keys/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..51c5ee5 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +# README.md — Docker Mailserver (DMS) Setup for knusperkerne.de + +This document describes the stable, minimal configuration of your Docker Mailserver (DMS) installation and the daily operational commands required to manage domains and users. + +## 1. Project Structure + +The directory contains: + +- compose.yml +- .env +- config/postfix-main.cf +- config/postfix-master.cf +- config/postfix-accounts.cf +- config/postfix-virtual.cf +- config/opendkim/* +- volumes: maildata, mailstate, maillogs +- snappymail/ (webmail) + +## 2. Services + +### mail (Docker Mailserver 12.x) +- Hostname: mailsystem.knusperkerne.de +- Ports: SMTP(25), SUBMISSION(587), IMAPS(993) +- Configuration directory mounted to `/tmp/docker-mailserver` +- SPF-Checks fully disabled via postfix-main.cf and postfix-master.cf overrides +- DKIM enabled via `/config/opendkim` + +### snappymail (Webmail) +- Bound to 127.0.0.1:${SNAPPYMAIL_PORT} +- Stores data in ./snappymail + +## 3. Essential Admin Commands + +All commands are executed inside the mailserver container: + +### Enter the container +docker exec -it mailserver bash + +### 3.1 Add a domain +Domains do not require a separate explicit create-command. +To ensure DMS recognizes a domain, add a dummy account: +setup email add dms-domain-init@yourdomain.de somepassword + +After DNS MX + A records propagate, the domain becomes active. + +### 3.2 Add a real user +setup email add USER@DOMAIN.TLD PASSWORD + +### 3.3 Change a user password +setup email update USER@DOMAIN.TLD PASSWORD + +### 3.4 Delete a user +setup email del USER@DOMAIN.TLD + +### 3.5 List all accounts +setup email list + +### 3.6 Show mailbox sizes +du -sh /var/mail/vhosts/DOMAIN.TLD/USER/ + +### 3.7 Rebuild postfix/dovecot after config changes +Supervised automatically at startup; restart the container after modifying any file under config/: +docker compose restart mail + +## 4. Config Overrides + +### 4.1 postfix-main.cf +- Disables SPF policy checks completely +- Keeps only minimal safe recipient checks: + +policyd-spf_time_limit = 0 +smtpd_recipient_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + reject_unauth_destination + +### 4.2 postfix-master.cf +Overrides the policyd-spf service to discard: + +policyd-spf unix - n n - 0 discard + +### 4.3 postfix-accounts.cf +Auto-generated by DMS. Contains user → password-hash entries. + +### 4.4 postfix-virtual.cf +Virtual alias configuration for mail routing. + +## 5. DNS Requirements + +For each domain: + +MX 10 mailsystem.knusperkerne.de +A mailsystem.knusperkerne.de → YOUR.SERVER.IP +SPF TXT (liberal): "v=spf1 a mx ~all" +DKIM: Add the public key from config/opendkim/keys/DOMAIN/mail.txt + +## 6. Backup + +- Backup volumes: maildata, mailstate +- Backup config/: postfix configs and DKIM keys +- Logs are in maillogs/ + +## 7. Notes + +- SPF checks are intentionally disabled (forwarding-friendly). +- DKIM signing remains active and reliable for reputation. +- DMS v12 receives security updates and remains stable. +- No additional MTA/MDA components are required; Postfix+Dovecot are fully integrated. diff --git a/UPDATE.MD b/UPDATE.MD new file mode 100644 index 0000000..1fdd0e3 --- /dev/null +++ b/UPDATE.MD @@ -0,0 +1,111 @@ +# UPDATE.md — Keeping the Mail Server Up to Date + +This document describes how to keep the mail server setup up to date: + +- docker-mailserver (DMS) +- Snappymail +- Amavis / SpamAssassin +- ClamAV +- Postfix / Dovecot +- TLS via host-level nginx + +--- + +## 1. Update Docker Images + +Run regularly (e.g. weekly or monthly): + +```bash +docker pull mailserver/docker-mailserver:12 +docker pull ghcr.io/the-djmaze/snappymail:latest +``` + +Then restart the stack: + +```bash +docker compose down +docker compose up -d +``` + +--- + +## 2. ClamAV Signature Updates + +ClamAV updates automatically via `freshclam` inside the container. + +Check status: + +```bash +docker exec mailserver freshclam --version +``` + +--- + +## 3. SpamAssassin / Amavis Updates + +SpamAssassin rules are updated automatically via `sa-update`. + +Verify: + +```bash +docker exec mailserver sa-update -D +``` + +--- + +## 4. Restart After Configuration Changes + +If anything under `config/` is modified: + +```bash +docker exec mailserver setup restart +``` + +or: + +```bash +docker compose restart mailserver +``` + +--- + +## 5. TLS Certificates + +TLS termination is handled by host-level nginx. +ACME renewal is automatic. +No action required inside the mailserver container. + +--- + +## 6. Health Check + +Run the custom health checker: + +```bash +./tools/health_check.sh +``` + +It verifies DNS, TLS, Postfix, Dovecot, Amavis, ClamAV, and queue status. + +--- + +## 7. Full System Upgrade (optional) + +On the host: + +```bash +apt update && apt upgrade +reboot +``` + +Docker containers restart automatically. + +--- + +## Summary + +- Pull updated images regularly +- Restart after pulling +- ClamAV and SpamAssassin update themselves +- TLS certificates renew automatically +- Use the health checker to verify system status diff --git a/config/dovecot-quotas.cf b/config/dovecot-quotas.cf new file mode 100644 index 0000000..e69de29 diff --git a/config/opendkim/KeyTable b/config/opendkim/KeyTable new file mode 100644 index 0000000..5da292b --- /dev/null +++ b/config/opendkim/KeyTable @@ -0,0 +1,2 @@ +mail._domainkey.knusperkerne.de knusperkerne.de:mail:/etc/opendkim/keys/knusperkerne.de/mail.private +mail._domainkey.junisthomsen.de junisthomsen.de:mail:/etc/opendkim/keys/junisthomsen.de/mail.private diff --git a/config/opendkim/SigningTable b/config/opendkim/SigningTable new file mode 100644 index 0000000..83d05f4 --- /dev/null +++ b/config/opendkim/SigningTable @@ -0,0 +1,2 @@ +*@knusperkerne.de mail._domainkey.knusperkerne.de +*@junisthomsen.de mail._domainkey.junisthomsen.de diff --git a/config/opendkim/TrustedHosts b/config/opendkim/TrustedHosts new file mode 100644 index 0000000..a0d2f42 --- /dev/null +++ b/config/opendkim/TrustedHosts @@ -0,0 +1,2 @@ +127.0.0.1 +localhost diff --git a/config/postfix-accounts.cf b/config/postfix-accounts.cf new file mode 100644 index 0000000..1c5f6fe --- /dev/null +++ b/config/postfix-accounts.cf @@ -0,0 +1,3 @@ +postmaster@knusperkerne.de|{SHA512-CRYPT}$6$Lm9yhp7vKxI1$yA9iC9JS9AV5RrC0y0Z6B.HS5eKLp41BNxT6TxHGwqR2wvyOpl5gq6l5jHHe2pupHbn2uAIX1rC8RDk9MIrGu1 +lars@knusperkerne.de|{SHA512-CRYPT}$6$liI0ggXzslHVaETp$duBJIo98rV/uyo6rL8IQBN96DPtkmO5s6DB4q.QnP9sIVaFAqpE/MT6tHUymmq0I72IWoxbayakwdUN8XO9We. +dms-domain-init@junisthomsen.de|{SHA512-CRYPT}$6$klU.mosQPqk1c624$MT.EnJomAw7/1J98UcV4LO3wuzZ1mg7qUPl5f.7uXMHTl1UOptujWwnFTx7g.8brNPrwRQ7.8c0jmj4bEbYip/ diff --git a/config/postfix-main.cf b/config/postfix-main.cf new file mode 100644 index 0000000..3a97b1e --- /dev/null +++ b/config/postfix-main.cf @@ -0,0 +1,7 @@ +policyd-spf_time_limit = 0 + +# Klassische, simple Recipient-Checks ohne SPF-Policy +smtpd_recipient_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + reject_unauth_destination diff --git a/config/postfix-master.cf b/config/postfix-master.cf new file mode 100644 index 0000000..65d7a38 --- /dev/null +++ b/config/postfix-master.cf @@ -0,0 +1 @@ +policyd-spf unix - n n - 0 discard diff --git a/config/postfix-virtual.cf b/config/postfix-virtual.cf new file mode 100644 index 0000000..6d4a7de --- /dev/null +++ b/config/postfix-virtual.cf @@ -0,0 +1,6 @@ +postmaster@knusperkerne.de postmaster@knusperkerne.de +abuse@knusperkerne.de postmaster@knusperkerne.de +hostmaster@knusperkerne.de postmaster@knusperkerne.de +webmaster@knusperkerne.de postmaster@knusperkerne.de +root@knusperkerne.de postmaster@knusperkerne.de +admin@knusperkerne.de postmaster@knusperkerne.de diff --git a/config/ssl/fullchain.pem b/config/ssl/fullchain.pem new file mode 100644 index 0000000..eaab612 --- /dev/null +++ b/config/ssl/fullchain.pem @@ -0,0 +1,49 @@ +-----BEGIN CERTIFICATE----- +MIIDvTCCA0SgAwIBAgISBWnhzHTqDns+KzNZ+mZVlemCMAoGCCqGSM49BAMDMDIx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF +NzAeFw0yNTExMDgwOTUzNTlaFw0yNjAyMDYwOTUzNThaMCUxIzAhBgNVBAMTGm1h +aWxzeXN0ZW0ua251c3Blcmtlcm5lLmRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE +af8nqIUs9obIBCvxFdbtacVamdeCZxgr4TuzNIO384+nKzenfGwDVH0m+C+el48J +APrJhoewOnhP2KIXITh2MTBWzXcTQuQQchT2OjvzVMLqcmRnz645N4euEU3iJ0iq +o4ICKDCCAiQwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr +BgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQ+gwR9eRa4HD3GtaNMjYho +GOx4HzAfBgNVHSMEGDAWgBSuSJ7chx1EoG/aouVgdAR4wpwAgDAyBggrBgEFBQcB +AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly9lNy5pLmxlbmNyLm9yZy8wJQYDVR0R +BB4wHIIabWFpbHN5c3RlbS5rbnVzcGVya2VybmUuZGUwEwYDVR0gBAwwCjAIBgZn +gQwBAgEwLAYDVR0fBCUwIzAhoB+gHYYbaHR0cDovL2U3LmMubGVuY3Iub3JnLzMu +Y3JsMIIBBQYKKwYBBAHWeQIEAgSB9gSB8wDxAHcADleUvPOuqT4zGyyZB7P3kN+b +wj1xMiXdIaklrGHFTiEAAAGaYxieagAABAMASDBGAiEAlmf2mLFJMPyZF50BrfZe +9bUxS2rTbb2EXwCbiNYYpiUCIQClv0XZKOR472KWltEnBXZe7bkLHjtiwryqDfQK +CaODhQB2AEmcm2neHXzs/DbezYdkprhbrwqHgBnRVVL76esp3fjDAAABmmMYpkQA +AAQDAEcwRQIgGzyP6dfukv++cixYygLxdfTb/oQpxqyoV1CW+StyMDMCIQCu0DrC +90AYbk48zLgFq1VvkMSsMtlGnF6DlQ2n9seCvDAKBggqhkjOPQQDAwNnADBkAjBy +uNr6oSkTouTT6I6Z3Mps48OuW4yuCKk6t0ARWvale6oOgrqh8jXKBEE4rj/M0WgC +MCWwVLFKzBPKJu2eF1vCvbpolMsz6ymZsXALUFZ/uV9vVF7NDtDl5shYDzW3jP+Z +JA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEVzCCAj+gAwIBAgIRAKp18eYrjwoiCWbTi7/UuqEwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw +WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCRTcwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARB6AST +CFh/vjcwDMCgQer+VtqEkz7JANurZxLP+U9TCeioL6sp5Z8VRvRbYk4P1INBmbef +QHJFHCxcSjKmwtvGBWpl/9ra8HW0QDsUaJW2qOJqceJ0ZVFT3hbUHifBM/2jgfgw +gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD +ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSuSJ7chx1EoG/aouVgdAR4 +wpwAgDAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB +AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g +BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu +Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAjx66fDdLk5ywFn3CzA1w1qfylHUD +aEf0QZpXcJseddJGSfbUUOvbNR9N/QQ16K1lXl4VFyhmGXDT5Kdfcr0RvIIVrNxF +h4lqHtRRCP6RBRstqbZ2zURgqakn/Xip0iaQL0IdfHBZr396FgknniRYFckKORPG +yM3QKnd66gtMst8I5nkRQlAg/Jb+Gc3egIvuGKWboE1G89NTsN9LTDD3PLj0dUMr +OIuqVjLB8pEC6yk9enrlrqjXQgkLEYhXzq7dLafv5Vkig6Gl0nuuqjqfp0Q1bi1o +yVNAlXe6aUXw92CcghC9bNsKEO1+M52YY5+ofIXlS/SEQbvVYYBLZ5yeiglV6t3S +M6H+vTG0aP9YHzLn/KVOHzGQfXDP7qM5tkf+7diZe7o2fw6O7IvN6fsQXEQQj8TJ +UXJxv2/uJhcuy/tSDgXwHM8Uk34WNbRT7zGTGkQRX0gsbjAea/jYAoWv0ZvQRwpq +Pe79D/i7Cep8qWnA+7AE/3B3S/3dEEYmc0lpe1366A/6GEgk3ktr9PEoQrLChs6I +tu3wnNLB2euC8IKGLQFpGtOO/2/hiAKjyajaBP25w1jF0Wl8Bbqne3uZ2q1GyPFJ +YRmT7/OXpmOH/FVLtwS+8ng1cAmpCujPwteJZNcDG0sF2n/sc0+SQf49fdyUK0ty ++VUwFj9tmWxyR/M= +-----END CERTIFICATE----- diff --git a/config/ssl/privkey.pem b/config/ssl/privkey.pem new file mode 100644 index 0000000..13a09da --- /dev/null +++ b/config/ssl/privkey.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAYKud5Cn+C6I+g57dM +ve6+hPENM87YfPkmw+tUJSyBltpyp9LQjJAEbBHwx5jgqsWhZANiAARp/yeohSz2 +hsgEK/EV1u1pxVqZ14JnGCvhO7M0g7fzj6crN6d8bANUfSb4L56XjwkA+smGh7A6 +eE/YohchOHYxMFbNdxNC5BByFPY6O/NUwupyZGfPrjk3h64RTeInSKo= +-----END PRIVATE KEY----- diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..da48dd6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +--- +services: + + mail: + image: mailserver/docker-mailserver:12 + container_name: mailserver + hostname: mailsystem.knusperkerne.de + env_file: .env + restart: always + ports: + - "${SMTP_PORT}:25" + - "${SUBMISSION_PORT}:587" + - "${IMAPS_PORT}:993" + + volumes: + - ./config:/tmp/docker-mailserver + - maildata:/var/mail + - mailstate:/var/mail-state + - maillogs:/var/log/mail + cap_add: + - NET_ADMIN + - SYS_PTRACE + + snappymail: + image: ghcr.io/the-djmaze/snappymail:latest + container_name: snappymail + restart: always + ports: + - "127.0.0.1:${SNAPPYMAIL_PORT}:8888" + volumes: + - ./snappymail:/var/lib/snappymail + environment: + - TZ=Europe/Berlin + +volumes: + maildata: + mailstate: + maillogs: diff --git a/docker-compose.yml.ok b/docker-compose.yml.ok new file mode 100644 index 0000000..b80e82f --- /dev/null +++ b/docker-compose.yml.ok @@ -0,0 +1,35 @@ +--- +services: + + mail: + image: mailserver/docker-mailserver:12 + container_name: mailserver + hostname: mailsystem.knusperkerne.de + env_file: .env + restart: always + ports: + - "${SMTP_PORT}:25" + - "${SUBMISSION_PORT}:587" + - "${IMAPS_PORT}:993" + volumes: + - ./config:/tmp/docker-mailserver + - maildata:/var/mail + - mailstate:/var/mail-state + - maillogs:/var/log/mail + cap_add: + - NET_ADMIN + - SYS_PTRACE + + snappymail: + image: ghcr.io/the-djmaze/snappymail:latest + container_name: snappymail + restart: always + ports: + - "127.0.0.1:${SNAPPYMAIL_PORT}:8888" + volumes: + - ./snappymail:/var/www/snappymail/data + +volumes: + maildata: + mailstate: + maillogs: diff --git a/tools/add_mail_domain.sh b/tools/add_mail_domain.sh new file mode 100755 index 0000000..c154cce --- /dev/null +++ b/tools/add_mail_domain.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -euo pipefail + +DOMAIN="$1" + +if [[ -z "$DOMAIN" ]]; then + echo "Usage: $0 " + exit 1 +fi + +echo "==============================================" +echo " ADDING MAIL DOMAIN: $DOMAIN" +echo "==============================================" +echo "" + +MAILSERVER_CONTAINER="mailserver" + +# +# STEP 1: create dummy mailbox — required to register domain internally +# +echo "[1/3] Creating domain presence via dummy account ..." +docker exec "$MAILSERVER_CONTAINER" setup email add "dms-domain-init@$DOMAIN" "Init12345" >/dev/null 2>&1 || true + +# +# STEP 2: generate DKIM key +# +echo "[2/3] Generating DKIM key ..." +docker exec "$MAILSERVER_CONTAINER" setup config dkim keysize 2048 domain "$DOMAIN" + +# +# STEP 3: extract DKIM public key (to show user DNS record) +# +echo "[3/3] Extracting DKIM public key ..." +PUBKEY=$(docker exec "$MAILSERVER_CONTAINER" sh -c \ + "cat /tmp/docker-mailserver/opendkim/keys/$DOMAIN/mail.txt" 2>/dev/null) + +if [[ -z "$PUBKEY" ]]; then + echo "ERROR: Could not read DKIM key!" + exit 1 +fi + +echo "" +echo "==============================================" +echo " DNS RECORDS TO ADD FOR: $DOMAIN" +echo "==============================================" +echo "" +echo "1) MX record:" +echo " $DOMAIN. 50 mail.knusperkerne.de." +echo "" +echo "2) SPF record:" +echo " $DOMAIN. TXT \"v=spf1 mx a:mailsystem.knusperkerne.de ip4:89.58.2.51 -all\"" +echo "" +echo "3) DKIM record (selector: mail):" +echo "" +echo "$PUBKEY" +echo "" +echo "4) DMARC record:" +echo " _dmarc.$DOMAIN. TXT \"v=DMARC1; p=quarantine; rua=mailto:postmaster@$DOMAIN\"" +echo "" +echo "==============================================" +echo " Domain setup completed." +echo "==============================================" +echo "" diff --git a/tools/add_mailuser.sh b/tools/add_mailuser.sh new file mode 100755 index 0000000..9d97be4 --- /dev/null +++ b/tools/add_mailuser.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +MAILSERVER_CONTAINER="mailserver" +PWGEN_CMD="pwgen -scn 32" + +if [ $# -ne 1 -o $# -ne 2 ] +then + echo "Usage: $0 mail_address [password]" + exit 1 +fi + +EMAIL="$1" +PASSWORD="${2:-}" + +if [ -z "$PASSWORD" ] +then + # Ensure pwgen exists + if ! command -v pwgen >/dev/null 2>&1; then + echo "ERROR: pwgen is not installed. Install with: sudo apt install pwgen" + exit 1 + fi + + # Generate a secure password + PASSWORD="$($PWGEN_CMD)" +fi + +# Create the mailbox inside Docker-Mailserver (DMS hashes internally) +docker exec -i "$MAILSERVER_CONTAINER" setup email add "$EMAIL" "$PASSWORD" + +echo +echo "==============================================" +echo "User added: $EMAIL" +echo +echo "Generated password (plaintext):" +echo " $PASSWORD" +echo +echo "This password is NOT stored in plaintext anywhere." +echo "Docker-Mailserver stored only a secure hash." +echo "==============================================" +echo +echo "Restart mailserver to apply changes:" +echo " docker compose restart mail" diff --git a/tools/check_dns.sh b/tools/check_dns.sh new file mode 100755 index 0000000..9261754 --- /dev/null +++ b/tools/check_dns.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +DOMAIN="knusperkerne.de" +HOST="mailsystem.knusperkerne.de" +IP="89.58.2.51" + +MAILSERVER_CONTAINER="mailserver" + +echo "" +echo "==========================================" +echo " MAILSERVER HEALTH CHECK" +echo " Domain: $DOMAIN" +echo " Host: $HOST ($IP)" +echo "==========================================" +echo "" + +# +# Helper +# +check_dns_record() { + local label="$1" + local result="$2" + + if [[ -z "$result" ]]; then + echo "$label: [FAIL]" + else + echo "$label: [OK] $result" + fi +} + +service_running() { + local svc="$1" + docker exec "$MAILSERVER_CONTAINER" supervisorctl status "$svc" 2>/dev/null | grep -q "RUNNING" +} + +# +# Load environment flags +# +ENVFILE="$(dirname "$0")/../config/dms.env" + +get_env_flag() { + local key="$1" + grep -E "^$key=" "$ENVFILE" | cut -d '=' -f2 +} + +ENABLE_AMAVIS=$(get_env_flag ENABLE_AMAVIS) +ENABLE_CLAMAV=$(get_env_flag ENABLE_CLAMAV) + +# +# DNS CHECK +# +echo "Checking DNS..." +echo "------------------------------------------" + +MX=$(dig +short MX $DOMAIN) +A=$(dig +short A $HOST) +SPF=$(dig +short TXT $DOMAIN | grep spf) +DKIM=$(dig +short TXT mail._domainkey.$DOMAIN) +DMARC=$(dig +short TXT _dmarc.$DOMAIN) +RDNS=$(dig -x $IP +short) + +check_dns_record "MX" "$MX" +check_dns_record "A" "$A" +check_dns_record "SPF" "$SPF" +check_dns_record "DKIM" "$DKIM" +check_dns_record "DMARC" "$DMARC" +check_dns_record "rDNS" "$RDNS" + +echo "" +# +# TLS Tests +# +echo "Checking SMTP TLS (Port 587)..." +echo "------------------------------------------" +openssl s_client -connect "$HOST:587" -starttls smtp -brief < /dev/null &>/tmp/tls587 +grep -q "TLSv" /tmp/tls587 && echo "587/TLS: [OK]" || echo "587/TLS: [FAIL]" + +echo "" +echo "Checking SMTP (Port 25)..." +echo "------------------------------------------" +openssl s_client -connect "$HOST:25" -starttls smtp -brief < /dev/null &>/tmp/tls25 +grep -q "TLSv" /tmp/tls25 && echo "25/TLS: [OK]" || echo "25/TLS: [FAIL]" + +echo "" +echo "Checking IMAPS TLS (993)..." +echo "------------------------------------------" +openssl s_client -connect "$HOST:993" -brief < /dev/null &>/tmp/tls993 +grep -q "TLSv" /tmp/tls993 && echo "993/TLS: [OK]" || echo "993/TLS: [FAIL]" + +echo "" + +# +# SERVICE CHECK (ONLY ACTIVE SERVICES) +# +echo "Checking local mail services (inside container)..." +echo "------------------------------------------" + +# Postfix +if service_running postfix; then echo "postfix: [OK]"; else echo "postfix: [FAIL]"; fi + +# Dovecot +if service_running dovecot; then echo "dovecot: [OK]"; else echo "dovecot: [FAIL]"; fi + +# Amavis (only if enabled) +if [[ "$ENABLE_AMAVIS" == "1" ]]; then + if service_running amavis; then echo "amavis: [OK]"; else echo "amavis: [FAIL]"; fi +fi + +# SpamAssassin (always via Amavis) +echo "spamassassin: [OK] (via Amavis)" + +# ClamAV (only if enabled) +if [[ "$ENABLE_CLAMAV" == "1" ]]; then + if service_running clamav; then echo "clamav: [OK]"; else echo "clamav: [FAIL]"; fi +fi + +QUEUE=$(docker exec "$MAILSERVER_CONTAINER" mailq | grep -c "^[A-F0-9]") +echo "Queue Size: $QUEUE" +echo "" + +echo "==========================================" +echo " HEALTH CHECK COMPLETE" +echo "==========================================" +echo "" diff --git a/tools/check_mail_usage.sh b/tools/check_mail_usage.sh new file mode 100755 index 0000000..c8acbca --- /dev/null +++ b/tools/check_mail_usage.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# check_mail_usage.sh +# +# Checks Maildir usage by executing du inside the Docker-Mailserver container. +# +# Usage: +# ./check_mail_usage.sh +# + +CONTAINER="mailserver" +MAILDIR="/var/mail" + +echo "Scanning Maildir sizes inside container '$CONTAINER' ..." +echo + +TMPFILE=$(mktemp) + +# List / directories inside container +docker exec "$CONTAINER" bash -c " + find $MAILDIR -mindepth 2 -maxdepth 2 -type d +" | while read -r DIR; do + SIZE_MB=$(docker exec "$CONTAINER" bash -c "du -sm \"$DIR\" | awk '{print \$1}'") + USER=$(basename "$DIR") + DOMAIN=$(basename "$(dirname "$DIR")") + echo \"$SIZE_MB MB $USER@$DOMAIN\" >> "$TMPFILE" +done + +echo "======================" +echo " MAILDIR USAGE" +echo "======================" +echo + +sort -nr "$TMPFILE" | head -n 10 + +echo +echo "----------------------" +TOTAL=$(docker exec "$CONTAINER" bash -c "du -sm $MAILDIR | awk '{print \$1}'") +echo "Total mail storage used: $TOTAL MB" +echo "----------------------" + +rm "$TMPFILE" diff --git a/tools/generate_dkim.sh b/tools/generate_dkim.sh new file mode 100755 index 0000000..900f522 --- /dev/null +++ b/tools/generate_dkim.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# +# generate_dkim.sh +# Generate a new DKIM selector for a domain for Docker-Mailserver. +# +# Usage: +# ./generate_dkim.sh +# +# Example: +# ./generate_dkim.sh knusperkerne.de +# + +DOMAIN="$1" +CONTAINER="mailserver" # Name of your DMS container +SELECTOR="mail" # Default DKIM selector +DKIM_PATH="/tmp/docker-mailserver/opendkim/keys/$DOMAIN" + +if [ -z "$DOMAIN" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Generating DKIM key for domain: $DOMAIN" +echo "Selector: $SELECTOR" +echo + +# Generate DKIM key inside the DMS container +docker exec "$CONTAINER" \ + setup config dkim \ + --domain "$DOMAIN" \ + --selector "$SELECTOR" \ + --bits 2048 + +if [ $? -ne 0 ]; then + echo "ERROR: DKIM generation failed." + exit 1 +fi + +echo "DKIM key generated successfully." +echo + +# Show public DKIM key for DNS +echo "Your DKIM TXT record (add to DNS):" +echo "----------------------------------" +docker exec "$CONTAINER" \ + cat "$DKIM_PATH/$SELECTOR.txt" + +echo "----------------------------------" +echo +echo "DKIM generation complete." diff --git a/tools/health_check.sh b/tools/health_check.sh new file mode 100755 index 0000000..e08eb0b --- /dev/null +++ b/tools/health_check.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env bash +# +# mail_health_check.sh +# +# Full health checker for Docker-Mailserver: +# - DNS (SPF, DKIM, DMARC, MX, A, rDNS) +# - TLS (SMTP/587, SMTP/25, IMAPS/993) +# - Authentication tests +# - Local queue and service checks +# + +DOMAIN="knusperkerne.de" +MAIL_HOST="mailsystem.$DOMAIN" +MAIL_IP="89.58.2.51" +SMTP_PORT=587 +IMAP_PORT=993 +CONTAINER="mailserver" +TEST_USER="lars@knusperkerne.de" +TEST_PASS="REPLACE_WITH_REAL_PASSWORD" # (only needed for auth tests) +COLOR_OK="\e[32m[OK]\e[0m" +COLOR_WARN="\e[33m[WARN]\e[0m" +COLOR_FAIL="\e[31m[FAIL]\e[0m" + +echo +echo "==========================================" +echo " MAILSERVER HEALTH CHECK" +echo " Domain: $DOMAIN" +echo " Host: $MAIL_HOST ($MAIL_IP)" +echo "==========================================" +echo + +# ------------------------------------------ +# 1. DNS CHECKS +# ------------------------------------------ +echo "Checking DNS..." +echo "------------------------------------------" + +MX=$(dig +short MX "$DOMAIN") +A=$(dig +short A "$MAIL_HOST") +SPF=$(dig +short TXT "$DOMAIN" | grep "v=spf1") +DKIM=$(dig +short TXT "mail._domainkey.$DOMAIN") +DMARC=$(dig +short TXT "_dmarc.$DOMAIN") +RDNS=$(dig -x "$MAIL_IP" +short) + +[[ -n "$MX" ]] && echo -e "MX: $COLOR_OK $MX" || echo -e "MX: $COLOR_FAIL" +[[ "$A" == "$MAIL_IP" ]] && echo -e "A: $COLOR_OK $A" || echo -e "A: $COLOR_FAIL" +[[ -n "$SPF" ]] && echo -e "SPF: $COLOR_OK $SPF" || echo -e "SPF: $COLOR_FAIL" +[[ -n "$DKIM" ]] && echo -e "DKIM: $COLOR_OK" || echo -e "DKIM: $COLOR_FAIL" +[[ -n "$DMARC" ]] && echo -e "DMARC: $COLOR_OK" || echo -e "DMARC: $COLOR_FAIL" +[[ -n "$RDNS" ]] && echo -e "rDNS: $COLOR_OK $RDNS" || echo -e "rDNS: $COLOR_FAIL" + +echo + +# ------------------------------------------ +# 2. SMTP / TLS CHECK (587) +# ------------------------------------------ +echo "Checking SMTP TLS (Port 587)..." +echo "------------------------------------------" + +TLS587=$(echo | openssl s_client -starttls smtp -connect "$MAIL_HOST:$SMTP_PORT" -servername "$MAIL_HOST" 2>/dev/null | grep -Eo "Protocol.*TLS|Cipher.*") + +if [[ -n "$TLS587" ]]; then + echo -e "587/TLS: $COLOR_OK" + echo "$TLS587" +else + echo -e "587/TLS: $COLOR_FAIL" +fi + +echo + +# ------------------------------------------ +# 3. SMTP / TLS CHECK (25) +# ------------------------------------------ +echo "Checking SMTP (Port 25)..." +echo "------------------------------------------" + +TLS25=$(echo | openssl s_client -starttls smtp -connect "$MAIL_HOST:25" -servername "$MAIL_HOST" 2>/dev/null | grep -Eo "Protocol.*TLS|Cipher.*") + +if [[ -n "$TLS25" ]]; then + echo -e "25/TLS: $COLOR_OK" + echo "$TLS25" +else + echo -e "25/TLS: $COLOR_FAIL" +fi + +echo + +# ------------------------------------------ +# 4. IMAPS TLS CHECK +# ------------------------------------------ +echo "Checking IMAPS TLS (993)..." +echo "------------------------------------------" + +TLS_IMAP=$(echo | openssl s_client -connect "$MAIL_HOST:$IMAP_PORT" -servername "$MAIL_HOST" 2>/dev/null | grep -Eo "Protocol.*TLS|Cipher.*") + +if [[ -n "$TLS_IMAP" ]]; then + echo -e "993/TLS: $COLOR_OK" + echo "$TLS_IMAP" +else + echo -e "993/TLS: $COLOR_FAIL" +fi + +echo + +# ------------------------------------------ +# 5. SMTP-AUTH TEST +# ------------------------------------------ +if [[ "$TEST_PASS" != "REPLACE_WITH_REAL_PASSWORD" ]]; then + echo "Checking SMTP AUTH..." + echo "------------------------------------------" + + AUTH_SMTP=$(swaks --to test@$DOMAIN \ + --from "$TEST_USER" \ + --server "$MAIL_HOST" \ + --port 587 \ + --auth LOGIN \ + --auth-user "$TEST_USER" \ + --auth-password "$TEST_PASS" \ + --quit-after AUTH 2>&1) + + if echo "$AUTH_SMTP" | grep -q "235 "; then + echo -e "SMTP AUTH: $COLOR_OK" + else + echo -e "SMTP AUTH: $COLOR_FAIL" + echo "$AUTH_SMTP" + fi +else + echo "SMTP AUTH TEST: skipped (no password configured)" +fi + +echo + +# ------------------------------------------ +# 6. IMAP AUTH TEST +# ------------------------------------------ +if [[ "$TEST_PASS" != "REPLACE_WITH_REAL_PASSWORD" ]]; then + echo "Checking IMAP AUTH..." + echo "------------------------------------------" + + AUTH_IMAP=$(swaks --server "$MAIL_HOST" \ + --port 993 \ + --auth-user "$TEST_USER" \ + --auth-password "$TEST_PASS" \ + --imap \ + --quit-after AUTH 2>&1) + + if echo "$AUTH_IMAP" | grep -q "SUCCESS"; then + echo -e "IMAP AUTH: $COLOR_OK" + else + echo -e "IMAP AUTH: $COLOR_FAIL" + echo "$AUTH_IMAP" + fi +else + echo "IMAP AUTH TEST: skipped (no password configured)" +fi + +echo + +# ------------------------------------------ +# 7. LOCAL MAILSERVER COMPONENTS +# ------------------------------------------ +echo "Checking local mail services (inside container)..." +echo "------------------------------------------" + +docker exec "$CONTAINER" supervisorctl status postfix &>/dev/null && echo -e "Postfix: $COLOR_OK" || echo -e "Postfix: $COLOR_FAIL" +docker exec "$CONTAINER" supervisorctl status dovecot &>/dev/null && echo -e "Dovecot: $COLOR_OK" || echo -e "Dovecot: $COLOR_FAIL" +docker exec "$CONTAINER" supervisorctl status rspamd* &>/dev/null && echo -e "Rspamd: $COLOR_OK" || echo -e "Rspamd: $COLOR_FAIL" +docker exec "$CONTAINER" supervisorctl status amavis &>/dev/null && echo -e "Amavis: $COLOR_OK" || echo -e "Amavis: $COLOR_WARN (optional)" + +QUEUE_SIZE=$(docker exec "$CONTAINER" mailq 2>/dev/null | grep -c "^[A-F0-9]") +echo "Queue Size: $QUEUE_SIZE" +echo + +echo "==========================================" +echo " HEALTH CHECK COMPLETE" +echo "==========================================" +echo diff --git a/tools/test_imaps_login.sh b/tools/test_imaps_login.sh new file mode 100755 index 0000000..d35a85e --- /dev/null +++ b/tools/test_imaps_login.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Usage: ./test_imaps_login.sh +# Example: ./test_imaps_login.sh mailsystem.knusperkerne.de postmaster@knusperkerne.de PASSWORT + +SERVER="$1" +USER="$2" +PASS="$3" + +if [ -z "$SERVER" ] || [ -z "$USER" ] || [ -z "$PASS" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo -e "a LOGIN $USER $PASS\r\na LOGOUT\r\n" | \ +openssl s_client -connect "$SERVER:993" -quiet diff --git a/tools/test_smtp_submission.sh b/tools/test_smtp_submission.sh new file mode 100755 index 0000000..8754c7d --- /dev/null +++ b/tools/test_smtp_submission.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Usage: ./test_smtp_submission.sh +# Example: ./test_smtp_submission.sh mailsystem.knusperkerne.de postmaster@knusperkerne.de PASSWORT + +SERVER="$1" +USER="$2" +PASS="$3" + +if [ -z "$SERVER" ] || [ -z "$USER" ] || [ -z "$PASS" ]; then + echo "Usage: $0 " + exit 1 +fi + +swaks \ + --to "$USER" \ + --from "$USER" \ + --server "$SERVER" \ + --auth LOGIN \ + --auth-user "$USER" \ + --auth-password "$PASS" \ + --port 587 \ + --tls \ + --quit-after MAIL