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 <casperklein@users.noreply.github.com>
Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
This commit is contained in:
Georg Lauterbach 2021-11-01 02:20:22 +01:00 committed by GitHub
parent 0c8c936c74
commit 537247031f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 46 deletions

View file

@ -1,21 +1,19 @@
# Authentication for passwd-file users. Included from 10-auth.conf. # Authentication for passwd-file users. Included from 10-auth.conf.
# #
# passwd-like file with specified location. # Documentation
# <doc/wiki/AuthDatabase.PasswdFile.txt> # 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 { passdb {
driver = passwd-file driver = passwd-file
args = scheme=CRYPT username_format=%u /etc/dovecot/userdb args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/userdb
} }
userdb { userdb {
driver = passwd-file driver = passwd-file
args = username_format=%u /etc/dovecot/userdb 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 default_fields = uid=docker gid=docker home=/var/mail/%d/%u
# Override fields from passwd-file
#override_fields = home=/home/virtual/%u
} }

View file

@ -27,33 +27,33 @@ function dms_panic
local SHUTDOWN_MESSAGE local SHUTDOWN_MESSAGE
case "${PANIC_TYPE}" in case "${PANIC_TYPE:-}" in
( 'fail-init' ) # PANIC_INFO == <name of service or process that failed to start / initialize> ( 'fail-init' ) # PANIC_INFO == <name of service or process that failed to start / initialize>
SHUTDOWN_MESSAGE="Failed to start ${PANIC_INFO}!" SHUTDOWN_MESSAGE="Failed to start ${PANIC_INFO}!"
;; ;;
( 'no-env' ) # PANIC_INFO == <ENV VAR name> ( 'no-env' ) # PANIC_INFO == <ENV VAR name>
SHUTDOWN_MESSAGE="Environment Variable: ${PANIC_INFO} is not set!" SHUTDOWN_MESSAGE="Environment Variable: ${PANIC_INFO} is not set!"
;; ;;
( 'no-file' ) # PANIC_INFO == <invalid filepath> ( 'no-file' ) # PANIC_INFO == <invalid filepath>
SHUTDOWN_MESSAGE="File ${PANIC_INFO} does not exist!" SHUTDOWN_MESSAGE="File ${PANIC_INFO} does not exist!"
;; ;;
( 'misconfigured' ) # PANIC_INFO == <something possibly misconfigured, eg an ENV var> ( 'misconfigured' ) # PANIC_INFO == <something possibly misconfigured, eg an ENV var>
SHUTDOWN_MESSAGE="${PANIC_INFO} appears to be misconfigured, please verify." SHUTDOWN_MESSAGE="${PANIC_INFO} appears to be misconfigured, please verify."
;; ;;
( 'invalid-value' ) # PANIC_INFO == <an unsupported or invalid value, eg in a case match> ( 'invalid-value' ) # PANIC_INFO == <an unsupported or invalid value, eg in a case match>
SHUTDOWN_MESSAGE="Invalid value for ${PANIC_INFO}!" SHUTDOWN_MESSAGE="Invalid value for ${PANIC_INFO}!"
;; ;;
( * ) # `dms_panic` was called directly without a valid PANIC_TYPE ( * ) # `dms_panic` was called directly without a valid PANIC_TYPE
SHUTDOWN_MESSAGE='Something broke :(' SHUTDOWN_MESSAGE='Something broke :('
;; ;;
esac esac
if [[ -n ${PANIC_SCOPE} ]] if [[ -n ${PANIC_SCOPE:-} ]]
then then
_shutdown "${PANIC_SCOPE} | ${SHUTDOWN_MESSAGE}" _shutdown "${PANIC_SCOPE} | ${SHUTDOWN_MESSAGE}"
else else

View file

