From 537247031f536ef14e0cb6818f8c65170eb6878a Mon Sep 17 00:00:00 2001 From: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com> Date: Mon, 1 Nov 2021 02:20:22 +0100 Subject: [PATCH] fix: Make Dovecot aware of basic aliases in userdb for quota support + Use correct hash scheme in passdb configuration (#2248) Dovecot quota support would log auth failures when Postfix validated incoming mail to accept/reject and the `check_policy_service` for `quota-status` was queried with a recipient that was an account alias. When Dovecot is not aware of the user account, it will not be able to check a quota and inform Postfix that everything is fine, Postfix will accept the mail and send it to Dovecot, where if the quota is exceeded will result in a bounce back to the sender. This is considered "backscatter" and can be abused by spammers forging the sender address which can get your server blacklisted. The solution is to either disable quota support `ENABLE_QUOTAS=0`, or as a workaround, add dummy accounts to Dovecot userdb for aliases in `postfix-virtual.cf` (not `postfix-aliases.cf`), these dummy accounts will map to the real user account mailbox (real users are defined in `postfix-accounts.cf`). The workaround is naive, in that we only check for basic 1-to-1 alias mapping to real accounts. This will still be an issue for aliases that map to another alias or multiple addresses (real or alias). Unfortunately Postfix will not expand aliases until accepting mail where this would be too late. A better solution is to proxy the `check_policy_service` from Dovecot `quota-status` that Postfix queries in `main.cf:smtpd_recipient_restrictions`, however this requires a fair amount more of additional work and still requires an implementation to recursively query aliases for nested or multiple address mappings, which can then be forwarded to the `quota-status` service configured by Dovecot in `/etc/dovecot/conf.d/90-quota.conf`. LDAP users are unaffected as quota support is not supported/implemented with `docker-mailserver` at this time, it is always considered disabled when using LDAP. --- Additionally Dovecot configuration for `passdb` has been fixed to use the correct password hash scheme of `SHA512-CRYPT`. Co-authored-by: Casper Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com> --- target/dovecot/auth-passwdfile.inc | 16 ++-- target/scripts/helper-functions.sh | 16 ++-- target/scripts/startup/setup-stack.sh | 87 ++++++++++++++++--- .../conf.d/auth-passwdfile.conf.ext | 19 ++-- .../dovecot-lmtp/conf.d/auth-passwdfile.inc | 16 ++-- 5 files changed, 108 insertions(+), 46 deletions(-) diff --git a/target/dovecot/auth-passwdfile.inc b/target/dovecot/auth-passwdfile.inc index bba1af7e..d05c9abb 100644 --- a/target/dovecot/auth-passwdfile.inc +++ b/target/dovecot/auth-passwdfile.inc @@ -1,21 +1,19 @@ # Authentication for passwd-file users. Included from 10-auth.conf. # -# passwd-like file with specified location. -# +# Documentation +# PassDB: https://doc.dovecot.org/configuration_manual/authentication/password_databases_passdb/ +# UserDB: https://doc.dovecot.org/configuration_manual/authentication/user_databases_userdb/ +# +# !!! Attention !!! +# Do not add `scheme=SHA512-CRYPT` to the userdb args. This is not supported. passdb { driver = passwd-file - args = scheme=CRYPT username_format=%u /etc/dovecot/userdb + args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/userdb } userdb { driver = passwd-file args = username_format=%u /etc/dovecot/userdb - - # Default fields that can be overridden by passwd-file - #default_fields = quota_rule=*:storage=1G default_fields = uid=docker gid=docker home=/var/mail/%d/%u - - # Override fields from passwd-file - #override_fields = home=/home/virtual/%u } diff --git a/target/scripts/helper-functions.sh b/target/scripts/helper-functions.sh index 3333af73..832e9533 100755 --- a/target/scripts/helper-functions.sh +++ b/target/scripts/helper-functions.sh @@ -27,33 +27,33 @@ function dms_panic local SHUTDOWN_MESSAGE - case "${PANIC_TYPE}" in + case "${PANIC_TYPE:-}" in ( 'fail-init' ) # PANIC_INFO == SHUTDOWN_MESSAGE="Failed to start ${PANIC_INFO}!" - ;; + ;; ( 'no-env' ) # PANIC_INFO == SHUTDOWN_MESSAGE="Environment Variable: ${PANIC_INFO} is not set!" - ;; + ;; ( 'no-file' ) # PANIC_INFO == SHUTDOWN_MESSAGE="File ${PANIC_INFO} does not exist!" - ;; + ;; ( 'misconfigured' ) # PANIC_INFO == SHUTDOWN_MESSAGE="${PANIC_INFO} appears to be misconfigured, please verify." - ;; + ;; ( 'invalid-value' ) # PANIC_INFO == SHUTDOWN_MESSAGE="Invalid value for ${PANIC_INFO}!" - ;; + ;; ( * ) # `dms_panic` was called directly without a valid PANIC_TYPE SHUTDOWN_MESSAGE='Something broke :(' - ;; + ;; esac - if [[ -n ${PANIC_SCOPE} ]] + if [[ -n ${PANIC_SCOPE:-} ]] then _shutdown "${PANIC_SCOPE} | ${SHUTDOWN_MESSAGE}" else diff --git a/target/scripts/startup/setup-stack.sh b/target/scripts/startup/setup-stack.sh index 98b6463c..c6e48ea9 100644 --- a/target/scripts/startup/setup-stack.sh +++ b/target/scripts/startup/setup-stack.sh @@ -332,6 +332,7 @@ function _setup_dovecot_local_user # creating users ; 'pass' is encrypted # comments and empty lines are ignored + local LOGIN PASS USER_ATTRIBUTES while IFS=$'|' read -r LOGIN PASS USER_ATTRIBUTES do # Setting variables for better readability @@ -342,24 +343,31 @@ function _setup_dovecot_local_user if [[ -f /tmp/docker-mailserver/dovecot-quotas.cf ]] then declare -a USER_QUOTA - IFS=':' ; read -r -a USER_QUOTA < <(grep "${USER}@${DOMAIN}:" -i /tmp/docker-mailserver/dovecot-quotas.cf) - unset IFS + IFS=':' read -r -a USER_QUOTA < <(grep "${USER}@${DOMAIN}:" -i /tmp/docker-mailserver/dovecot-quotas.cf) - [[ ${#USER_QUOTA[@]} -eq 2 ]] && USER_ATTRIBUTES="${USER_ATTRIBUTES} userdb_quota_rule=*:bytes=${USER_QUOTA[1]}" + if [[ ${#USER_QUOTA[@]} -eq 2 ]] + then + USER_ATTRIBUTES="${USER_ATTRIBUTES:+${USER_ATTRIBUTES} }userdb_quota_rule=*:bytes=${USER_QUOTA[1]}" + fi fi - # Let's go! - _notify 'inf' "user '${USER}' for domain '${DOMAIN}' with password '********', attr=${USER_ATTRIBUTES}" + if [[ -z ${USER_ATTRIBUTES} ]] + then + _notify 'inf' "Creating user '${USER}' for domain '${DOMAIN}'" + else + _notify 'inf' "Creating user '${USER}' for domain '${DOMAIN}' with attributes '${USER_ATTRIBUTES}'" + fi echo "${LOGIN} ${DOMAIN}/${USER}/" >> /etc/postfix/vmailbox - # User database for dovecot has the following format: + # Dovecot's userdb has the following format # user:password:uid:gid:(gecos):home:(shell):extra_fields - # Example : - # ${LOGIN}:${PASS}:5000:5000::/var/mail/${DOMAIN}/${USER}::userdb_mail=maildir:/var/mail/${DOMAIN}/${USER} - echo "${LOGIN}:${PASS}:5000:5000::/var/mail/${DOMAIN}/${USER}::${USER_ATTRIBUTES}" >> /etc/dovecot/userdb + echo \ + "${LOGIN}:${PASS}:5000:5000::/var/mail/${DOMAIN}/${USER}::${USER_ATTRIBUTES}" \ + >>/etc/dovecot/userdb + mkdir -p "/var/mail/${DOMAIN}/${USER}" - # Copy user provided sieve file, if present + # copy user provided sieve file, if present if [[ -e "/tmp/docker-mailserver/${LOGIN}.dovecot.sieve" ]] then cp "/tmp/docker-mailserver/${LOGIN}.dovecot.sieve" "/var/mail/${DOMAIN}/${USER}/.dovecot.sieve" @@ -367,6 +375,65 @@ function _setup_dovecot_local_user echo "${DOMAIN}" >> /tmp/vhost.tmp done < <(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-accounts.cf) + + # see https://github.com/docker-mailserver/docker-mailserver/pull/2248#issuecomment-953313852 + # for more details on this section + if [[ -f /tmp/docker-mailserver/postfix-virtual.cf ]] && [[ ${ENABLE_QUOTAS} -eq 1 ]] + then + # adding aliases to Dovecot's userdb + # ${REAL_FQUN} is a user's fully-qualified username + local ALIAS REAL_FQUN + while read -r ALIAS REAL_FQUN + do + # ignore comments + [[ ${ALIAS} == \#* ]] && continue + + # alias is assumed to not be a proper e-mail + # these aliases do not need to be added to Dovecot's userdb + [[ ! ${ALIAS} == *@* ]] && continue + + # clear possibly already filled arrays + # do not remove the following line of code + unset REAL_ACC USER_QUOTA + declare -a REAL_ACC USER_QUOTA + + local REAL_USERNAME REAL_DOMAINNAME + REAL_USERNAME=$(cut -d '@' -f 1 <<< "${REAL_FQUN}") + REAL_DOMAINNAME=$(cut -d '@' -f 2 <<< "${REAL_FQUN}") + + if ! grep -q "${REAL_FQUN}" /tmp/docker-mailserver/postfix-accounts.cf + then + _notify 'inf' "Alias '${ALIAS}' is non-local (or mapped to a non-existing account) and will not be added to Dovecot's userdb" + continue + fi + + _notify 'inf' "Adding alias '${ALIAS}' for user '${REAL_FQUN}' to Dovecot's userdb" + + # ${REAL_ACC[0]} => real account name (e-mail address) == ${REAL_FQUN} + # ${REAL_ACC[1]} => password hash + # ${REAL_ACC[2]} => optional user attributes + IFS='|' read -r -a REAL_ACC < <(grep "${REAL_FQUN}" /tmp/docker-mailserver/postfix-accounts.cf) + + if [[ -z ${REAL_ACC[1]} ]] + then + dms_panic__misconfigured 'postfix-accounts.cf' 'alias configuration' + fi + + # test if user has a defined quota + if [[ -f /tmp/docker-mailserver/dovecot-quotas.cf ]] + then + IFS=':' read -r -a USER_QUOTA < <(grep "${REAL_FQUN}:" -i /tmp/docker-mailserver/dovecot-quotas.cf) + if [[ ${#USER_QUOTA[@]} -eq 2 ]] + then + REAL_ACC[2]="${REAL_ACC[2]:+${REAL_ACC[2]} }userdb_quota_rule=*:bytes=${USER_QUOTA[1]}" + fi + fi + + echo \ + "${ALIAS}:${REAL_ACC[1]}:5000:5000::/var/mail/${REAL_DOMAINNAME}/${REAL_USERNAME}::${REAL_ACC[2]:-}" \ + >> /etc/dovecot/userdb + done < /tmp/docker-mailserver/postfix-virtual.cf + fi else _notify 'inf' "'/tmp/docker-mailserver/postfix-accounts.cf' is not provided. No mail account created." fi diff --git a/test/config/dovecot-lmtp/conf.d/auth-passwdfile.conf.ext b/test/config/dovecot-lmtp/conf.d/auth-passwdfile.conf.ext index c89d28c6..d05c9abb 100644 --- a/test/config/dovecot-lmtp/conf.d/auth-passwdfile.conf.ext +++ b/test/config/dovecot-lmtp/conf.d/auth-passwdfile.conf.ext @@ -1,20 +1,19 @@ # Authentication for passwd-file users. Included from 10-auth.conf. # -# passwd-like file with specified location. -# +# Documentation +# PassDB: https://doc.dovecot.org/configuration_manual/authentication/password_databases_passdb/ +# UserDB: https://doc.dovecot.org/configuration_manual/authentication/user_databases_userdb/ +# +# !!! Attention !!! +# Do not add `scheme=SHA512-CRYPT` to the userdb args. This is not supported. passdb { driver = passwd-file - args = scheme=CRYPT username_format=%u /etc/dovecot/users + args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/userdb } userdb { driver = passwd-file - args = username_format=%u /etc/dovecot/users - - # Default fields that can be overridden by passwd-file - #default_fields = quota_rule=*:storage=1G - - # Override fields from passwd-file - #override_fields = home=/home/virtual/%u + args = username_format=%u /etc/dovecot/userdb + default_fields = uid=docker gid=docker home=/var/mail/%d/%u } diff --git a/test/config/dovecot-lmtp/conf.d/auth-passwdfile.inc b/test/config/dovecot-lmtp/conf.d/auth-passwdfile.inc index bba1af7e..d05c9abb 100644 --- a/test/config/dovecot-lmtp/conf.d/auth-passwdfile.inc +++ b/test/config/dovecot-lmtp/conf.d/auth-passwdfile.inc @@ -1,21 +1,19 @@ # Authentication for passwd-file users. Included from 10-auth.conf. # -# passwd-like file with specified location. -# +# Documentation +# PassDB: https://doc.dovecot.org/configuration_manual/authentication/password_databases_passdb/ +# UserDB: https://doc.dovecot.org/configuration_manual/authentication/user_databases_userdb/ +# +# !!! Attention !!! +# Do not add `scheme=SHA512-CRYPT` to the userdb args. This is not supported. passdb { driver = passwd-file - args = scheme=CRYPT username_format=%u /etc/dovecot/userdb + args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/userdb } userdb { driver = passwd-file args = username_format=%u /etc/dovecot/userdb - - # Default fields that can be overridden by passwd-file - #default_fields = quota_rule=*:storage=1G default_fields = uid=docker gid=docker home=/var/mail/%d/%u - - # Override fields from passwd-file - #override_fields = home=/home/virtual/%u }