diff --git a/.env.dist b/.env.dist index df2cf266..6a064212 100644 --- a/.env.dist +++ b/.env.dist @@ -273,3 +273,26 @@ SRS_EXCLUDE_DOMAINS= # signing and the remaining will be used for verification. this is how you # rotate and expire keys SRS_SECRET= + +# ----------------------------------------------------------------------------------------------------------------------------- +# ---------------- Multi-domain relay section --------------------------------------------------------------------------------- +# ----------------------------------------------------------------------------------------------------------------------------- + +# Setup relaying for multiple domains based on the domain name of the sender +# optionally uses usernames and passwords in postfix-sasl-password.cf and relay host mappings in postfix-relaymap.cf +# +# empty => don't configure relay host +# default host to relay mail through +RELAY_HOST= + +# empty => 25 +# default port to relay mail +RELAY_PORT=25 + +# empty => no default +# default relay username (if no specific entry exists in postfix-sasl-password.cf) +RELAY_USER= + +# empty => no default +# password for default relay user +RELAY_PASSWORD= diff --git a/CHANGELOG.md b/CHANGELOG.md index ac570b20..aa8e2c01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 6.0.0 + +* Implementation of multi-domain relay hosts (#922, #926) + AWS_SES_HOST and AWS_SES_PORT are deprecated now. + RELAY_HOST and RELAY_PORT are introduced to replace them. +* Password creation fix (#908, #914) +* Fixes 'duplicate log entry for /var/log/mail/mail.log' (#925, #927) +* fixed cleanup (mail_with_relays didn't get cleaned up) (#930) +* fix line breaks in postfix-summary mail error case (#936) +* Set default virus delete time (#932, #935) + This defaults to 7 days +* Ensure that the account contains a @ (#923, #924) +* Introducing global filters. (#934) +* add missing env vars to docker-compose.yml (#937) +* set postmaster address to a sensible default (#938, #939, #940) +* Testfixes & more (#942) + ## 5.8.1 * add headers to postfix summary mail (#919) diff --git a/Dockerfile b/Dockerfile index 0344ece7..4357eb4e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -117,10 +117,8 @@ RUN sed -i -e 's/include_try \/usr\/share\/dovecot\/protocols\.d/include_try \/e chmod 755 /etc/dovecot/ssl && \ cd /usr/share/dovecot && \ ./mkcert.sh && \ - mkdir /usr/lib/dovecot/sieve-pipe && \ - chmod 755 /usr/lib/dovecot/sieve-pipe && \ - mkdir /usr/lib/dovecot/sieve-filter && \ - chmod 755 /usr/lib/dovecot/sieve-filter + mkdir -p /usr/lib/dovecot/sieve-pipe /usr/lib/dovecot/sieve-filter /usr/lib/dovecot/sieve-global && \ + chmod 755 -R /usr/lib/dovecot/sieve-pipe /usr/lib/dovecot/sieve-filter /usr/lib/dovecot/sieve-global # Configures LDAP COPY target/dovecot/dovecot-ldap.conf.ext /etc/dovecot @@ -198,8 +196,8 @@ RUN sed -i -r "/^#?compress/c\compress\ncopytruncate" /etc/logrotate.conf && \ sed -i -r 's|UpdateLogFile /var/log/clamav/|UpdateLogFile /var/log/mail/|g' /etc/clamav/freshclam.conf && \ sed -i -r 's|/var/log/clamav|/var/log/mail|g' /etc/logrotate.d/clamav-daemon && \ sed -i -r 's|/var/log/clamav|/var/log/mail|g' /etc/logrotate.d/clamav-freshclam && \ - sed -i -r '/\/var\/log\/mail\/mail.log/d' /etc/logrotate.d/rsyslog && \ sed -i -r 's|/var/log/mail|/var/log/mail/mail|g' /etc/logrotate.d/rsyslog && \ + sed -i -r '/\/var\/log\/mail\/mail.log/d' /etc/logrotate.d/rsyslog && \ # prevent syslog logrotate warnings \ sed -i -e 's/\(printerror "could not determine current runlevel"\)/#\1/' /usr/sbin/invoke-rc.d && \ sed -i -e 's/^\(POLICYHELPER=\).*/\1/' /usr/sbin/invoke-rc.d && \ diff --git a/Makefile b/Makefile index 566f8aa1..02d2aedd 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ run: -e PERMIT_DOCKER=network \ -e DMS_DEBUG=0 \ -e OVERRIDE_HOSTNAME=mail.my-domain.com \ - -h mail.my-domain.com \ + -h unknown.domain.tld \ -t $(NAME) sleep 15 docker run -d --name mail_fail2ban \ @@ -207,10 +207,23 @@ run: -e SA_SPAM_SUBJECT="undef" \ -h mail.my-domain.com -t $(NAME) sleep 15 - + docker run -d --name mail_with_relays \ + -v "`pwd`/test/config/relay-hosts":/tmp/docker-mailserver \ + -v "`pwd`/test":/tmp/docker-mailserver-test \ + -e RELAY_HOST=default.relay.com \ + -e RELAY_PORT=2525 \ + -e RELAY_USER=smtp_user \ + -e RELAY_PASSWORD=smtp_password \ + --cap-add=SYS_PTRACE \ + -e PERMIT_DOCKER=host \ + -e DMS_DEBUG=0 \ + -h mail.my-domain.com -t $(NAME) + sleep 15 generate-accounts-after-run: docker run --rm -e MAIL_USER=added@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf + docker exec mail addmailuser pass@localhost.localdomain 'may be \a `p^a.*ssword' + sleep 10 fixtures: @@ -267,14 +280,15 @@ clean: mail_with_postgrey \ mail_undef_spam_subject \ mail_postscreen \ - mail_override_hostname + mail_override_hostname \ + mail_with_relays @if [ -d config.bak ]; then\ rm -rf config ;\ mv config.bak config ;\ fi @if [ -d testconfig.bak ]; then\ - rm -rf test/config ;\ + sudo rm -rf test/config ;\ mv testconfig.bak test/config ;\ fi -sudo rm -rf test/onedir diff --git a/README.md b/README.md index 14842be0..d15901ea 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Download the docker-compose.yml, the .env and the setup.sh files: #### Create your mail accounts - ./setup.sh email add [] + ./setup.sh email add [] #### Generate DKIM keys @@ -538,3 +538,25 @@ Note: This postgrey setting needs `ENABLE_POSTGREY=1` - you may specify multiple keys, comma separated. the first one is used for signing and the remaining will be used for verification. this is how you rotate and expire keys - if you have a cluster/swarm make sure the same keys are on all nodes - example command to generate a key: `dd if=/dev/urandom bs=24 count=1 2>/dev/null | base64` + +## Multi-domain Relay Hosts + +#### RELAY_HOST + + - **empty** => don't configure relay host + - default host to relay mail through + +#### RELAY_PORT + + - **empty** => 25 + - default port to relay mail through + +#### RELAY_USER + + - **empty** => no default + - default relay username (if no specific entry exists in postfix-sasl-password.cf) + +#### RELAY_PASSWORD + + - **empty** => no default + - password for default relay user diff --git a/docker-compose.yml.dist b/docker-compose.yml.dist index 8fc776ac..b8156581 100644 --- a/docker-compose.yml.dist +++ b/docker-compose.yml.dist @@ -68,6 +68,12 @@ services: - SASLAUTHD_LDAP_SEARCH_BASE=${SASLAUTHD_LDAP_SEARCH_BASE} - SASLAUTHD_LDAP_FILTER=${SASLAUTHD_LDAP_FILTER} - SASL_PASSWD=${SASL_PASSWD} + - SRS_EXCLUDE_DOMAINS=${SRS_EXCLUDE_DOMAINS} + - SRS_SECRET=${SRS_SECRET} + - RELAY_HOST=${RELAY_HOST} + - RELAY_PORT=${RELAY_PORT} + - RELAY_USER=${RELAY_USER} + - RELAY_PASSWORD=${RELAY_PASSWORD} cap_add: - NET_ADMIN - SYS_PTRACE diff --git a/setup.sh b/setup.sh index 4ee77be6..56da03b2 100755 --- a/setup.sh +++ b/setup.sh @@ -61,6 +61,12 @@ SUBCOMMANDS: $0 config dkim (default: 2048) $0 config ssl + relay: + + $0 relay add-domain [] + $0 relay add-auth [] + $0 relay exclude-domain + debug: $0 debug fetchmail @@ -197,6 +203,27 @@ case $1 in esac ;; + relay) + shift + case $1 in + add-domain) + shift + _docker_image addrelayhost $@ + ;; + add-auth) + shift + _docker_image addsaslpassword $@ + ;; + exclude-domain) + shift + _docker_image excluderelaydomain $@ + ;; + *) + _usage + ;; + esac + ;; + debug) shift case $1 in diff --git a/target/bin/addmailuser b/target/bin/addmailuser index c9e83d9c..04b40b4b 100755 --- a/target/bin/addmailuser +++ b/target/bin/addmailuser @@ -3,7 +3,8 @@ DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-accounts.cf} USER="$1" -PASSWD="$2" +shift +PASSWD="$@" usage() { echo "Usage: addmailuser []" @@ -19,6 +20,7 @@ escape() { } [ -z "$USER" ] && { usage; errex "no username specified"; } +expr index "$USER" "@" >/dev/null || { usage; errex "username must include the domain"; } grep -qi "^$(escape "$USER")|" $DATABASE 2>/dev/null && errex "User \"$USER\" already exists" @@ -28,5 +30,5 @@ if [ -z "$PASSWD" ]; then echo [ -z "$PASSWD" ] && errex "Password must not be empty" fi - -echo "$USER|$(doveadm pw -s SHA512-CRYPT -u "$USER" -p "$PASSWD")" >>$DATABASE +HASH="$(doveadm pw -s SHA512-CRYPT -u "$USER" -p "$PASSWD")" +echo "$USER|$HASH" >> $DATABASE diff --git a/target/bin/addrelayhost b/target/bin/addrelayhost new file mode 100755 index 00000000..ea5e5ce0 --- /dev/null +++ b/target/bin/addrelayhost @@ -0,0 +1,33 @@ +#! /bin/bash + +DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-relaymap.cf} + +DOMAIN="$1" +HOST="$2" +PORT="$3" + +usage() { + echo "Usage: addrelayhost []" +} + +errex() { + echo "$@" 1>&2 + exit 1 +} + +escape() { + echo "${1//./\\.}" +} + +[ -z "$DOMAIN" ] && { usage; errex "no domain specified"; } +[ -z "$HOST" ] && { usage; errex "no relay host specified"; } + +if [ -z "$PORT" ]; then + PORT=25 +fi + +if grep -qi "^@$DOMAIN" $DATABASE 2>/dev/null; then + sed -i "s ^@"$DOMAIN".* "@$DOMAIN"\t\t["$HOST"]:"$PORT" " $DATABASE +else + echo -e "@$DOMAIN\t\t[$HOST]:$PORT" >> $DATABASE +fi diff --git a/target/bin/addsaslpassword b/target/bin/addsaslpassword new file mode 100755 index 00000000..ac372267 --- /dev/null +++ b/target/bin/addsaslpassword @@ -0,0 +1,35 @@ +#! /bin/bash + +DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-sasl-password.cf} + +DOMAIN="$1" +USER="$2" +PASSWD="$3" + +usage() { + echo "Usage: addsaslpassword " +} + +errex() { + echo "$@" 1>&2 + exit 1 +} + +escape() { + echo "${1//./\\.}" +} + +[ -z "$DOMAIN" ] && { usage; errex "no domain specified"; } +[ -z "$USER" ] && { usage; errex "no username specified"; } + +if [ -z "$PASSWD" ]; then + read -s -p "Enter Password: " PASSWD + echo + [ -z "$PASSWD" ] && errex "Password must not be empty" +fi + +if grep -qi "^@$DOMAIN" $DATABASE 2>/dev/null; then + sed -i "s ^@"$DOMAIN".* "@$DOMAIN"\t\t"$USER":"$PASSWD" " $DATABASE +else + echo -e "@$DOMAIN\t\t$USER:$PASSWD" >> $DATABASE +fi diff --git a/target/bin/excluderelaydomain b/target/bin/excluderelaydomain new file mode 100755 index 00000000..07f6dce9 --- /dev/null +++ b/target/bin/excluderelaydomain @@ -0,0 +1,22 @@ +#! /bin/bash + +DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-relaymap.cf} + +DOMAIN="$1" + +usage() { + echo "Usage: excluderelayhost " +} + +errex() { + echo "$@" 1>&2 + exit 1 +} + +[ -z "$DOMAIN" ] && { usage; errex "no domain specified"; } + +if grep -qi "^@$DOMAIN" $DATABASE 2>/dev/null; then + sed -i "s/^@"$DOMAIN".*/@"$DOMAIN"/" $DATABASE +else + echo -e "@$DOMAIN" >> $DATABASE +fi diff --git a/target/bin/postfix-summary b/target/bin/postfix-summary index d30b8819..8bba8e7f 100755 --- a/target/bin/postfix-summary +++ b/target/bin/postfix-summary @@ -11,9 +11,16 @@ errex() { test -x /usr/sbin/pflogsumm || errex "Critical: /usr/sbin/pflogsumm not found" # The case that the mail.log.1 file isn't readable shouldn't actually be possible with logrotate not rotating empty files.. But you never know! -[ -r "/var/log/mail/mail.log.1" ] \ - && BODY=$(/usr/sbin/pflogsumm /var/log/mail/mail.log.1 --problems-first) \ - || BODY="Error: Mail log not readable or not found: /var/log/mail/mail.log.1\n\nIn case of mail inactivity since the last report, this might be considered a nuisance warning.\n\nYours faithfully, The $HOSTNAME Mailserver" +if [ -r "/var/log/mail/mail.log.1" ]; then + BODY=$(/usr/sbin/pflogsumm /var/log/mail/mail.log.1 --problems-first) +else + BODY="Error: Mail log not readable or not found: /var/log/mail/mail.log.1 + +In case of mail inactivity since the last report, this might be considered a nuisance warning. + +Yours faithfully, +The $HOSTNAME Mailserver" +fi sendmail -t < [password]" @@ -20,7 +21,13 @@ escape() { [ -z "$USER" ] && { usage; errex "no username specified"; } +if [ -z "$PASSWD" ]; then + read -s -p "Enter Password: " PASSWD + echo + [ -z "$PASSWD" ] && errex "Password must not be empty" +fi +HASH="$(doveadm pw -s SHA512-CRYPT -u "$USER" -p "$PASSWD")" grep -qi "^$(escape "$USER")|" $DATABASE 2>/dev/null || errex "User \"$USER\" does not exist" -sed -i "s ^"$USER"|.* "$USER"|"$PASSWD" " $DATABASE -#sed -i "s/^"$(escape "$USER")"|*/"$USER"|"$(doveadm pw -s SHA512-CRYPT -u "$USER" -p "$PASSWD")"/" $DATABASE + +sed -i "s ^"$USER"|.* "$USER"|"$HASH" " $DATABASE diff --git a/target/check-for-changes.sh b/target/check-for-changes.sh index 8cf78e02..23533a55 100755 --- a/target/check-for-changes.sh +++ b/target/check-for-changes.sh @@ -51,6 +51,37 @@ if ! [ $resu_acc = "OK" ] || ! [ $resu_vir = "OK" ]; then chmod 640 /etc/dovecot/userdb sed -i -e '/\!include auth-ldap\.conf\.ext/s/^/#/' /etc/dovecot/conf.d/10-auth.conf sed -i -e '/\!include auth-passwdfile\.inc/s/^#//' /etc/dovecot/conf.d/10-auth.conf + + # rebuild relay host + if [ ! -z "$RELAY_HOST" ]; then + # keep old config + echo -n > /etc/postfix/sasl_passwd + echo -n > /etc/postfix/relayhost_map + if [ ! -z "$SASL_PASSWD" ]; then + echo "$SASL_PASSWD" >> /etc/postfix/sasl_passwd + fi + # add domain-specific auth from config file + if [ -f /tmp/docker-mailserver/postfix-sasl-password.cf ]; then + while read line; do + if ! echo "$line" | grep -q -e "\s*#"; then + echo "$line" >> /etc/postfix/sasl_passwd + fi + done < /tmp/docker-mailserver/postfix-sasl-password.cf + fi + # add default relay + if [ ! -z "$RELAY_USER" ] && [ ! -z "$RELAY_PASSWORD" ]; then + echo "[$RELAY_HOST]:$RELAY_PORT $RELAY_USER:$RELAY_PASSWORD" >> /etc/postfix/sasl_passwd + fi + # add relay maps from file + if [ -f /tmp/docker-mailserver/postfix-relaymap.cf ]; then + while read line; do + if ! echo "$line" | grep -q -e "\s*#"; then + echo "$line" >> /etc/postfix/relayhost_map + fi + done < /tmp/docker-mailserver/postfix-relaymap.cf + fi + fi + # Creating users # 'pass' is encrypted # comments and empty lines are ignored @@ -78,8 +109,22 @@ if ! [ $resu_acc = "OK" ] || ! [ $resu_vir = "OK" ]; then # Copy user provided sieve file, if present test -e /tmp/docker-mailserver/${login}.dovecot.sieve && cp /tmp/docker-mailserver/${login}.dovecot.sieve /var/mail/${domain}/${user}/.dovecot.sieve echo ${domain} >> /tmp/vhost.tmp + # add domains to relayhost_map + if [ ! -z "$RELAY_HOST" ]; then + if ! grep -q -e "^@${domain}\s" /etc/postfix/relayhost_map; then + echo "@${domain} [$RELAY_HOST]:$RELAY_PORT" >> /etc/postfix/relayhost_map + fi + fi done fi + if [ -f /etc/postfix/sasl_passwd ]; then + chown root:root /etc/postfix/sasl_passwd + chmod 0600 /etc/postfix/sasl_passwd + fi + if [ -f /etc/postfix/relayhost_map ]; then + chown root:root /etc/postfix/relayhost_map + chmod 0600 /etc/postfix/relayhost_map + fi if [ -f postfix-virtual.cf ]; then # regen postfix aliases echo -n > /etc/postfix/virtual diff --git a/target/dovecot/90-sieve.conf b/target/dovecot/90-sieve.conf index 5e53df72..8fb7649e 100644 --- a/target/dovecot/90-sieve.conf +++ b/target/dovecot/90-sieve.conf @@ -31,14 +31,14 @@ plugin { # executed. The order of execution within a directory is determined by the # file names, using a normal 8bit per-character comparison. Multiple script # file or directory paths can be specified by appending an increasing number. - #sieve_before = + #sieve_before = /usr/lib/dovecot/sieve-global/before.dovecot.sieve #sieve_before2 = #sieve_before3 = (etc...) # Identical to sieve_before, only the specified scripts are executed after the # user's script (only when keep is still in effect!). Multiple script file or # directory paths can be specified by appending an increasing number. - #sieve_after = + #sieve_after = /usr/lib/dovecot/sieve-global/after.dovecot.sieve #sieve_after2 = #sieve_after2 = (etc...) diff --git a/target/start-mailserver.sh b/target/start-mailserver.sh index a1a4a58f..abd22b18 100644 --- a/target/start-mailserver.sh +++ b/target/start-mailserver.sh @@ -23,13 +23,14 @@ DEFAULT_VARS["ENABLE_SASLAUTHD"]="${ENABLE_SASLAUTHD:="0"}" DEFAULT_VARS["SMTP_ONLY"]="${SMTP_ONLY:="0"}" DEFAULT_VARS["DMS_DEBUG"]="${DMS_DEBUG:="0"}" DEFAULT_VARS["OVERRIDE_HOSTNAME"]="${OVERRIDE_HOSTNAME}" -DEFAULT_VARS["POSTMASTER_ADDRESS"]="${POSTMASTER_ADDRESS:="postmaster@domain.com"}" DEFAULT_VARS["POSTSCREEN_ACTION"]="${POSTSCREEN_ACTION:="enforce"}" DEFAULT_VARS["SPOOF_PROTECTION"]="${SPOOF_PROTECTION:="0"}" DEFAULT_VARS["TLS_LEVEL"]="${TLS_LEVEL:="modern"}" DEFAULT_VARS["ENABLE_SRS"]="${ENABLE_SRS:="0"}" DEFAULT_VARS["REPORT_RECIPIENT"]="${REPORT_RECIPIENT:="0"}" DEFAULT_VARS["REPORT_INTERVAL"]="${REPORT_INTERVAL:="daily"}" +DEFAULT_VARS["VIRUSMAILS_DELETE_DELAY"]="${VIRUSMAILS_DELETE_DELAY:="7"}" + ########################################################################## # << DEFAULT VARS ########################################################################## @@ -135,7 +136,11 @@ function register_functions() { _register_setup_function "_setup_postfix_access_control" if [ ! -z "$AWS_SES_HOST" -a ! -z "$AWS_SES_USERPASS" ]; then - _register_setup_function "_setup_postfix_relay_amazon_ses" + _register_setup_function "_setup_postfix_relay_hosts" + fi + + if [ ! -z "$RELAY_HOST" ]; then + _register_setup_function "_setup_postfix_relay_hosts" fi if [ "$ENABLE_POSTFIX_VIRTUAL_TRANSPORT" = 1 ]; then @@ -410,6 +415,9 @@ function setup() { function _setup_default_vars() { notify 'task' "Setting up default variables [$FUNCNAME]" + # update POSTMASTER_ADDRESS - must be done done after _check_hostname() + DEFAULT_VARS["POSTMASTER_ADDRESS"]="${POSTMASTER_ADDRESS:=postmaster@${DOMAINNAME}}" + for var in ${!DEFAULT_VARS[@]}; do echo "export $var=${DEFAULT_VARS[$var]}" >> /root/.bashrc [ $? != 0 ] && notify 'err' "Unable to set $var=${DEFAULT_VARS[$var]}" && kill -15 `cat /var/run/supervisord.pid` && return 1 @@ -477,16 +485,25 @@ function _setup_dovecot() { # Copy pipe and filter programs, if any rm -f /usr/lib/dovecot/sieve-filter/* rm -f /usr/lib/dovecot/sieve-pipe/* - if [ -d /tmp/docker-mailserver/sieve-filter ]; then - cp /tmp/docker-mailserver/sieve-filter/* /usr/lib/dovecot/sieve-filter/ - chown docker:docker /usr/lib/dovecot/sieve-filter/* - chmod 550 /usr/lib/dovecot/sieve-filter/* + [ -d /tmp/docker-mailserver/sieve-filter ] && cp /tmp/docker-mailserver/sieve-filter/* /usr/lib/dovecot/sieve-filter/ + [ -d /tmp/docker-mailserver/sieve-pipe ] && cp /tmp/docker-mailserver/sieve-pipe/* /usr/lib/dovecot/sieve-pipe/ + if [ -f /tmp/docker-mailserver/before.dovecot.sieve ]; then + sed -i "s/#sieve_before =/sieve_before =/" /etc/dovecot/conf.d/90-sieve.conf + cp /tmp/docker-mailserver/before.dovecot.sieve /usr/lib/dovecot/sieve-global/ + sievec /usr/lib/dovecot/sieve-global/before.dovecot.sieve + else + sed -i "s/ sieve_before =/ #sieve_before =/" /etc/dovecot/conf.d/90-sieve.conf fi - if [ -d /tmp/docker-mailserver/sieve-pipe ]; then - cp /tmp/docker-mailserver/sieve-pipe/* /usr/lib/dovecot/sieve-pipe/ - chown docker:docker /usr/lib/dovecot/sieve-pipe/* - chmod 550 /usr/lib/dovecot/sieve-pipe/* + + if [ -f /tmp/docker-mailserver/after.dovecot.sieve ]; then + sed -i "s/#sieve_after =/sieve_after =/" /etc/dovecot/conf.d/90-sieve.conf + cp /tmp/docker-mailserver/after.dovecot.sieve /usr/lib/dovecot/sieve-global/ + sievec /usr/lib/dovecot/sieve-global/after.dovecot.sieve + else + sed -i "s/ sieve_after =/ #sieve_after =/" /etc/dovecot/conf.d/90-sieve.conf fi + chown docker:docker -R /usr/lib/dovecot/sieve* + chmod 550 -R /usr/lib/dovecot/sieve* } function _setup_dovecot_local_user() { @@ -1001,22 +1018,102 @@ function _setup_postfix_sasl_password() { fi } -function _setup_postfix_relay_amazon_ses() { - notify 'task' 'Setting up Postfix Relay Amazon SES' - if [ -z "$AWS_SES_PORT" ];then - AWS_SES_PORT=25 +function _setup_postfix_relay_hosts() { + notify 'task' 'Setting up Postfix Relay Hosts' + # copy old AWS_SES variables to new variables + if [ -z "$RELAY_HOST" ]; then + if [ ! -z "$AWS_SES_HOST" ]; then + notify 'inf' "Using deprecated AWS_SES environment variables" + RELAY_HOST=$AWS_SES_HOST + fi fi - notify 'inf' "Setting up outgoing email via AWS SES host $AWS_SES_HOST:$AWS_SES_PORT" - echo "[$AWS_SES_HOST]:$AWS_SES_PORT $AWS_SES_USERPASS" >> /etc/postfix/sasl_passwd + if [ -z "$RELAY_PORT" ]; then + if [ -z "$AWS_SES_PORT" ]; then + RELAY_PORT=25 + else + RELAY_PORT=$AWS_SES_PORT + fi + fi + if [ -z "$RELAY_USER" ]; then + if [ ! -z "$AWS_SES_USERPASS" ]; then + # NB this will fail if the password contains a colon! + RELAY_USER=$(echo "$AWS_SES_USERPASS" | cut -f 1 -d ":") + RELAY_PASSWORD=$(echo "$AWS_SES_USERPASS" | cut -f 2 -d ":") + fi + fi + notify 'inf' "Setting up outgoing email relaying via $RELAY_HOST:$RELAY_PORT" + + # setup /etc/postfix/sasl_passwd + # -- + # @domain1.com postmaster@domain1.com:your-password-1 + # @domain2.com postmaster@domain2.com:your-password-2 + # @domain3.com postmaster@domain3.com:your-password-3 + # + # [smtp.mailgun.org]:587 postmaster@domain2.com:your-password-2 + + if [ -f /tmp/docker-mailserver/postfix-sasl-password.cf ]; then + notify 'inf' "Adding relay authentication from postfix-sasl-password.cf" + while read line; do + if ! echo "$line" | grep -q -e "\s*#"; then + echo "$line" >> /etc/postfix/sasl_passwd + fi + done < /tmp/docker-mailserver/postfix-sasl-password.cf + fi + + # add default relay + if [ ! -z "$RELAY_USER" ] && [ ! -z "$RELAY_PASSWORD" ]; then + echo "[$RELAY_HOST]:$RELAY_PORT $RELAY_USER:$RELAY_PASSWORD" >> /etc/postfix/sasl_passwd + else + if [ ! -f /tmp/docker-mailserver/postfix-sasl-password.cf ]; then + notify 'warn' "No relay auth file found and no default set" + fi + fi + + chown root:root /etc/postfix/sasl_passwd + chmod 0600 /etc/postfix/sasl_passwd + # end /etc/postfix/sasl_passwd + + # setup /etc/postfix/relayhost_map + # -- + # @domain1.com [smtp.mailgun.org]:587 + # @domain2.com [smtp.mailgun.org]:587 + # @domain3.com [smtp.mailgun.org]:587 + + echo -n > /etc/postfix/relayhost_map + + if [ -f /tmp/docker-mailserver/postfix-relaymap.cf ]; then + notify 'inf' "Adding relay mappings from postfix-relaymap.cf" + while read line; do + if ! echo "$line" | grep -q -e "\s*#"; then + echo "$line" >> /etc/postfix/relayhost_map + fi + done < /tmp/docker-mailserver/postfix-relaymap.cf + fi + grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-accounts.cf | while IFS=$'|' read login pass + do + domain=$(echo ${login} | cut -d @ -f2) + if ! grep -q -e "^@${domain}\b" /etc/postfix/relayhost_map; then + notify 'inf' "Adding relay mapping for ${domain}" + echo "@${domain} [$RELAY_HOST]:$RELAY_PORT" >> /etc/postfix/relayhost_map + fi + done + # remove lines with no destination + sed -i '/^@\S*\s*$/d' /etc/postfix/relayhost_map + + chown root:root /etc/postfix/relayhost_map + chmod 0600 /etc/postfix/relayhost_map + # end /etc/postfix/relayhost_map + postconf -e \ - "relayhost = [$AWS_SES_HOST]:$AWS_SES_PORT" \ "smtp_sasl_auth_enable = yes" \ "smtp_sasl_security_options = noanonymous" \ "smtp_sasl_password_maps = texthash:/etc/postfix/sasl_passwd" \ "smtp_use_tls = yes" \ "smtp_tls_security_level = encrypt" \ "smtp_tls_note_starttls_offer = yes" \ - "smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt" + "smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt" \ + "sender_dependent_relayhost_maps = texthash:/etc/postfix/relayhost_map" \ + "smtp_sender_dependent_authentication = yes" } function _setup_postfix_dhparam() { diff --git a/test/config/before.dovecot.sieve b/test/config/before.dovecot.sieve new file mode 100644 index 00000000..188412ba --- /dev/null +++ b/test/config/before.dovecot.sieve @@ -0,0 +1,6 @@ +require ["fileinto", "copy"]; + +if address :contains ["From"] "spam@spam.com" { + fileinto :copy "INBOX"; +} + diff --git a/test/config/relay-hosts/postfix-accounts.cf b/test/config/relay-hosts/postfix-accounts.cf new file mode 100644 index 00000000..838994ff --- /dev/null +++ b/test/config/relay-hosts/postfix-accounts.cf @@ -0,0 +1,3 @@ +user1@domainone.tld|{SHA512-CRYPT}$6$UMGnThsSm0IFgzEw$BynVshxudpGQHDRQaF4b7wb57A7NazGZcBUakYYLflp7J4E3UHK2qo/C1qXMCkRlYFlTd.SuwCsCKb7zBaUkb/ +user2@domaintwo.tld|{SHA512-CRYPT}$6$pDkaJmqaNJB0GNtl$692CLTUW1Z2C3kV1c9geJ7jWRrxqtT3eE7d/EEufEU2hI0br.SbLTItIfKPO9vhqpgWYB/bmUM7VRs7UZbN1w. +user3@domainthree.tld|{SHA512-CRYPT}$6$y370fIl7h8PUWXkp$aC2Pp7xw2Gc9CSQW0cYORFfAOjzBJB8iu/GDFe9D4PVnE0aupFGdupm9db.7SU8Ur9T4eZyJ75.Be747XdDZ.0 diff --git a/test/config/relay-hosts/postfix-relaymap.cf b/test/config/relay-hosts/postfix-relaymap.cf new file mode 100644 index 00000000..bb20c536 --- /dev/null +++ b/test/config/relay-hosts/postfix-relaymap.cf @@ -0,0 +1,2 @@ +@domaintwo.tld [other.relay.com]:587 +@domainthree.tld diff --git a/test/config/relay-hosts/postfix-sasl-password.cf b/test/config/relay-hosts/postfix-sasl-password.cf new file mode 100644 index 00000000..186d4651 --- /dev/null +++ b/test/config/relay-hosts/postfix-sasl-password.cf @@ -0,0 +1 @@ +@domaintwo.tld smtp_user_2:smtp_password_2 diff --git a/test/tests.bats b/test/tests.bats index c6ee913a..4bc6db65 100644 --- a/test/tests.bats +++ b/test/tests.bats @@ -15,7 +15,7 @@ load 'test_helper/bats-assert/load' } @test "checking configuration: hostname/domainname override: check container hostname is applied correctly" { - run docker exec mail_override_hostname /bin/bash -c "hostname | grep mail.my-domain.com" + run docker exec mail_override_hostname /bin/bash -c "hostname | grep unknown.domain.tld" assert_success } @@ -329,7 +329,7 @@ load 'test_helper/bats-assert/load' @test "checking smtp: user1 should have received 6 mails" { run docker exec mail /bin/sh -c "ls -A /var/mail/localhost.localdomain/user1/new | wc -l" assert_success - assert_output 6 + assert_output 7 } @test "checking smtp: rejects mail to unknown user" { @@ -1047,9 +1047,18 @@ load 'test_helper/bats-assert/load' assert_output 1 } +@test "checking sieve global: user1 should have gotten a copy of his spam mail" { + run docker exec mail /bin/sh -c "grep 'Spambot ' -R /var/mail/localhost.localdomain/user1/new/" + assert_success +} # # accounts # +@test "checking accounts: user_without_domain creation should be rejected since user@domain format is required" { + run docker exec mail /bin/sh -c "addmailuser user_without_domain mypassword" + assert_failure + assert_output --partial "username must include the domain" +} @test "checking accounts: user3 should have been added to /tmp/docker-mailserver/postfix-accounts.cf" { docker exec mail /bin/sh -c "addmailuser user3@domain.tld mypassword" @@ -1188,17 +1197,28 @@ load 'test_helper/bats-assert/load' assert_success value=$(cat ./config/postfix-accounts.cf | grep lorem@impsum.org | awk -F '|' '{print $1}') [ "$value" = "lorem@impsum.org" ] + + docker exec mail doveadm auth test -x service=smtp pass@localhost.localdomain 'may be \a `p^a.*ssword' | grep 'auth succeeded' + assert_success } + @test "checking setup.sh: setup.sh email list" { run ./setup.sh -c mail email list assert_success } + @test "checking setup.sh: setup.sh email update" { initialpass=$(cat ./config/postfix-accounts.cf | grep lorem@impsum.org | awk -F '|' '{print $2}') - run ./setup.sh -c mail email update lorem@impsum.org consectetur + run ./setup.sh email update lorem@impsum.org my password + sleep 10 updatepass=$(cat ./config/postfix-accounts.cf | grep lorem@impsum.org | awk -F '|' '{print $2}') - [ "$initialpass" != "$changepass" ] + [ "$initialpass" != "$updatepass" ] + assert_success + + docker exec mail doveadm pw -t "$updatepass" -p 'my password' | grep 'verified' + assert_success } + @test "checking setup.sh: setup.sh email del" { run ./setup.sh -c mail email del -y lorem@impsum.org assert_success @@ -1243,7 +1263,6 @@ load 'test_helper/bats-assert/load' run /bin/sh -c 'cat ./config/postfix-virtual.cf | grep "test1@example.org test1@forward.com,test2@forward.com" | wc -l | grep 1' assert_success } - @test "checking setup.sh: setup.sh alias del" { echo -e 'test1@example.org test1@forward.com,test2@forward.com\ntest2@example.org test1@forward.com' > ./config/postfix-virtual.cf @@ -1310,6 +1329,46 @@ load 'test_helper/bats-assert/load' assert_output --partial "You need to specify an IP address. Run" } +@test "checking setup.sh: setup.sh relay add-domain" { + echo -n > ./config/postfix-relaymap.cf + ./setup.sh -c mail relay add-domain example1.org smtp.relay1.com 2525 + ./setup.sh -c mail relay add-domain example2.org smtp.relay2.com + ./setup.sh -c mail relay add-domain example3.org smtp.relay3.com 2525 + ./setup.sh -c mail relay add-domain example3.org smtp.relay.com 587 + + # check adding + run /bin/sh -c 'cat ./config/postfix-relaymap.cf | grep -e "^@example1.org\s\+\[smtp.relay1.com\]:2525" | wc -l | grep 1' + assert_success + # test default port + run /bin/sh -c 'cat ./config/postfix-relaymap.cf | grep -e "^@example2.org\s\+\[smtp.relay2.com\]:25" | wc -l | grep 1' + assert_success + # test modifying + run /bin/sh -c 'cat ./config/postfix-relaymap.cf | grep -e "^@example3.org\s\+\[smtp.relay.com\]:587" | wc -l | grep 1' + assert_success +} + +@test "checking setup.sh: setup.sh relay add-auth" { + echo -n > ./config/postfix-sasl-password.cf + ./setup.sh -c mail relay add-auth example.org smtp_user smtp_pass + ./setup.sh -c mail relay add-auth example2.org smtp_user2 smtp_pass2 + ./setup.sh -c mail relay add-auth example2.org smtp_user2 smtp_pass_new + + # test adding + run /bin/sh -c 'cat ./config/postfix-sasl-password.cf | grep -e "^@example.org\s\+smtp_user:smtp_pass" | wc -l | grep 1' + assert_success + # test updating + run /bin/sh -c 'cat ./config/postfix-sasl-password.cf | grep -e "^@example2.org\s\+smtp_user2:smtp_pass_new" | wc -l | grep 1' + assert_success +} + +@test "checking setup.sh: setup.sh relay exclude-domain" { + echo -n > ./config/postfix-relaymap.cf + ./setup.sh -c mail relay exclude-domain example.org + + run /bin/sh -c 'cat ./config/postfix-relaymap.cf | grep -e "^@example.org\s*$" | wc -l | grep 1' + assert_success +} + # # LDAP # @@ -1424,11 +1483,14 @@ load 'test_helper/bats-assert/load' } @test "checking dovecot: postmaster address" { - run docker exec mail /bin/sh -c "grep 'postmaster_address = postmaster@domain.com' /etc/dovecot/conf.d/15-lda.conf" + run docker exec mail /bin/sh -c "grep 'postmaster_address = postmaster@my-domain.com' /etc/dovecot/conf.d/15-lda.conf" assert_success run docker exec mail_with_ldap /bin/sh -c "grep 'postmaster_address = postmaster@localhost.localdomain' /etc/dovecot/conf.d/15-lda.conf" assert_success + + run docker exec mail_override_hostname /bin/sh -c "grep 'postmaster_address = postmaster@my-domain.com' /etc/dovecot/conf.d/15-lda.conf" + assert_success } @test "checking spoofing: rejects sender forging" { @@ -1617,3 +1679,32 @@ load 'test_helper/bats-assert/load' run docker exec mail_with_ldap /bin/bash -c "pkill saslauthd && sleep 10 && ps aux --forest | grep -v grep | grep '/usr/sbin/saslauthd'" assert_success } + +# +# relay hosts +# + +@test "checking relay hosts: default mapping is added from env vars" { + run docker exec mail_with_relays /bin/sh -c 'cat /etc/postfix/relayhost_map | grep -e "^@domainone.tld\s\+\[default.relay.com\]:2525" | wc -l | grep 1' + assert_success +} + +@test "checking relay hosts: custom mapping is added from file" { + run docker exec mail_with_relays /bin/sh -c 'cat /etc/postfix/relayhost_map | grep -e "^@domaintwo.tld\s\+\[other.relay.com\]:587" | wc -l | grep 1' + assert_success +} + +@test "checking relay hosts: ignored domain is not added" { + run docker exec mail_with_relays /bin/sh -c 'cat /etc/postfix/relayhost_map | grep -e "^@domainthree.tld\s\+\[any.relay.com\]:25" | wc -l | grep 0' + assert_success +} + +@test "checking relay hosts: auth entry is added" { + run docker exec mail_with_relays /bin/sh -c 'cat /etc/postfix/sasl_passwd | grep -e "^@domaintwo.tld\s\+smtp_user_2:smtp_password_2" | wc -l | grep 1' + assert_success +} + +@test "checking relay hosts: default auth entry is added" { + run docker exec mail_with_relays /bin/sh -c 'cat /etc/postfix/sasl_passwd | grep -e "^\[default.relay.com\]:2525\s\+smtp_user:smtp_password" | wc -l | grep 1' + assert_success +}