@ -332,6 +332,7 @@ function _setup_dovecot_local_user
# creating users ; 'pass' is encrypted # creating users ; 'pass' is encrypted
# comments and empty lines are ignored # comments and empty lines are ignored
local LOGIN PASS USER_ATTRIBUTES
while IFS=$'|' read -r LOGIN PASS USER_ATTRIBUTES while IFS=$'|' read -r LOGIN PASS USER_ATTRIBUTES
do do
# Setting variables for better readability # Setting variables for better readability
@ -342,24 +343,31 @@ function _setup_dovecot_local_user
if [[ -f /tmp/docker-mailserver/dovecot-quotas.cf ]] if [[ -f /tmp/docker-mailserver/dovecot-quotas.cf ]]
then then
declare -a USER_QUOTA declare -a USER_QUOTA
IFS=':' ; read -r -a USER_QUOTA < <(grep "${USER}@${DOMAIN}:" -i /tmp/docker-mailserver/dovecot-quotas.cf) IFS=':' read -r -a USER_QUOTA < <(grep "${USER}@${DOMAIN}:" -i /tmp/docker-mailserver/dovecot-quotas.cf)
unset IFS
[[ ${#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 fi
# Let's go! if [[ -z ${USER_ATTRIBUTES} ]]
_notify 'inf' "user '${USER}' for domain '${DOMAIN}' with password '********', attr=${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 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 # user:password:uid:gid:(gecos):home:(shell):extra_fields
# Example : echo \
# ${LOGIN}:${PASS}:5000:5000::/var/mail/${DOMAIN}/${USER}::userdb_mail=maildir:/var/mail/${DOMAIN}/${USER} "${LOGIN}:${PASS}:5000:5000::/var/mail/${DOMAIN}/${USER}::${USER_ATTRIBUTES}" \
echo "${LOGIN}:${PASS}:5000:5000::/var/mail/${DOMAIN}/${USER}::${USER_ATTRIBUTES}" >> /etc/dovecot/userdb >>/etc/dovecot/userdb
mkdir -p "/var/mail/${DOMAIN}/${USER}" 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" ]] if [[ -e "/tmp/docker-mailserver/${LOGIN}.dovecot.sieve" ]]
then then
cp "/tmp/docker-mailserver/${LOGIN}.dovecot.sieve" "/var/mail/${DOMAIN}/${USER}/.dovecot.sieve" 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 echo "${DOMAIN}" >> /tmp/vhost.tmp
done < <(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-accounts.cf) 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 else
_notify 'inf' "'/tmp/docker-mailserver/postfix-accounts.cf' is not provided. No mail account created." _notify 'inf' "'/tmp/docker-mailserver/postfix-accounts.cf' is not provided. No mail account created."
fi fi

View file

@ -1,20 +1,19 @@
# Authentication for passwd-file users. Included from 10-auth.conf. # Authentication for passwd-file users. Included from 10-auth.conf.
# #
# passwd-like file with specified location. # Documentation
# <doc/wiki/AuthDatabase.PasswdFile.txt> # 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 { passdb {
driver = passwd-file driver = passwd-file
args = scheme=CRYPT username_format=%u /etc/dovecot/users args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/userdb
} }
userdb { userdb {
driver = passwd-file driver = passwd-file
args = username_format=%u /etc/dovecot/users args = username_format=%u /etc/dovecot/userdb
default_fields = uid=docker gid=docker home=/var/mail/%d/%u
# 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
} }

View file

@ -1,21 +1,19 @@
# Authentication for passwd-file users. Included from 10-auth.conf. # Authentication for passwd-file users. Included from 10-auth.conf.
# #
# passwd-like file with specified location. # Documentation
# <doc/wiki/AuthDatabase.PasswdFile.txt> # 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 { passdb {
driver = passwd-file driver = passwd-file
args = scheme=CRYPT username_format=%u /etc/dovecot/userdb args = scheme=SHA512-CRYPT username_format=%u /etc/dovecot/userdb
} }
userdb { userdb {
driver = passwd-file driver = passwd-file
args = username_format=%u /etc/dovecot/userdb 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 default_fields = uid=docker gid=docker home=/var/mail/%d/%u
# Override fields from passwd-file
#override_fields = home=/home/virtual/%u
} }