commit 3a4ecf24f42c5dd0381241ca9595738b52086826 Author: Tobias Gehrke Date: Tue Nov 20 18:13:09 2018 +0100 Erverything runs/tests needed/ansible deploy needed diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3cd4328 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +traefik/acme* diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c2c93e --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# General + +Postfix mta doesnt provide SNI, so there mailserver and lists must run on the same domain + + + + +# Configure + +## Postfixadmin + +1 - Go to the setup page : https://admin.domain.tld/setup.php + +:bulb: Don't forget to add a new A/CNAME record in your DNS zone. + +2 - Define the setup password + +3 - Set the setup hash + +``` +docker exec -ti admin setup + +> Postfixadmin setup hash : ffdeb741c58db70d060ddb170af4623a:54e0ac9a55d69c5e53d214c7ad7f1e3df40a3caa +Setup done. +``` + +4 - Create your admin account + +5 - Go to the login page : https://admin.your-domain.tld/ + +6 - You can now create your domains, mailboxes, alias...etc :smiley: + +![](http://i.imgur.com/4B7UMKi.png) + +![](http://i.imgur.com/Jhoy5On.png)% + +## mailman-web + +Restore the Password for admin defined in docker-compose.yml via the defined mail address. + + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e21d352 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,237 @@ +version: '3' +services: + smtp: + container_name: smtp + build: ./smtp + restart: always + depends_on: + - imap + - db + ports: + - '25:25' + - '587:587' + links: + - imap:imap + - db:db + - spam:spam + volumes: + - ./smtp/main.cf:/etc/postfix/main.cf:ro + - ./smtp/master.cf:/etc/postfix/master.cf:ro + - /data/mailserver/mailman/data:/mailman + - mails:/home/vmail + - certs:/certs + environment: + - DATABASE_USER=mail + - DATABASE_PASSWORD=db_password + - DATABASE_NAME=postfix + - MYORIGIN=creditcards.bayern + - MYHOSTNAME=mail.creditcards.bayern + labels: + - "traefik.enable=false" + + imap: + container_name: imap + build: ./imap + restart: always + depends_on: + - extractor + - db + ports: + - '993:993' + - '4190:4190' + expose: + - '24' + - '8472' + links: + - db + - spam + volumes: + - mails:/home/vmail + - certs:/certs + environment: + - "DATABASE_USER=mail" + - "DATABASE_PASSWORD=db_password" + - "DATABASE_NAME=postfix" + - "MAILDOMAIN=mail.creditcards.bayern" + labels: + - "traefik.enable=false" + + spam: + container_name: spam + build: ./spam + restart: always + expose: + - 11334 + volumes: + - spam:/data + - /etc/localtime:/etc/localtime:ro + environment: + - PASSWORD=nichtsicher + - PORT=11334 + labels: + - "traefik.frontend.rule=Host:spam.creditcards.bayern" + - "traefik.port=11334" + + webmail: + container_name: webmail + image: roundcube/roundcubemail:latest-apache + depends_on: + - smtp + - imap + - db + restart: always + links: + - imap:imap + - smtp:smtp + environment: + ROUNDCUBEMAIL_DEFAULT_HOST: imap + ROUNDCUBEMAIL_SMTP_SERVER: smtp + ROUNDCUBEMAIL_PLUGINS: archive,zipdownload,managesieve,password + ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE: 100M + ROUNDCUBEMAIL_DB_TYPE: mysql + ROUNDCUBEMAIL_DB_HOST: db + ROUNDCUBEMAIL_DB_USER: mail + ROUNDCUBEMAIL_DB_PASSWORD: BGun02otSchuj3z + ROUNDCUBEMAIL_DB_NAME: postfix + labels: + - "traefik.frontend.rule=Host:mail.creditcards.bayern" + - "traefik.port=80" + + admin: + container_name: admin + links: + - db:db + depends_on: + - db + image: hardware/postfixadmin:latest + expose: + - "8888" + restart: always + environment: + DBTYPPE: mysql + DBHOST: db + DBUSER: mail + DBNAME: postfix + DBPASS: BGun02otSchuj3z + SMTPHOST: smtp + DOMAIN: creditcards.bayern + labels: + - "traefik.frontend.rule=Host:admin.creditcards.bayern" + - "traefik.port=8888" + + db: + container_name: mariadb + image: mariadb:10.3 + restart: always + environment: + MYSQL_ROOT_PASSWORD: root_password + MYSQL_DATABASES: "postfix mailman" + MYSQL_USER: mail + MYSQL_PASSWORD: db_password + volumes: + - database:/var/lib/mysql + - ./docker-entrypoint.sh:/docker-entrypoint.sh + labels: + - "traefik.enable=false" + + mailman-core: + image: maxking/mailman-core:latest + container_name: mailman-core + hostname: mailman-core + volumes: + - /data/mailserver/mailman/core:/opt/mailman/ + - ./mailman-extra.cfg:/opt/mailman/core/mailman-extra.cfg + links: + - db + - smtp + - imap + depends_on: + - db + environment: + - DATABASE_URL=mysql://mail:db_password@db/mailman + - DATABASE_TYPE=mysql + - DATABASE_CLASS=mailman.database.mysql.MySQLDatabase + - HYPERKITTY_API_KEY=someapikey + labels: + - "traefik.enable=false" + + mailman-web: + image: maxking/mailman-web:latest + container_name: mailman-web + hostname: mailman-web + expose: + - 8000 + - 8080 + depends_on: + - db + links: + - mailman-core:mailman-core + - db:db + volumes: + - /data/mailserver/mailman/web:/opt/mailman-web-data + environment: + - DATABASE_URL=mysql://mail:db_password@db/mailman + - DATABASE_TYPE=mysql + - HYPERKITTY_API_KEY=someapikey + - SECRET_KEY=thisisaverysecretkey + - DYLD_LIBRARY_PATH=/usr/local/mysql/lib/ + - SERVE_FROM_DOMAIN=lists.creditcards.bayern + - DJANGO_ALLOWED_HOSTS=mailman.creditcards.bayern + - MAILMAN_ADMIN_USER=admin + - MAILMAN_ADMIN_EMAIL=a3x@eris.cc + - UWSGI_STATIC_MAP=/static=/opt/mailman-web-data/static + labels: + #- "traefik.frontend.rule=Host:mailman.creditcards.bayern" + #- "traefik.port=8000" + - "traefik.enable=false" + + + nginx: + container_name: nginx + image: nginx:mainline + restart: always + expose: + - 80 + links: + - mailman-web:mailman-web + volumes: + - ./nginx/:/etc/nginx/conf.d/ + - /data/mailserver/mailman/web:/opt/mailman/ + labels: + - "traefik.frontend.rule=Host:mailman.creditcards.bayern" + - "traefik.port=80" + + + + + traefik: + container_name: traefik + image: traefik # The official Traefik docker image + command: --api --docker # Enables the web UI and tells Traefik to listen to docker + restart: always + ports: + - "80:80" # The HTTP port + - "443:443" + - "8080:8080" # The Web UI (enabled by --api) + volumes: + - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events + - ./traefik/:/etc/traefik + labels: + - "traefik.frontend.rule=Host:traefik.creditcards.bayern" + + extractor: + container_name: extractor + image: danielhuisman/traefik-certificate-extractor + volumes: + - /data/mailserver/traefik:/app/data + - certs:/app/certs_flat + labels: + - "traefik.enable=false" + + + +volumes: + database: + mails: + certs: + spam: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..a9882dc --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,206 @@ +#!/bin/bash +set -eo pipefail +shopt -s nullglob + +# if command starts with an option, prepend mysqld +if [ "${1:0:1}" = '-' ]; then + set -- mysqld "$@" +fi + +# skip setup if they want an option that stops mysqld +wantHelp= +for arg; do + case "$arg" in + -'?'|--help|--print-defaults|-V|--version) + wantHelp=1 + break + ;; + esac +done + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +_check_config() { + toRun=( "$@" --verbose --help --log-bin-index="$(mktemp -u)" ) + if ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; then + cat >&2 <<-EOM + ERROR: mysqld failed while attempting to check config + command was: "${toRun[*]}" + $errors + EOM + exit 1 + fi +} + +# Fetch value from server config +# We use mysqld --verbose --help instead of my_print_defaults because the +# latter only show values present in config files, and not server defaults +_get_config() { + local conf="$1"; shift + "$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null \ + | awk '$1 == "'"$conf"'" && /^[^ \t]/ { sub(/^[^ \t]+[ \t]+/, ""); print; exit }' + # match "datadir /some/path with/spaces in/it here" but not "--xyz=abc\n datadir (xyz)" +} + +# allow the container to be started with `--user` +if [ "$1" = 'mysqld' -a -z "$wantHelp" -a "$(id -u)" = '0' ]; then + _check_config "$@" + DATADIR="$(_get_config 'datadir' "$@")" + mkdir -p "$DATADIR" + find "$DATADIR" \! -user mysql -exec chown mysql '{}' + + exec gosu mysql "$BASH_SOURCE" "$@" +fi + +if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then + # still need to check config, container may have started with --user + _check_config "$@" + # Get config + DATADIR="$(_get_config 'datadir' "$@")" + + if [ ! -d "$DATADIR/mysql" ]; then + file_env 'MYSQL_ROOT_PASSWORD' + if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then + echo >&2 'error: database is uninitialized and password option is not specified ' + echo >&2 ' You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD' + exit 1 + fi + + mkdir -p "$DATADIR" + + echo 'Initializing database' + # "Other options are passed to mysqld." (so we pass all "mysqld" arguments directly here) + mysql_install_db --datadir="$DATADIR" --rpm "${@:2}" + echo 'Database initialized' + + SOCKET="$(_get_config 'socket' "$@")" + "$@" --skip-networking --socket="${SOCKET}" & + pid="$!" + + mysql=( mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" ) + + for i in {30..0}; do + if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then + break + fi + echo 'MySQL init process in progress...' + sleep 1 + done + if [ "$i" = 0 ]; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then + # sed is for https://bugs.mysql.com/bug.php?id=20545 + mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql + fi + + if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then + export MYSQL_ROOT_PASSWORD="$(pwgen -1 32)" + echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD" + fi + + rootCreate= + # default root to listen for connections from anywhere + file_env 'MYSQL_ROOT_HOST' '%' + if [ ! -z "$MYSQL_ROOT_HOST" -a "$MYSQL_ROOT_HOST" != 'localhost' ]; then + # no, we don't care if read finds a terminating character in this heredoc + # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 + read -r -d '' rootCreate <<-EOSQL || true + CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; + GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ; + EOSQL + fi + + "${mysql[@]}" <<-EOSQL + -- What's done in this file shouldn't be replicated + -- or products like mysql-fabric won't work + SET @@SESSION.SQL_LOG_BIN=0; + DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; + SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ; + GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ; + ${rootCreate} + DROP DATABASE IF EXISTS test ; + FLUSH PRIVILEGES ; + EOSQL + + if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then + mysql+=( -p"${MYSQL_ROOT_PASSWORD}" ) + fi + + file_env 'MYSQL_DATABASE' + if [ "$MYSQL_DATABASE" ]; then + echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}" + mysql+=( "$MYSQL_DATABASE" ) + fi + + # create several databases using the MYSQL DATABASES env + #example: export MYSQL_DATABASES = "one two three" + file_env 'MYSQL_DATABASES' + if [ "$MYSQL_DATABASES" ]; then + for databaseName in $MYSQL_DATABASES; do + echo "CREATE DATABASE IF NOT EXISTS \`$databaseName\` ;" | "${mysql[@]}" + done + + fi + + file_env 'MYSQL_USER' + file_env 'MYSQL_PASSWORD' + if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then + echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" | "${mysql[@]}" + + if [ "$MYSQL_DATABASE" ]; then + echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}" + fi + + #create the permissions for the different databases created with the db user + if [ "$MYSQL_DATABASES" ]; then + for databaseName in $MYSQL_DATABASES; do + echo "GRANT ALL ON \`$databaseName\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}" + done + fi + fi + + echo + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sh) echo "$0: running $f"; . "$f" ;; + *.sql) echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;; + *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;; + *) echo "$0: ignoring $f" ;; + esac + echo + done + + if ! kill -s TERM "$pid" || ! wait "$pid"; then + echo >&2 'MySQL init process failed.' + exit 1 + fi + + echo + echo 'MySQL init process done. Ready for start up.' + echo + fi +fi + +exec "$@" diff --git a/imap/10-auth.conf b/imap/10-auth.conf new file mode 100644 index 0000000..ebf553a --- /dev/null +++ b/imap/10-auth.conf @@ -0,0 +1,12 @@ +disable_plaintext_auth = yes + +auth_mechanisms = plain login + +passdb { + driver = sql + args = /etc/dovecot/sql.conf.ext +} +userdb { + driver = static + args = uid=vmail gid=vmail mail_home=/home/vmail/%d/%n mail_location=mbox:~/mail:INBOX=/home/vmail/%u +} diff --git a/imap/10-master.conf b/imap/10-master.conf new file mode 100644 index 0000000..22a5fbb --- /dev/null +++ b/imap/10-master.conf @@ -0,0 +1,59 @@ +service imap-login { + inet_listener imap { + #port = 143 + } + inet_listener imaps { + #port = 993 + #ssl = yes + } +} + +service pop3-login { + inet_listener pop3 { + #port = 110 + } + inet_listener pop3s { + #port = 995 + #ssl = yes + } +} + +service imap { +} + +service pop3 { +} + +service auth { + inet_listener { + address = * :: + port = 8472 + } + + unix_listener auth-userdb { + mode = 0600 + user = vmail + } + user = dovecot +} + +service auth-worker { + user = vmail +} + + +service dict { + unix_listener dict { + } +} + +service lmtp { + inet_listener lmtp { + address = * :: + port = 24 + } +} + +plugin { + sieve = file:~/sieve;active=~/.dovecot.sieve +} \ No newline at end of file diff --git a/imap/20-lmtp.conf b/imap/20-lmtp.conf new file mode 100644 index 0000000..ab6bdcf --- /dev/null +++ b/imap/20-lmtp.conf @@ -0,0 +1,3 @@ +protocol lmtp { + mail_plugins = $mail_plugins sieve +} \ No newline at end of file diff --git a/imap/20-managesieve.conf b/imap/20-managesieve.conf new file mode 100644 index 0000000..61edb3e --- /dev/null +++ b/imap/20-managesieve.conf @@ -0,0 +1,8 @@ +service managesieve-login { +} + +service managesieve { +} + +protocol sieve { +} \ No newline at end of file diff --git a/imap/90-sieve.conf b/imap/90-sieve.conf new file mode 100644 index 0000000..cdbc670 --- /dev/null +++ b/imap/90-sieve.conf @@ -0,0 +1,10 @@ +plugin { + sieve_extensions = +spamtest +spamtestplus + + sieve_spamtest_status_type = score + sieve_spamtest_status_header = \ + X-Spam-Score: (-?[[:digit:]]+\.[[:digit:]]).* + sieve_spamtest_max_value = 5.0 + + sieve_before = /var/lib/dovecot/sieve/global_sieves/move_to_spam_folder.sieve +} \ No newline at end of file diff --git a/imap/Dockerfile b/imap/Dockerfile new file mode 100644 index 0000000..d1e80ff --- /dev/null +++ b/imap/Dockerfile @@ -0,0 +1,26 @@ +FROM alpine:latest +RUN apk add --no-cache dovecot dovecot-pigeonhole-plugin + +RUN adduser -u 5000 -g vmail -s /usr/bin/nologin -h /home/vmail -S vmail + +RUN mkdir /etc/dovecot/sieve-filter +RUN ln -s /usr/bin/vendor_perl/spamc /etc/dovecot/sieve-filter/spamc + +ADD dovecot.conf /etc/dovecot/dovecot.conf +ADD 10-auth.conf /etc/dovecot/conf.d/10-auth.conf +ADD 10-master.conf /etc/dovecot/conf.d/10-master.conf + + +ADD 20-lmtp.conf /etc/dovecot/conf.d/20-lmtp.conf +ADD 90-sieve.conf /etc/dovecot/conf.d/90-sieve.conf + + +ADD move_to_spam_folder.sieve /var/lib/dovecot/sieve/global_sieves/move_to_spam_folder.sieve + +ADD ./start.sh /start.sh + +RUN sievec /var/lib/dovecot/sieve/global_sieves + +EXPOSE 993 + +ENTRYPOINT ["/start.sh"] diff --git a/imap/dovecot.conf b/imap/dovecot.conf new file mode 100644 index 0000000..59fd275 --- /dev/null +++ b/imap/dovecot.conf @@ -0,0 +1,12 @@ +#auth_verbose = yes +#auth_debug = yes +#auth_debug_passwords = yes +#auth_verbose_passwords = yes +#mail_debug = yes + + +log_path = /dev/stdout + +!include_try /usr/share/dovecot/protocols.d/*.protocol +!include conf.d/*.conf +protocols = imap lmtp sieve diff --git a/imap/move_to_spam_folder.sieve b/imap/move_to_spam_folder.sieve new file mode 100644 index 0000000..5af7f40 --- /dev/null +++ b/imap/move_to_spam_folder.sieve @@ -0,0 +1,8 @@ +require "spamtestplus"; +require "fileinto"; +require "relational"; +require "comparator-i;ascii-numeric"; + +if header :contains "X-Spam-Flag" "YES" { + fileinto "Spam"; +} diff --git a/imap/sql.conf.ext b/imap/sql.conf.ext new file mode 100644 index 0000000..b489ab7 --- /dev/null +++ b/imap/sql.conf.ext @@ -0,0 +1,4 @@ +driver = mysql +connect = host=db dbname=postfix user=mail password=BGun02otSchuj3z +default_pass_scheme = SHA512-CRYPT +password_query = SELECT username as user, password FROM mailbox WHERE username='%u'; \ No newline at end of file diff --git a/imap/start.sh b/imap/start.sh new file mode 100755 index 0000000..a6fadc6 --- /dev/null +++ b/imap/start.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +#if [ -n "${DATABASE_NAME}" -a -n "${DATABASE_USER}" -a -n "${DATABASE_PASSWORD}" ] ; then + echo -e "driver = mysql\n \ + connect = host=db dbname=${DATABASE_NAME} user=${DATABASE_USER} password=${DATABASE_PASSWORD}\n \ + default_pass_scheme = SHA512-CRYPT\n \ + password_query = SELECT username as user, password FROM mailbox WHERE username='%u';" > /etc/dovecot/sql.conf.ext +#fi + +#if [ -n "${MAILDOMAIN}" ]; then + echo -e "ssl = yes\n \ + ssl_cert = /etc/dovecot/conf.d/10-ssl.conf +#fi + +dovecot -F diff --git a/mailman-extra.cfg b/mailman-extra.cfg new file mode 100644 index 0000000..a601873 --- /dev/null +++ b/mailman-extra.cfg @@ -0,0 +1,10 @@ +# mailman-extra.cfg + +[mta] +incoming: mailman.mta.postfix.LMTP +outgoing: mailman.mta.deliver.deliver +lmtp_host: imap +lmtp_port: 8472 +smtp_host: smtp +smtp_port: 25 +configuration: /etc/postfix-mailman.cfg diff --git a/nginx/mailman-web.conf b/nginx/mailman-web.conf new file mode 100644 index 0000000..6ed2e3a --- /dev/null +++ b/nginx/mailman-web.conf @@ -0,0 +1,15 @@ + + +server { + listen 80; + server_name mailman.creditcards.bayern; + + location / { + # First attempt to serve request as file, then + + uwsgi_pass mailman-web:8080; + include uwsgi_params; + uwsgi_read_timeout 300; + } +} + diff --git a/smtp/Dockerfile b/smtp/Dockerfile new file mode 100644 index 0000000..45f04de --- /dev/null +++ b/smtp/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:latest + +RUN adduser -u 5000 -g vmail -s /usr/bin/nologin -h /home/vmail -S vmail +RUN apk add --no-cache postfix ca-certificates + +ADD ./main.cf /etc/postfix/main.cf +ADD ./master.cf /etc/postfix/master.cf + +ADD ./start.sh /start.sh + +ENTRYPOINT ["/start.sh"] diff --git a/smtp/main.cf b/smtp/main.cf new file mode 100644 index 0000000..d95d781 --- /dev/null +++ b/smtp/main.cf @@ -0,0 +1,90 @@ +compatibility_level = 2 +smtpd_banner = $myhostname ESMTP $mail_name (Hail Eris!) +biff = no + +append_dot_mydomain = no + +readme_directory = no + +smtpd_helo_required = yes +strict_rfc821_envelopes = yes +disable_vrfy_command = yes +unknown_address_reject_code = 554 +unknown_hostname_reject_code = 554 +unknown_client_reject_code = 554 + +#smtpd_tls_key_file=/certs/privkey.pem +#smtpd_tls_cert_file=/certs/cert.pem +smtpd_tls_CAfile = /etc/ssl/certs/ca-certificates.crt + +smtpd_use_tls=yes +smtpd_tls_auth_only = yes + +smtp_tls_security_level = may + +smtpd_sasl_path = inet:imap:8472 +smtpd_sasl_type = dovecot +smtpd_sasl_auth_enable = yes + +smtputf8_enable = no +smtputf8_autodetect_classes = bounce + +#smtpd_recipient_restrictions = +# permit_sasl_authenticated, +# permit_mynetworks, +# reject_unauth_destination + +# Let's try ze new config stuff! +smtpd_recipient_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + reject_rbl_client ix.dnsbl.manitu.net, + reject_rbl_client bl.spamcop.net, + reject_rbl_client multi.surbl.org, + reject_rbl_client dnsbl-1.uceprotect.net, + reject_rbl_client cbl.abuseat.org, + reject_rbl_client combined.rbl.msrbl.net, + reject_rbl_client b.barracudacentral.org, + reject_invalid_hostname, + reject_non_fqdn_hostname, + reject_non_fqdn_sender, + reject_non_fqdn_recipient, + reject_unknown_sender_domain, + reject_unknown_recipient_domain, + reject_unauth_pipelining, + reject_unauth_destination, + reject_unlisted_recipient + + +smtpd_sender_restrictions = +# reject_sender_login_mismatch #too harsh + permit_sasl_authenticated + +smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination +alias_maps = hash:/etc/aliases +alias_database = hash:/etc/aliases +mydestination = localhost +relayhost = +mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 172.16.0.0/12 +mailbox_size_limit = 0 +recipient_delimiter = + +inet_interfaces = all + +virtual_transport = lmtp:inet:imap:24 + +virtual_mailbox_domains = mysql:/etc/postfix/virtual_mailbox_domains.cf +virtual_mailbox_maps = mysql:/etc/postfix/virtual_mailbox_maps.cf +virtual_alias_maps = mysql:/etc/postfix/virtual_alias_maps.cf + +message_size_limit = 20480000 + +# Milter setup +smtpd_milters = inet:spam:11332 +milter_default_action = accept +milter_protocol = 6 + +unknown_local_recipient_reject_code = 550 +owner_request_special = no +transport_maps = hash:/mailman/var/data/postfix_lmtp +local_recipient_maps = hash:/mailman/var/data/postfix_lmtp +relay_domains = hash:/mailman/var/data/postfix_domains diff --git a/smtp/master.cf b/smtp/master.cf new file mode 100644 index 0000000..dd70ac0 --- /dev/null +++ b/smtp/master.cf @@ -0,0 +1,55 @@ +# +# Postfix master process configuration file. For details on the format +# of the file, see the master(5) manual page (command: "man 5 master" or +# on-line: http://www.postfix.org/master.5.html). +# +# Do not forget to execute "postfix reload" after editing this file. +# +# ========================================================================== +# service type private unpriv chroot wakeup maxproc command + args +# (yes) (yes) (yes) (never) (100) +# ========================================================================== +smtp inet n - - - - smtpd + -o content_filter=spamassassin +submission inet n - - - - smtpd + -o syslog_name=postfix/submission + -o smtpd_sasl_auth_enable=yes +pickup unix n - - 60 1 pickup +cleanup unix n - - - 0 cleanup +qmgr unix n - n 300 1 qmgr +tlsmgr unix - - - 1000? 1 tlsmgr +rewrite unix - - - - - trivial-rewrite +bounce unix - - - - 0 bounce +defer unix - - - - 0 bounce +trace unix - - - - 0 bounce +verify unix - - - - 1 verify +flush unix n - - 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - - - - smtp +relay unix - - - - - smtp +showq unix n - - - - showq +error unix - - - - - error +retry unix - - - - - error +discard unix - - - - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - - - - lmtp +anvil unix - - - - 1 anvil +scache unix - - - - 1 scache + +maildrop unix - n n - - pipe + flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient} + +uucp unix - n n - - pipe + flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) + +# +# Other external delivery methods. +# +ifmail unix - n n - - pipe + flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient) +bsmtp unix - n n - - pipe + flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient +scalemail-backend unix - n n - 2 pipe + flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension} diff --git a/smtp/start.sh b/smtp/start.sh new file mode 100755 index 0000000..f7637bc --- /dev/null +++ b/smtp/start.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +#if [ -n "${MYORIGIN}" -a -n "${MYHOSTNAME}" ]; then + echo -e "myorigin = ${MYORIGIN}\n \ + myhostname = ${MYHOSTNAME} \ + smtpd_tls_key_file = /certs/${MYHOSTNAME}.key \ + smtpd_tls_cert_file=/certs/${MYHOSTNAME}.crt" >> /etc/postfix/main_addendum.cf +#fi + +#if [ -n "${DATABASE_USER}" -a -n "${DATBASE_PASSWORD}" -a -n "${DATABASE_NAME}" ]; then + echo -e "user = ${DATABASE_USER}\n \ + password = ${DATABASE_PASSWORD}\n \ + hosts = db\n \ + dbname = ${DATABASE_NAME}\n \ + table = alias\n \ + select_field = goto\n \ + where_field = address" > /etc/postfix/virtual_alias_maps.cf; + + echo -e "user = ${DATABASE_USER}\n \ + password = ${DATABASE_PASSWORD}\n \ + hosts = db\n \ + dbname = ${DATABASE_NAME}\n \ + table = domain\n \ + select_field = domain\n \ + where_field = domain" > /etc/postfix/virtual_mailbox_domains.cf; + + echo -e "user = ${DATABASE_USER}\n \ + password = ${DATABASE_PASSWORD}\n \ + hosts = db\n \ + dbname = ${DATABASE_NAME}\n \ + table = mailbox\n \ + select_field = maildir\n \ + where_field = username" > /etc/postfix/virtual_mailbox_maps.cf; +#fi + +postfix start-fg diff --git a/spam/Dockerfile b/spam/Dockerfile new file mode 100644 index 0000000..5111185 --- /dev/null +++ b/spam/Dockerfile @@ -0,0 +1,15 @@ +FROM alpine:edge + +# We have to upgrade musl, or rspamd will not work. +RUN echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories \ + && apk add --no-cache rspamd rspamd-controller rsyslog ca-certificates + +RUN mkdir /run/rspamd + +RUN echo 'type = "console";' > /etc/rspamd/override.d/logging.inc \ + && echo 'pidfile = false;' > /etc/rspamd/override.d/options.inc + + +COPY start.sh /start.sh + +CMD ["/start.sh"] diff --git a/spam/start.sh b/spam/start.sh new file mode 100755 index 0000000..c6a859f --- /dev/null +++ b/spam/start.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +SECURE_IP=${SECURE_IP:-"127.0.0.1"} +PASSWORD=${PASSWORD:-"mailu"} +ENABLE_PASSWORD=${ENABLE_PASSWORD:-$PASSWORD} + +cat << EOF > /etc/rspamd/override.d/worker-controller.inc + bind_socket = "0.0.0.0:${PORT}"; + secure_ip = "${SECURE_IP}"; + password = "${PASSWORD}"; + enable_password = "${PASSWORD}"; +EOF + +/usr/sbin/rspamd -f --insecure diff --git a/traefik/traefik.toml b/traefik/traefik.toml new file mode 100644 index 0000000..11a75fb --- /dev/null +++ b/traefik/traefik.toml @@ -0,0 +1,41 @@ +debug = false + +logLevel = "ERROR" +defaultEntryPoints = ["https","http"] + +[entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.http.redirect] + entryPoint = "https" + [entryPoints.https] + address = ":443" + [entryPoints.https.tls] + minVersion = "VersionTLS11" + cipherSuites = [ + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" + ] + +[api] + +[retry] + +[docker] +endpoint = "unix:///var/run/docker.sock" +domain = "creditcards.bayern" +watch = true +exposedByDefault = false + +[acme] +email = "noc@creditcards.bayern" +storage = "/etc/traefik/acme.json" +entryPoint = "https" +onHostRule = true +KeyType = "EC256" +keyType = "EC256" +[acme.httpChallenge] +entryPoint = "http"