diff --git a/Dockerfile b/Dockerfile index 938428b6..400e35c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -258,6 +258,7 @@ COPY \ RUN chmod +x /usr/local/bin/* COPY target/scripts/helpers /usr/local/bin/helpers +COPY target/scripts/startup/setup.d /usr/local/bin/setup.d # # Final stage focuses only on image config diff --git a/target/bin/debug-fetchmail b/target/bin/debug-fetchmail index ab3df7e6..f1e7051f 100755 --- a/target/bin/debug-fetchmail +++ b/target/bin/debug-fetchmail @@ -2,8 +2,8 @@ # shellcheck source=../scripts/helpers/log.sh source /usr/local/bin/helpers/log.sh -# shellcheck source=../scripts/startup/setup-stack.sh -source /usr/local/bin/setup-stack.sh +# shellcheck source=../scripts/startup/setup.d/fetchmail.sh +source /usr/local/bin/setup.d/fetchmail.sh _setup_fetchmail diff --git a/target/scripts/helpers/dhparams.sh b/target/scripts/helpers/dhparams.sh new file mode 100644 index 00000000..e487989f --- /dev/null +++ b/target/scripts/helpers/dhparams.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +function _setup_dhparam +{ + local DH_SERVICE=$1 + local DH_DEST=$2 + local DH_CUSTOM='/tmp/docker-mailserver/dhparams.pem' + + _log 'debug' "Setting up ${DH_SERVICE} dhparam" + + if [[ -f ${DH_CUSTOM} ]] + then # use custom supplied dh params (assumes they're probably insecure) + _log 'trace' "${DH_SERVICE} will use custom provided DH paramters" + _log 'warn' "Using self-generated dhparams is considered insecure - unless you know what you are doing, please remove '${DH_CUSTOM}'" + + cp -f "${DH_CUSTOM}" "${DH_DEST}" + else # use official standardized dh params (provided via Dockerfile) + _log 'trace' "${DH_SERVICE} will use official standardized DH parameters (ffdhe4096)." + fi +} diff --git a/target/scripts/helpers/index.sh b/target/scripts/helpers/index.sh index 1e919513..644f8374 100644 --- a/target/scripts/helpers/index.sh +++ b/target/scripts/helpers/index.sh @@ -10,6 +10,7 @@ function _import_scripts source "${PATH_TO_SCRIPTS}/accounts.sh" source "${PATH_TO_SCRIPTS}/aliases.sh" source "${PATH_TO_SCRIPTS}/change-detection.sh" + source "${PATH_TO_SCRIPTS}/dhparams.sh" source "${PATH_TO_SCRIPTS}/dns.sh" source "${PATH_TO_SCRIPTS}/error.sh" source "${PATH_TO_SCRIPTS}/lock.sh" diff --git a/target/scripts/start-mailserver.sh b/target/scripts/start-mailserver.sh index 4c709f05..6498a914 100755 --- a/target/scripts/start-mailserver.sh +++ b/target/scripts/start-mailserver.sh @@ -13,6 +13,9 @@ # shellcheck source=./helpers/index.sh source /usr/local/bin/helpers/index.sh +# shellcheck source=./startup/variables-stack.sh +source /usr/local/bin/variables-stack.sh + # shellcheck source=./startup/check-stack.sh source /usr/local/bin/check-stack.sh @@ -34,13 +37,8 @@ source /usr/local/bin/daemons-stack.sh # ? >> Early setup & environment variables setup # ------------------------------------------------------------ -# shellcheck source=./helpers/variables.sh -source /usr/local/bin/helpers/variables.sh - -_setup_supervisor -_obtain_hostname_and_domainname -_environment_variables_backwards_compatibility -_environment_variables_general_setup +_early_setup_supervisor +_early_variables_setup # ------------------------------------------------------------ # ? << Early setup & environment variables setup @@ -81,7 +79,7 @@ function _register_functions ;; ( 'OIDC' ) - _register_setup_function '_setup_oidc' + _shutdown 'OIDC user account provisioning is not yet implemented' ;; ( * ) @@ -195,12 +193,6 @@ function _register_start_daemon _log 'trace' "${1}() registered" } -function _register_setup_function -{ - FUNCS_SETUP+=("${1}") - _log 'trace' "${1}() registered" -} - function _register_fix_function { FUNCS_FIX+=("${1}") @@ -233,7 +225,7 @@ _setup [[ ${LOG_LEVEL} =~ (debug|trace) ]] && print-environment _apply_fixes _start_misc -_setup_user_patches +_setup_run_user_patches _start_daemons # marker to check if container was restarted diff --git a/target/scripts/startup/setup-stack.sh b/target/scripts/startup/setup-stack.sh index a7ec552c..461ab3f0 100644 --- a/target/scripts/startup/setup-stack.sh +++ b/target/scripts/startup/setup-stack.sh @@ -1,7 +1,21 @@ #!/bin/bash +declare -a FUNCS_SETUP + +function _register_setup_function +{ + FUNCS_SETUP+=("${1}") + _log 'trace' "${1}() registered" +} + function _setup { + while read -r FILE + do + # shellcheck source=/dev/null + source "${FILE}" + done < <(find /usr/local/bin/setup.d/ -type f) + _log 'info' 'Configuring mail server' for FUNC in "${FUNCS_SETUP[@]}" do @@ -12,7 +26,7 @@ function _setup _prepare_for_change_detection } -function _setup_supervisor +function _early_setup_supervisor { SUPERVISOR_LOGLEVEL="${SUPERVISOR_LOGLEVEL:-warn}" @@ -62,1048 +76,7 @@ function _setup_file_permissions chmod 640 /var/log/mail/freshclam.log } -function _setup_mailname -{ - _log 'debug' "Setting up mailname and creating '/etc/mailname'" - echo "${DOMAINNAME}" >/etc/mailname -} - -function _setup_amavis -{ - if [[ ${ENABLE_AMAVIS} -eq 1 ]] - then - _log 'debug' 'Setting up Amavis' - - cat /etc/dms/postfix/master.d/postfix-amavis.cf >>/etc/postfix/master.cf - postconf 'content_filter = smtp-amavis:[127.0.0.1]:10024' - - sed -i \ - "s|^#\$myhostname = \"mail.example.com\";|\$myhostname = \"${HOSTNAME}\";|" \ - /etc/amavis/conf.d/05-node_id - else - _log 'debug' 'Disabling Amavis cron job' - mv /etc/cron.d/amavisd-new /etc/cron.d/amavisd-new.disabled - chmod 0 /etc/cron.d/amavisd-new.disabled - - if [[ ${ENABLE_CLAMAV} -eq 1 ]] && [[ ${ENABLE_RSPAMD} -eq 0 ]] - then - _log 'warn' 'ClamAV will not work when Amavis & rspamd are disabled. Enable either Amavis or rspamd to fix it.' - fi - - if [[ ${ENABLE_SPAMASSASSIN} -eq 1 ]] - then - _log 'warn' 'Spamassassin will not work when Amavis is disabled. Enable Amavis to fix it.' - fi - fi -} - -function _setup_rspamd -{ - if [[ -f /usr/local/bin/helpers/setup-rspamd.sh ]] - then - # ShellCheck sources this from two different files, hence we discard the check of external sources. - # shellcheck source=/dev/null - source /usr/local/bin/helpers/setup-rspamd.sh - else - _shutdown 'error' '(Rspamd setup) Helper functions required for setup were not found' - fi - - _log 'warn' 'Rspamd integration is work in progress - expect (breaking) changes at any time' - _log 'debug' 'Enabling Rspamd' - - __rspamd__preflight_checks - __rspamd__adjust_postfix_configuration - __rspamd__disable_default_modules - __rspamd__handle_modules_configuration -} - -function _setup_dmarc_hostname -{ - _log 'debug' 'Setting up DMARC' - sed -i -e \ - "s|^AuthservID.*$|AuthservID ${HOSTNAME}|g" \ - -e "s|^TrustedAuthservIDs.*$|TrustedAuthservIDs ${HOSTNAME}|g" \ - /etc/opendmarc.conf -} - -function _setup_postfix_hostname -{ - _log 'debug' 'Applying hostname and domainname to Postfix' - postconf "myhostname = ${HOSTNAME}" - postconf "mydomain = ${DOMAINNAME}" -} - -function _setup_dovecot_hostname -{ - _log 'debug' 'Applying hostname to Dovecot' - sed -i "s|^#hostname =.*$|hostname = '${HOSTNAME}'|g" /etc/dovecot/conf.d/15-lda.conf -} - -function _setup_dovecot -{ - _log 'debug' 'Setting up Dovecot' - - cp -a /usr/share/dovecot/protocols.d /etc/dovecot/ - # disable pop3 (it will be eventually enabled later in the script, if requested) - mv /etc/dovecot/protocols.d/pop3d.protocol /etc/dovecot/protocols.d/pop3d.protocol.disab - mv /etc/dovecot/protocols.d/managesieved.protocol /etc/dovecot/protocols.d/managesieved.protocol.disab - sed -i -e 's|#ssl = yes|ssl = yes|g' /etc/dovecot/conf.d/10-master.conf - sed -i -e 's|#port = 993|port = 993|g' /etc/dovecot/conf.d/10-master.conf - sed -i -e 's|#port = 995|port = 995|g' /etc/dovecot/conf.d/10-master.conf - sed -i -e 's|#ssl = yes|ssl = required|g' /etc/dovecot/conf.d/10-ssl.conf - sed -i 's|^postmaster_address = .*$|postmaster_address = '"${POSTMASTER_ADDRESS}"'|g' /etc/dovecot/conf.d/15-lda.conf - - if ! grep -q -E '^stats_writer_socket_path=' /etc/dovecot/dovecot.conf - then - printf '\n%s\n' 'stats_writer_socket_path=' >>/etc/dovecot/dovecot.conf - fi - - # set mail_location according to mailbox format - case "${DOVECOT_MAILBOX_FORMAT}" in - - ( 'sdbox' | 'mdbox' ) - _log 'trace' "Dovecot ${DOVECOT_MAILBOX_FORMAT} format configured" - sed -i -e \ - "s|^mail_location = .*$|mail_location = ${DOVECOT_MAILBOX_FORMAT}:\/var\/mail\/%d\/%n|g" \ - /etc/dovecot/conf.d/10-mail.conf - - _log 'trace' 'Enabling cron job for dbox purge' - mv /etc/cron.d/dovecot-purge.disabled /etc/cron.d/dovecot-purge - chmod 644 /etc/cron.d/dovecot-purge - ;; - - ( * ) - _log 'trace' 'Dovecot default format (maildir) configured' - sed -i -e 's|^mail_location = .*$|mail_location = maildir:\/var\/mail\/%d\/%n|g' /etc/dovecot/conf.d/10-mail.conf - ;; - - esac - - # enable Managesieve service by setting the symlink - # to the configuration file Dovecot will actually find - if [[ ${ENABLE_MANAGESIEVE} -eq 1 ]] - then - _log 'trace' 'Sieve management enabled' - mv /etc/dovecot/protocols.d/managesieved.protocol.disab /etc/dovecot/protocols.d/managesieved.protocol - fi - - # copy pipe and filter programs, if any - rm -f /usr/lib/dovecot/sieve-filter/* - rm -f /usr/lib/dovecot/sieve-pipe/* - [[ -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/ - - # create global sieve directories - mkdir -p /usr/lib/dovecot/sieve-global/before - mkdir -p /usr/lib/dovecot/sieve-global/after - - if [[ -f /tmp/docker-mailserver/before.dovecot.sieve ]] - then - cp /tmp/docker-mailserver/before.dovecot.sieve /usr/lib/dovecot/sieve-global/before/50-before.dovecot.sieve - sievec /usr/lib/dovecot/sieve-global/before/50-before.dovecot.sieve - else - rm -f /usr/lib/dovecot/sieve-global/before/50-before.dovecot.sieve /usr/lib/dovecot/sieve-global/before/50-before.dovecot.svbin - fi - - if [[ -f /tmp/docker-mailserver/after.dovecot.sieve ]] - then - cp /tmp/docker-mailserver/after.dovecot.sieve /usr/lib/dovecot/sieve-global/after/50-after.dovecot.sieve - sievec /usr/lib/dovecot/sieve-global/after/50-after.dovecot.sieve - else - rm -f /usr/lib/dovecot/sieve-global/after/50-after.dovecot.sieve /usr/lib/dovecot/sieve-global/after/50-after.dovecot.svbin - fi - - # sieve will move spams to .Junk folder when SPAMASSASSIN_SPAM_TO_INBOX=1 and MOVE_SPAM_TO_JUNK=1 - if [[ ${SPAMASSASSIN_SPAM_TO_INBOX} -eq 1 ]] && [[ ${MOVE_SPAM_TO_JUNK} -eq 1 ]] - then - _log 'debug' 'Spam messages will be moved to the Junk folder' - cp /etc/dovecot/sieve/before/60-spam.sieve /usr/lib/dovecot/sieve-global/before/ - sievec /usr/lib/dovecot/sieve-global/before/60-spam.sieve - else - rm -f /usr/lib/dovecot/sieve-global/before/60-spam.sieve /usr/lib/dovecot/sieve-global/before/60-spam.svbin - fi - - chown docker:docker -R /usr/lib/dovecot/sieve* - chmod 550 -R /usr/lib/dovecot/sieve* - chmod -f +x /usr/lib/dovecot/sieve-pipe/* -} - -function _setup_dovecot_quota -{ - _log 'debug' 'Setting up Dovecot quota' - - # Dovecot quota is disabled when using LDAP or SMTP_ONLY or when explicitly disabled. - if [[ ${ACCOUNT_PROVISIONER} != 'FILE' ]] || [[ ${SMTP_ONLY} -eq 1 ]] || [[ ${ENABLE_QUOTAS} -eq 0 ]] - then - # disable dovecot quota in docevot confs - if [[ -f /etc/dovecot/conf.d/90-quota.conf ]] - then - mv /etc/dovecot/conf.d/90-quota.conf /etc/dovecot/conf.d/90-quota.conf.disab - sed -i \ - "s|mail_plugins = \$mail_plugins quota|mail_plugins = \$mail_plugins|g" \ - /etc/dovecot/conf.d/10-mail.conf - sed -i \ - "s|mail_plugins = \$mail_plugins imap_quota|mail_plugins = \$mail_plugins|g" \ - /etc/dovecot/conf.d/20-imap.conf - fi - - # disable quota policy check in postfix - sed -i "s|check_policy_service inet:localhost:65265||g" /etc/postfix/main.cf - else - if [[ -f /etc/dovecot/conf.d/90-quota.conf.disab ]] - then - mv /etc/dovecot/conf.d/90-quota.conf.disab /etc/dovecot/conf.d/90-quota.conf - sed -i \ - "s|mail_plugins = \$mail_plugins|mail_plugins = \$mail_plugins quota|g" \ - /etc/dovecot/conf.d/10-mail.conf - sed -i \ - "s|mail_plugins = \$mail_plugins|mail_plugins = \$mail_plugins imap_quota|g" \ - /etc/dovecot/conf.d/20-imap.conf - fi - - local MESSAGE_SIZE_LIMIT_MB=$((POSTFIX_MESSAGE_SIZE_LIMIT / 1000000)) - local MAILBOX_LIMIT_MB=$((POSTFIX_MAILBOX_SIZE_LIMIT / 1000000)) - - sed -i \ - "s|quota_max_mail_size =.*|quota_max_mail_size = ${MESSAGE_SIZE_LIMIT_MB}$([[ ${MESSAGE_SIZE_LIMIT_MB} -eq 0 ]] && echo "" || echo "M")|g" \ - /etc/dovecot/conf.d/90-quota.conf - - sed -i \ - "s|quota_rule = \*:storage=.*|quota_rule = *:storage=${MAILBOX_LIMIT_MB}$([[ ${MAILBOX_LIMIT_MB} -eq 0 ]] && echo "" || echo "M")|g" \ - /etc/dovecot/conf.d/90-quota.conf - - if [[ -d /tmp/docker-mailserver ]] && [[ ! -f /tmp/docker-mailserver/dovecot-quotas.cf ]] - then - _log 'trace' "'/tmp/docker-mailserver/dovecot-quotas.cf' is not provided. Using default quotas." - : >/tmp/docker-mailserver/dovecot-quotas.cf - fi - - # enable quota policy check in postfix - sed -i -E \ - "s|(reject_unknown_recipient_domain)|\1, check_policy_service inet:localhost:65265|g" \ - /etc/postfix/main.cf - fi -} - -function _setup_dovecot_local_user -{ - [[ ${SMTP_ONLY} -eq 1 ]] && return 0 - [[ ${ACCOUNT_PROVISIONER} == 'FILE' ]] || return 0 - - _log 'debug' 'Setting up Dovecot Local User' - - if [[ ! -f /tmp/docker-mailserver/postfix-accounts.cf ]] - then - _log 'trace' "No mail accounts to create - '/tmp/docker-mailserver/postfix-accounts.cf' is missing" - fi - - function __wait_until_an_account_is_added_or_shutdown - { - local SLEEP_PERIOD='10' - - for (( COUNTER = 11 ; COUNTER >= 0 ; COUNTER-- )) - do - if [[ $(grep -cE '.+@.+\|' /tmp/docker-mailserver/postfix-accounts.cf 2>/dev/null || printf '%s' '0') -ge 1 ]] - then - return 0 - else - _log 'warn' "You need at least one mail account to start Dovecot ($(( ( COUNTER + 1 ) * SLEEP_PERIOD ))s left for account creation before shutdown)" - sleep "${SLEEP_PERIOD}" - fi - done - - _shutdown 'No accounts provided - Dovecot could not be started' - } - - __wait_until_an_account_is_added_or_shutdown - - _create_accounts -} - -function _setup_ldap -{ - _log 'debug' 'Setting up LDAP' - _log 'trace' 'Checking for custom configs' - - for i in 'users' 'groups' 'aliases' 'domains' - do - local FPATH="/tmp/docker-mailserver/ldap-${i}.cf" - if [[ -f ${FPATH} ]] - then - cp "${FPATH}" "/etc/postfix/ldap-${i}.cf" - fi - done - - _log 'trace' 'Starting to override configs' - - local FILES=( - /etc/postfix/ldap-users.cf - /etc/postfix/ldap-groups.cf - /etc/postfix/ldap-aliases.cf - /etc/postfix/ldap-domains.cf - /etc/postfix/ldap-senders.cf - /etc/postfix/maps/sender_login_maps.ldap - ) - - for FILE in "${FILES[@]}" - do - [[ ${FILE} =~ ldap-user ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_USER}" - [[ ${FILE} =~ ldap-group ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_GROUP}" - [[ ${FILE} =~ ldap-aliases ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_ALIAS}" - [[ ${FILE} =~ ldap-domains ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_DOMAIN}" - [[ ${FILE} =~ ldap-senders ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_SENDERS}" - [[ -f ${FILE} ]] && _replace_by_env_in_file 'LDAP_' "${FILE}" - done - - _log 'trace' "Configuring Dovecot LDAP" - - declare -A DOVECOT_LDAP_MAPPING - - DOVECOT_LDAP_MAPPING['DOVECOT_BASE']="${DOVECOT_BASE:="${LDAP_SEARCH_BASE}"}" - DOVECOT_LDAP_MAPPING['DOVECOT_DN']="${DOVECOT_DN:="${LDAP_BIND_DN}"}" - DOVECOT_LDAP_MAPPING['DOVECOT_DNPASS']="${DOVECOT_DNPASS:="${LDAP_BIND_PW}"}" - DOVECOT_LDAP_MAPPING['DOVECOT_URIS']="${DOVECOT_URIS:="${DOVECOT_HOSTS:="${LDAP_SERVER_HOST}"}"}" - - # Add protocol to DOVECOT_URIS so that we can use dovecot's "uris" option: - # https://doc.dovecot.org/configuration_manual/authentication/ldap/ - if [[ ${DOVECOT_LDAP_MAPPING["DOVECOT_URIS"]} != *'://'* ]] - then - DOVECOT_LDAP_MAPPING['DOVECOT_URIS']="ldap://${DOVECOT_LDAP_MAPPING["DOVECOT_URIS"]}" - fi - - # Default DOVECOT_PASS_FILTER to the same value as DOVECOT_USER_FILTER - DOVECOT_LDAP_MAPPING['DOVECOT_PASS_FILTER']="${DOVECOT_PASS_FILTER:="${DOVECOT_USER_FILTER}"}" - - for VAR in "${!DOVECOT_LDAP_MAPPING[@]}" - do - export "${VAR}=${DOVECOT_LDAP_MAPPING[${VAR}]}" - done - - _replace_by_env_in_file 'DOVECOT_' '/etc/dovecot/dovecot-ldap.conf.ext' - - _log 'trace' 'Enabling Dovecot LDAP authentication' - - 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 - - _log 'trace' "Configuring LDAP" - - if [[ -f /etc/postfix/ldap-users.cf ]] - then - postconf 'virtual_mailbox_maps = ldap:/etc/postfix/ldap-users.cf' - else - _log 'warn' "'/etc/postfix/ldap-users.cf' not found" - fi - - if [[ -f /etc/postfix/ldap-domains.cf ]] - then - postconf 'virtual_mailbox_domains = /etc/postfix/vhost, ldap:/etc/postfix/ldap-domains.cf' - else - _log 'warn' "'/etc/postfix/ldap-domains.cf' not found" - fi - - if [[ -f /etc/postfix/ldap-aliases.cf ]] && [[ -f /etc/postfix/ldap-groups.cf ]] - then - postconf 'virtual_alias_maps = ldap:/etc/postfix/ldap-aliases.cf, ldap:/etc/postfix/ldap-groups.cf' - else - _log 'warn' "'/etc/postfix/ldap-aliases.cf' and / or '/etc/postfix/ldap-groups.cf' not found" - fi - - # shellcheck disable=SC2016 - sed -i 's|mydestination = \$myhostname, |mydestination = |' /etc/postfix/main.cf - - return 0 -} - -function _setup_oidc -{ - _shutdown 'OIDC user account provisioning is not yet implemented' -} - -function _setup_postgrey -{ - _log 'debug' 'Configuring Postgrey' - - sedfile -i -E \ - 's|(^smtpd_recipient_restrictions =.*)|\1, check_policy_service inet:127.0.0.1:10023|' \ - /etc/postfix/main.cf - - sed -i -e \ - "s|\"--inet=127.0.0.1:10023\"|\"--inet=127.0.0.1:10023 --delay=${POSTGREY_DELAY} --max-age=${POSTGREY_MAX_AGE} --auto-whitelist-clients=${POSTGREY_AUTO_WHITELIST_CLIENTS}\"|" \ - /etc/default/postgrey - - TEXT_FOUND=$(grep -c -i 'POSTGREY_TEXT' /etc/default/postgrey) - - if [[ ${TEXT_FOUND} -eq 0 ]] - then - printf 'POSTGREY_TEXT=\"%s\"\n\n' "${POSTGREY_TEXT}" >>/etc/default/postgrey - fi - - if [[ -f /tmp/docker-mailserver/whitelist_clients.local ]] - then - cp -f /tmp/docker-mailserver/whitelist_clients.local /etc/postgrey/whitelist_clients.local - fi - - if [[ -f /tmp/docker-mailserver/whitelist_recipients ]] - then - cp -f /tmp/docker-mailserver/whitelist_recipients /etc/postgrey/whitelist_recipients - fi -} - -function _setup_postfix_postscreen -{ - _log 'debug' 'Configuring Postscreen' - sed -i \ - -e "s|postscreen_dnsbl_action = enforce|postscreen_dnsbl_action = ${POSTSCREEN_ACTION}|" \ - -e "s|postscreen_greet_action = enforce|postscreen_greet_action = ${POSTSCREEN_ACTION}|" \ - -e "s|postscreen_bare_newline_action = enforce|postscreen_bare_newline_action = ${POSTSCREEN_ACTION}|" /etc/postfix/main.cf -} - -function _setup_postfix_sizelimits -{ - _log 'trace' "Configuring Postfix message size limit to '${POSTFIX_MESSAGE_SIZE_LIMIT}'" - postconf "message_size_limit = ${POSTFIX_MESSAGE_SIZE_LIMIT}" - - _log 'trace' "Configuring Postfix mailbox size limit to '${POSTFIX_MAILBOX_SIZE_LIMIT}'" - postconf "mailbox_size_limit = ${POSTFIX_MAILBOX_SIZE_LIMIT}" - - _log 'trace' "Configuring Postfix virtual mailbox size limit to '${POSTFIX_MAILBOX_SIZE_LIMIT}'" - postconf "virtual_mailbox_limit = ${POSTFIX_MAILBOX_SIZE_LIMIT}" -} - -function _setup_clamav_sizelimit -{ - _log 'trace' "Setting ClamAV message scan size limit to '${CLAMAV_MESSAGE_SIZE_LIMIT}'" - sedfile -i "s/^MaxFileSize.*/MaxFileSize ${CLAMAV_MESSAGE_SIZE_LIMIT}/" /etc/clamav/clamd.conf -} - -function _setup_postfix_smtputf8 -{ - _log 'trace' "Disabling Postfix's smtputf8 support" - postconf 'smtputf8_enable = no' -} - -function _setup_spoof_protection -{ - _log 'trace' 'Configuring spoof protection' - sed -i \ - 's|smtpd_sender_restrictions =|smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch,|' \ - /etc/postfix/main.cf - - if [[ ${ACCOUNT_PROVISIONER} == 'LDAP' ]] - then - if [[ -z ${LDAP_QUERY_FILTER_SENDERS} ]] - then - postconf 'smtpd_sender_login_maps = ldap:/etc/postfix/ldap-users.cf ldap:/etc/postfix/ldap-aliases.cf ldap:/etc/postfix/ldap-groups.cf' - else - postconf 'smtpd_sender_login_maps = ldap:/etc/postfix/ldap-senders.cf' - fi - else - if [[ -f /etc/postfix/regexp ]] - then - postconf 'smtpd_sender_login_maps = unionmap:{ texthash:/etc/postfix/virtual, hash:/etc/aliases, pcre:/etc/postfix/maps/sender_login_maps.pcre, pcre:/etc/postfix/regexp }' - else - postconf 'smtpd_sender_login_maps = texthash:/etc/postfix/virtual, hash:/etc/aliases, pcre:/etc/postfix/maps/sender_login_maps.pcre' - fi - fi -} - -function _setup_postfix_access_control -{ - _log 'trace' 'Configuring user access' - - if [[ -f /tmp/docker-mailserver/postfix-send-access.cf ]] - then - sed -i 's|smtpd_sender_restrictions =|smtpd_sender_restrictions = check_sender_access texthash:/tmp/docker-mailserver/postfix-send-access.cf,|' /etc/postfix/main.cf - fi - - if [[ -f /tmp/docker-mailserver/postfix-receive-access.cf ]] - then - sed -i 's|smtpd_recipient_restrictions =|smtpd_recipient_restrictions = check_recipient_access texthash:/tmp/docker-mailserver/postfix-receive-access.cf,|' /etc/postfix/main.cf - fi -} - -function _setup_postfix_sasl -{ - if [[ ${ENABLE_SASLAUTHD} -eq 1 ]] && [[ ! -f /etc/postfix/sasl/smtpd.conf ]] - then - cat >/etc/postfix/sasl/smtpd.conf << EOF -pwcheck_method: saslauthd -mech_list: plain login -EOF - fi - - if [[ ${ENABLE_SASLAUTHD} -eq 0 ]] && [[ ${SMTP_ONLY} -eq 1 ]] - then - sed -i -E \ - 's|^smtpd_sasl_auth_enable =.*|smtpd_sasl_auth_enable = no|g' \ - /etc/postfix/main.cf - sed -i -E \ - 's|^ -o smtpd_sasl_auth_enable=.*| -o smtpd_sasl_auth_enable=no|g' \ - /etc/postfix/master.cf - fi -} - -function _setup_saslauthd -{ - _log 'debug' 'Setting up SASLAUTHD' - - if [[ ! -f /etc/saslauthd.conf ]] - then - _log 'trace' 'Creating /etc/saslauthd.conf' - cat > /etc/saslauthd.conf << EOF -ldap_servers: ${SASLAUTHD_LDAP_SERVER} - -ldap_auth_method: ${SASLAUTHD_LDAP_AUTH_METHOD} -ldap_bind_dn: ${SASLAUTHD_LDAP_BIND_DN} -ldap_bind_pw: ${SASLAUTHD_LDAP_PASSWORD} - -ldap_search_base: ${SASLAUTHD_LDAP_SEARCH_BASE} -ldap_filter: ${SASLAUTHD_LDAP_FILTER} - -ldap_start_tls: ${SASLAUTHD_LDAP_START_TLS} -ldap_tls_check_peer: ${SASLAUTHD_LDAP_TLS_CHECK_PEER} - -${SASLAUTHD_LDAP_TLS_CACERT_FILE} -${SASLAUTHD_LDAP_TLS_CACERT_DIR} -${SASLAUTHD_LDAP_PASSWORD_ATTR} -${SASLAUTHD_LDAP_MECH} - -ldap_referrals: yes -log_level: 10 -EOF - fi - - sed -i \ - -e "/^[^#].*smtpd_sasl_type.*/s/^/#/g" \ - -e "/^[^#].*smtpd_sasl_path.*/s/^/#/g" \ - /etc/postfix/master.cf - - sed -i \ - -e "/smtpd_sasl_path =.*/d" \ - -e "/smtpd_sasl_type =.*/d" \ - -e "/dovecot_destination_recipient_limit =.*/d" \ - /etc/postfix/main.cf - - gpasswd -a postfix sasl >/dev/null -} - -function _setup_postfix_aliases -{ - _log 'debug' 'Setting up Postfix aliases' - _create_aliases -} - -function _setup_SRS -{ - _log 'debug' 'Setting up SRS' - - postconf 'sender_canonical_maps = tcp:localhost:10001' - postconf "sender_canonical_classes = ${SRS_SENDER_CLASSES}" - postconf 'recipient_canonical_maps = tcp:localhost:10002' - postconf 'recipient_canonical_classes = envelope_recipient,header_recipient' -} - -# Set up OpenDKIM & OpenDMARC. -# -# ## Attention -# -# The OpenDKIM milter must come before the OpenDMARC milter in Postfix's# -# `smtpd_milters` milters options. -function _setup_dkim_dmarc -{ - if [[ ${ENABLE_OPENDKIM} -eq 1 ]] - then - _log 'debug' 'Setting up DKIM' - - mkdir -p /etc/opendkim/keys/ - touch /etc/opendkim/{SigningTable,TrustedHosts,KeyTable} - - _log 'trace' "Adding OpenDKIM to Postfix's milters" - postconf 'dkim_milter = inet:localhost:8891' - # shellcheck disable=SC2016 - sed -i -E \ - -e 's|^(smtpd_milters =.*)|\1 \$dkim_milter|g' \ - -e 's|^(non_smtpd_milters =.*)|\1 \$dkim_milter|g' \ - /etc/postfix/main.cf - - # check if any keys are available - if [[ -e /tmp/docker-mailserver/opendkim/KeyTable ]] - then - cp -a /tmp/docker-mailserver/opendkim/* /etc/opendkim/ - _log 'trace' "DKIM keys added for: $(find /etc/opendkim/keys/ -maxdepth 1 -type f -printf '%f ')" - chown -R opendkim:opendkim /etc/opendkim/ - chmod -R 0700 /etc/opendkim/keys/ - else - _log 'debug' 'OpenDKIM enabled but no DKIM key(s) provided' - fi - - # setup nameservers parameter from /etc/resolv.conf if not defined - if ! grep -q '^Nameservers' /etc/opendkim.conf - then - local NAMESERVER_IPS - NAMESERVER_IPS=$(grep '^nameserver' /etc/resolv.conf | awk -F " " '{print $2}' | paste -sd ',' -) - echo "Nameservers ${NAMESERVER_IPS}" >>/etc/opendkim.conf - _log 'trace' "Nameservers added to '/etc/opendkim.conf'" - fi - fi - - if [[ ${ENABLE_OPENDMARC} -eq 1 ]] - then - # TODO when disabling SPF is possible, add a check whether DKIM and SPF is disabled - # for DMARC to work, you should have at least one enabled - # (see RFC 7489 https://www.rfc-editor.org/rfc/rfc7489#page-24) - _log 'trace' "Adding OpenDMARC to Postfix's milters" - postconf 'dmarc_milter = inet:localhost:8893' - # Make sure to append the OpenDMARC milter _after_ the OpenDKIM milter! - # shellcheck disable=SC2016 - sed -i -E 's|^(smtpd_milters =.*)|\1 \$dmarc_milter|g' /etc/postfix/main.cf - fi -} - -function _setup_postfix_vhost -{ - _log 'debug' 'Setting up Postfix vhost' - _create_postfix_vhost -} - -function _setup_postfix_inet_protocols -{ - _log 'trace' 'Setting up POSTFIX_INET_PROTOCOLS option' - postconf "inet_protocols = ${POSTFIX_INET_PROTOCOLS}" -} - -function _setup_dovecot_inet_protocols -{ - local PROTOCOL - - _log 'trace' 'Setting up DOVECOT_INET_PROTOCOLS option' - - # https://dovecot.org/doc/dovecot-example.conf - if [[ ${DOVECOT_INET_PROTOCOLS} == "ipv4" ]] - then - PROTOCOL='*' # IPv4 only - elif [[ ${DOVECOT_INET_PROTOCOLS} == "ipv6" ]] - then - PROTOCOL='[::]' # IPv6 only - else - # Unknown value, panic. - dms_panic__invalid_value 'DOVECOT_INET_PROTOCOLS' "${DOVECOT_INET_PROTOCOLS}" - fi - - sedfile -i "s|^#listen =.*|listen = ${PROTOCOL}|g" /etc/dovecot/dovecot.conf -} - -function _setup_docker_permit -{ - _log 'debug' 'Setting up PERMIT_DOCKER option' - - local CONTAINER_IP CONTAINER_NETWORK - - unset CONTAINER_NETWORKS - declare -a CONTAINER_NETWORKS - - CONTAINER_IP=$(ip addr show "${NETWORK_INTERFACE}" | \ - grep 'inet ' | sed 's|[^0-9\.\/]*||g' | cut -d '/' -f 1) - CONTAINER_NETWORK=$(echo "${CONTAINER_IP}" | cut -d '.' -f1-2).0.0 - - if [[ -z ${CONTAINER_IP} ]] - then - _log 'error' 'Detecting the container IP address failed' - dms_panic__misconfigured 'NETWORK_INTERFACE' 'Network Setup [docker_permit]' - fi - - while read -r IP - do - CONTAINER_NETWORKS+=("${IP}") - done < <(ip -o -4 addr show type veth | grep -E -o '[0-9\.]+/[0-9]+') - - function __clear_postfix_mynetworks - { - _log 'trace' "Clearing Postfix's 'mynetworks'" - postconf "mynetworks =" - } - - function __add_to_postfix_mynetworks - { - local NETWORK_TYPE=$1 - local NETWORK=$2 - - _log 'trace' "Adding ${NETWORK_TYPE} (${NETWORK}) to Postfix 'main.cf:mynetworks'" - _adjust_mtime_for_postfix_maincf - postconf "$(postconf | grep '^mynetworks =') ${NETWORK}" - [[ ${ENABLE_OPENDMARC} -eq 1 ]] && echo "${NETWORK}" >>/etc/opendmarc/ignore.hosts - [[ ${ENABLE_OPENDKIM} -eq 1 ]] && echo "${NETWORK}" >>/etc/opendkim/TrustedHosts - } - - case "${PERMIT_DOCKER}" in - ( 'none' ) - __clear_postfix_mynetworks - ;; - - ( 'connected-networks' ) - for CONTAINER_NETWORK in "${CONTAINER_NETWORKS[@]}" - do - CONTAINER_NETWORK=$(_sanitize_ipv4_to_subnet_cidr "${CONTAINER_NETWORK}") - __add_to_postfix_mynetworks 'Docker Network' "${CONTAINER_NETWORK}" - done - ;; - - ( 'container' ) - __add_to_postfix_mynetworks 'Container IP address' "${CONTAINER_IP}/32" - ;; - - ( 'host' ) - __add_to_postfix_mynetworks 'Host Network' "${CONTAINER_NETWORK}/16" - ;; - - ( 'network' ) - __add_to_postfix_mynetworks 'Docker IPv4 Subnet' '172.16.0.0/12' - ;; - - ( * ) - _log 'warn' "Invalid value for PERMIT_DOCKER: '${PERMIT_DOCKER}'" - __clear_postfix_mynetworks - ;; - - esac -} - -function _setup_postfix_virtual_transport -{ - _log 'trace' "Changing Postfix virtual transport to '${POSTFIX_DAGENT}'" - # Default value in main.cf should be 'lmtp:unix:/var/run/dovecot/lmtp' - postconf "virtual_transport = ${POSTFIX_DAGENT}" -} - -function _setup_postfix_override_configuration -{ - _log 'debug' 'Overriding / adjusting Postfix configuration with user-supplied values' - - if [[ -f /tmp/docker-mailserver/postfix-main.cf ]] - then - cat /tmp/docker-mailserver/postfix-main.cf >>/etc/postfix/main.cf - _adjust_mtime_for_postfix_maincf - - # do not directly output to 'main.cf' as this causes a read-write-conflict - postconf -n >/tmp/postfix-main-new.cf 2>/dev/null - - mv /tmp/postfix-main-new.cf /etc/postfix/main.cf - _adjust_mtime_for_postfix_maincf - _log 'trace' "Adjusted '/etc/postfix/main.cf' according to '/tmp/docker-mailserver/postfix-main.cf'" - else - _log 'trace' "No extra Postfix settings loaded because optional '/tmp/docker-mailserver/postfix-main.cf' was not provided" - fi - - if [[ -f /tmp/docker-mailserver/postfix-master.cf ]] - then - while read -r LINE - do - if [[ ${LINE} =~ ^[0-9a-z] ]] - then - postconf -P "${LINE}" - fi - done < /tmp/docker-mailserver/postfix-master.cf - _log 'trace' "Adjusted '/etc/postfix/master.cf' according to '/tmp/docker-mailserver/postfix-master.cf'" - else - _log 'trace' "No extra Postfix settings loaded because optional '/tmp/docker-mailserver/postfix-master.cf' was not provided" - fi -} - -function _setup_postfix_relay_hosts -{ - _setup_relayhost -} - -function _setup_postfix_dhparam -{ - _setup_dhparam 'Postfix' '/etc/postfix/dhparams.pem' -} - -function _setup_dovecot_dhparam -{ - _setup_dhparam 'Dovecot' '/etc/dovecot/dh.pem' -} - -function _setup_dhparam -{ - local DH_SERVICE=$1 - local DH_DEST=$2 - local DH_CUSTOM='/tmp/docker-mailserver/dhparams.pem' - - _log 'debug' "Setting up ${DH_SERVICE} dhparam" - - if [[ -f ${DH_CUSTOM} ]] - then # use custom supplied dh params (assumes they're probably insecure) - _log 'trace' "${DH_SERVICE} will use custom provided DH paramters" - _log 'warn' "Using self-generated dhparams is considered insecure - unless you know what you are doing, please remove '${DH_CUSTOM}'" - - cp -f "${DH_CUSTOM}" "${DH_DEST}" - else # use official standardized dh params (provided via Dockerfile) - _log 'trace' "${DH_SERVICE} will use official standardized DH parameters (ffdhe4096)." - fi -} - -function _setup_security_stack -{ - _log 'debug' 'Setting up Security Stack' - - # recreate auto-generated file - local DMS_AMAVIS_FILE=/etc/amavis/conf.d/61-dms_auto_generated - - echo "# WARNING: this file is auto-generated." >"${DMS_AMAVIS_FILE}" - echo "use strict;" >>"${DMS_AMAVIS_FILE}" - - # SpamAssassin - if [[ ${ENABLE_SPAMASSASSIN} -eq 0 ]] - then - _log 'debug' 'SpamAssassin is disabled' - echo "@bypass_spam_checks_maps = (1);" >>"${DMS_AMAVIS_FILE}" - elif [[ ${ENABLE_SPAMASSASSIN} -eq 1 ]] - then - _log 'debug' 'Enabling and configuring SpamAssassin' - - # shellcheck disable=SC2016 - sed -i -r 's|^\$sa_tag_level_deflt (.*);|\$sa_tag_level_deflt = '"${SA_TAG}"';|g' /etc/amavis/conf.d/20-debian_defaults - - # shellcheck disable=SC2016 - sed -i -r 's|^\$sa_tag2_level_deflt (.*);|\$sa_tag2_level_deflt = '"${SA_TAG2}"';|g' /etc/amavis/conf.d/20-debian_defaults - - # shellcheck disable=SC2016 - sed -i -r 's|^\$sa_kill_level_deflt (.*);|\$sa_kill_level_deflt = '"${SA_KILL}"';|g' /etc/amavis/conf.d/20-debian_defaults - - if [[ ${SA_SPAM_SUBJECT} == 'undef' ]] - then - # shellcheck disable=SC2016 - sed -i -r 's|^\$sa_spam_subject_tag (.*);|\$sa_spam_subject_tag = undef;|g' /etc/amavis/conf.d/20-debian_defaults - else - # shellcheck disable=SC2016 - sed -i -r 's|^\$sa_spam_subject_tag (.*);|\$sa_spam_subject_tag = '"'${SA_SPAM_SUBJECT}'"';|g' /etc/amavis/conf.d/20-debian_defaults - fi - - # activate short circuits when SA BAYES is certain it has spam or ham. - if [[ ${SA_SHORTCIRCUIT_BAYES_SPAM} -eq 1 ]] - then - # automatically activate the Shortcircuit Plugin - sed -i -r 's|^# loadplugin Mail::SpamAssassin::Plugin::Shortcircuit|loadplugin Mail::SpamAssassin::Plugin::Shortcircuit|g' /etc/spamassassin/v320.pre - sed -i -r 's|^# shortcircuit BAYES_99|shortcircuit BAYES_99|g' /etc/spamassassin/local.cf - fi - - if [[ ${SA_SHORTCIRCUIT_BAYES_HAM} -eq 1 ]] - then - # automatically activate the Shortcircuit Plugin - sed -i -r 's|^# loadplugin Mail::SpamAssassin::Plugin::Shortcircuit|loadplugin Mail::SpamAssassin::Plugin::Shortcircuit|g' /etc/spamassassin/v320.pre - sed -i -r 's|^# shortcircuit BAYES_00|shortcircuit BAYES_00|g' /etc/spamassassin/local.cf - fi - - if [[ -e /tmp/docker-mailserver/spamassassin-rules.cf ]] - then - cp /tmp/docker-mailserver/spamassassin-rules.cf /etc/spamassassin/ - fi - - if [[ ${SPAMASSASSIN_SPAM_TO_INBOX} -eq 1 ]] - then - _log 'trace' 'Configuring Spamassassin/Amavis to send SPAM to inbox' - - sed -i "s|\$final_spam_destiny.*=.*$|\$final_spam_destiny = D_PASS;|g" /etc/amavis/conf.d/49-docker-mailserver - sed -i "s|\$final_bad_header_destiny.*=.*$|\$final_bad_header_destiny = D_PASS;|g" /etc/amavis/conf.d/49-docker-mailserver - else - _log 'trace' 'Configuring Spamassassin/Amavis to bounce SPAM' - - sed -i "s|\$final_spam_destiny.*=.*$|\$final_spam_destiny = D_BOUNCE;|g" /etc/amavis/conf.d/49-docker-mailserver - sed -i "s|\$final_bad_header_destiny.*=.*$|\$final_bad_header_destiny = D_BOUNCE;|g" /etc/amavis/conf.d/49-docker-mailserver - fi - - if [[ ${ENABLE_SPAMASSASSIN_KAM} -eq 1 ]] - then - _log 'trace' 'Configuring Spamassassin KAM' - local SPAMASSASSIN_KAM_CRON_FILE=/etc/cron.daily/spamassassin_kam - - sa-update --import /etc/spamassassin/kam/kam.sa-channels.mcgrail.com.key - - cat >"${SPAMASSASSIN_KAM_CRON_FILE}" <<"EOF" -#!/bin/bash - -RESULT=$(sa-update --gpgkey 24C063D8 --channel kam.sa-channels.mcgrail.com 2>&1) -EXIT_CODE=${?} - -# see https://spamassassin.apache.org/full/3.1.x/doc/sa-update.html#exit_codes -if [[ ${EXIT_CODE} -ge 4 ]] -then - echo -e "Updating SpamAssassin KAM failed:\n${RESULT}\n" >&2 - exit 1 -fi - -exit 0 - -EOF - - chmod +x "${SPAMASSASSIN_KAM_CRON_FILE}" - fi - fi - - # ClamAV - if [[ ${ENABLE_CLAMAV} -eq 0 ]] - then - _log 'debug' 'ClamAV is disabled' - echo '@bypass_virus_checks_maps = (1);' >>"${DMS_AMAVIS_FILE}" - elif [[ ${ENABLE_CLAMAV} -eq 1 ]] - then - _log 'debug' 'Enabling ClamAV' - fi - - echo '1; # ensure a defined return' >>"${DMS_AMAVIS_FILE}" - chmod 444 "${DMS_AMAVIS_FILE}" - - # Fail2ban - if [[ ${ENABLE_FAIL2BAN} -eq 1 ]] - then - _log 'debug' 'Enabling Fail2Ban' - - if [[ -e /tmp/docker-mailserver/fail2ban-fail2ban.cf ]] - then - cp /tmp/docker-mailserver/fail2ban-fail2ban.cf /etc/fail2ban/fail2ban.local - fi - - if [[ -e /tmp/docker-mailserver/fail2ban-jail.cf ]] - then - cp /tmp/docker-mailserver/fail2ban-jail.cf /etc/fail2ban/jail.d/user-jail.local - fi - else - # disable logrotate config for fail2ban if not enabled - rm -f /etc/logrotate.d/fail2ban - fi - - # fix cron.daily for spamassassin - sed -i \ - 's|invoke-rc.d spamassassin reload|/etc/init\.d/spamassassin reload|g' \ - /etc/cron.daily/spamassassin - - # Amavis - if [[ ${ENABLE_AMAVIS} -eq 1 ]] - then - _log 'debug' 'Enabling Amavis' - if [[ -f /tmp/docker-mailserver/amavis.cf ]] - then - cp /tmp/docker-mailserver/amavis.cf /etc/amavis/conf.d/50-user - fi - - sed -i -E \ - "s|(log_level).*|\1 = ${AMAVIS_LOGLEVEL};|g" \ - /etc/amavis/conf.d/49-docker-mailserver - fi -} - -function _setup_logrotate -{ - _log 'debug' 'Setting up logrotate' - - LOGROTATE='/var/log/mail/mail.log\n{\n compress\n copytruncate\n delaycompress\n' - - case "${LOGROTATE_INTERVAL}" in - ( 'daily' ) - _log 'trace' 'Setting postfix logrotate interval to daily' - LOGROTATE="${LOGROTATE} rotate 4\n daily\n" - ;; - - ( 'weekly' ) - _log 'trace' 'Setting postfix logrotate interval to weekly' - LOGROTATE="${LOGROTATE} rotate 4\n weekly\n" - ;; - - ( 'monthly' ) - _log 'trace' 'Setting postfix logrotate interval to monthly' - LOGROTATE="${LOGROTATE} rotate 4\n monthly\n" - ;; - - ( * ) - _log 'warn' 'LOGROTATE_INTERVAL not found in _setup_logrotate' - ;; - - esac - - echo -e "${LOGROTATE}}" >/etc/logrotate.d/maillog -} - -function _setup_mail_summary -{ - local ENABLED_MESSAGE - ENABLED_MESSAGE="Enabling Postfix log summary reports with recipient '${PFLOGSUMM_RECIPIENT}'" - - case "${PFLOGSUMM_TRIGGER}" in - ( 'daily_cron' ) - _log 'debug' "${ENABLED_MESSAGE}" - _log 'trace' 'Creating daily cron job for pflogsumm report' - - cat >/etc/cron.daily/postfix-summary << EOF -#!/bin/bash - -/usr/local/bin/report-pflogsumm-yesterday ${HOSTNAME} ${PFLOGSUMM_RECIPIENT} ${PFLOGSUMM_SENDER} -EOF - - chmod +x /etc/cron.daily/postfix-summary - ;; - - ( 'logrotate' ) - _log 'debug' "${ENABLED_MESSAGE}" - _log 'trace' 'Add postrotate action for pflogsumm report' - sed -i \ - "s|}| postrotate\n /usr/local/bin/postfix-summary ${HOSTNAME} ${PFLOGSUMM_RECIPIENT} ${PFLOGSUMM_SENDER}\n endscript\n}\n|" \ - /etc/logrotate.d/maillog - ;; - - ( 'none' ) - _log 'debug' 'Postfix log summary reports disabled' - ;; - - ( * ) - _log 'warn' "Invalid value for PFLOGSUMM_TRIGGER: '${PFLOGSUMM_TRIGGER}'" - ;; - - esac -} - -function _setup_logwatch -{ - echo 'LogFile = /var/log/mail/freshclam.log' >>/etc/logwatch/conf/logfiles/clam-update.conf - echo "MailFrom = ${LOGWATCH_SENDER}" >>/etc/logwatch/conf/logwatch.conf - echo "Mailer = \"sendmail -t -f ${LOGWATCH_SENDER}\"" >>/etc/logwatch/conf/logwatch.conf - - case "${LOGWATCH_INTERVAL}" in - ( 'daily' | 'weekly' ) - _log 'debug' "Enabling logwatch reports with recipient '${LOGWATCH_RECIPIENT}'" - _log 'trace' "Creating ${LOGWATCH_INTERVAL} cron job for logwatch reports" - - local LOGWATCH_FILE INTERVAL - - LOGWATCH_FILE="/etc/cron.${LOGWATCH_INTERVAL}/logwatch" - INTERVAL='--range Yesterday' - - if [[ ${LOGWATCH_INTERVAL} == 'weekly' ]] - then - INTERVAL="--range 'between -7 days and -1 days'" - fi - - cat >"${LOGWATCH_FILE}" << EOF -#!/bin/bash - -/usr/sbin/logwatch ${INTERVAL} --hostname ${HOSTNAME} --mailto ${LOGWATCH_RECIPIENT} -EOF - chmod 744 "${LOGWATCH_FILE}" - ;; - - ( 'none' ) - _log 'debug' 'Logwatch reports disabled.' - ;; - - ( * ) - _log 'warn' "Invalid value for LOGWATCH_INTERVAL: '${LOGWATCH_INTERVAL}'" - ;; - - esac -} - -function _setup_user_patches +function _setup_run_user_patches { local USER_PATCHES='/tmp/docker-mailserver/user-patches.sh' @@ -1116,127 +89,6 @@ function _setup_user_patches fi } -function _setup_fail2ban -{ - _log 'debug' 'Setting up Fail2Ban' - - if [[ ${FAIL2BAN_BLOCKTYPE} != 'reject' ]] - then - echo -e '[Init]\nblocktype = drop' >/etc/fail2ban/action.d/nftables-common.local - fi - - echo '[Definition]' >/etc/fail2ban/filter.d/custom.conf -} - -function _setup_dnsbl_disable -{ - _log 'debug' 'Disabling postscreen DNS block lists' - postconf 'postscreen_dnsbl_action = ignore' - postconf 'postscreen_dnsbl_sites = ' -} - -function _setup_fetchmail -{ - _log 'trace' 'Preparing Fetchmail configuration' - - local CONFIGURATION FETCHMAILRC - - CONFIGURATION='/tmp/docker-mailserver/fetchmail.cf' - FETCHMAILRC='/etc/fetchmailrc' - - if [[ -f ${CONFIGURATION} ]] - then - cat /etc/fetchmailrc_general "${CONFIGURATION}" >"${FETCHMAILRC}" - else - cat /etc/fetchmailrc_general >"${FETCHMAILRC}" - fi - - chmod 700 "${FETCHMAILRC}" - chown fetchmail:root "${FETCHMAILRC}" -} - -function _setup_fetchmail_parallel -{ - _log 'trace' 'Setting up Fetchmail parallel' - mkdir /etc/fetchmailrc.d/ - - # Split the content of /etc/fetchmailrc into - # smaller fetchmailrc files per server [poll] entries. Each - # separate fetchmailrc file is stored in /etc/fetchmailrc.d - # - # The sole purpose for this is to work around what is known - # as the Fetchmail IMAP idle issue. - function _fetchmailrc_split - { - local FETCHMAILRC='/etc/fetchmailrc' - local FETCHMAILRCD='/etc/fetchmailrc.d' - local DEFAULT_FILE="${FETCHMAILRCD}/defaults" - - if [[ ! -r ${FETCHMAILRC} ]] - then - _log 'warn' "File '${FETCHMAILRC}' not found" - return 1 - fi - - if [[ ! -d ${FETCHMAILRCD} ]] - then - if ! mkdir "${FETCHMAILRCD}" - then - _log 'warn' "Unable to create folder '${FETCHMAILRCD}'" - return 1 - fi - fi - - local COUNTER=0 SERVER=0 - while read -r LINE - do - if [[ ${LINE} =~ poll ]] - then - # If we read "poll" then we reached a new server definition - # We need to create a new file with fetchmail defaults from - # /etc/fetcmailrc - COUNTER=$(( COUNTER + 1 )) - SERVER=1 - cat "${DEFAULT_FILE}" >"${FETCHMAILRCD}/fetchmail-${COUNTER}.rc" - echo "${LINE}" >>"${FETCHMAILRCD}/fetchmail-${COUNTER}.rc" - elif [[ ${SERVER} -eq 0 ]] - then - # We have not yet found "poll". Let's assume we are still reading - # the default settings from /etc/fetchmailrc file - echo "${LINE}" >>"${DEFAULT_FILE}" - else - # Just the server settings that need to be added to the specific rc.d file - echo "${LINE}" >>"${FETCHMAILRCD}/fetchmail-${COUNTER}.rc" - fi - done < <(_get_valid_lines_from_file "${FETCHMAILRC}") - - rm "${DEFAULT_FILE}" - } - - _fetchmailrc_split - - local COUNTER=0 - for RC in /etc/fetchmailrc.d/fetchmail-*.rc - do - COUNTER=$(( COUNTER + 1 )) - cat >"/etc/supervisor/conf.d/fetchmail-${COUNTER}.conf" << EOF -[program:fetchmail-${COUNTER}] -startsecs=0 -autostart=false -autorestart=true -stdout_logfile=/var/log/supervisor/%(program_name)s.log -stderr_logfile=/var/log/supervisor/%(program_name)s.log -user=fetchmail -command=/usr/bin/fetchmail -f ${RC} -v --nodetach --daemon %(ENV_FETCHMAIL_POLL)s -i /var/lib/fetchmail/.fetchmail-UIDL-cache --pidfile /var/run/fetchmail/%(program_name)s.pid -EOF - chmod 700 "${RC}" - chown fetchmail:root "${RC}" - done - - supervisorctl reread - supervisorctl update -} - function _setup_timezone { [[ -n ${TZ} ]] || return 0 diff --git a/target/scripts/startup/setup.d/dmarc_dkim_spf.sh b/target/scripts/startup/setup.d/dmarc_dkim_spf.sh new file mode 100644 index 00000000..77460398 --- /dev/null +++ b/target/scripts/startup/setup.d/dmarc_dkim_spf.sh @@ -0,0 +1,68 @@ +#!/bin/bash + + +# Set up OpenDKIM & OpenDMARC. +# +# ## Attention +# +# The OpenDKIM milter must come before the OpenDMARC milter in Postfix's# +# `smtpd_milters` milters options. +function _setup_dkim_dmarc +{ + if [[ ${ENABLE_OPENDKIM} -eq 1 ]] + then + _log 'debug' 'Setting up DKIM' + + mkdir -p /etc/opendkim/keys/ + touch /etc/opendkim/{SigningTable,TrustedHosts,KeyTable} + + _log 'trace' "Adding OpenDKIM to Postfix's milters" + postconf 'dkim_milter = inet:localhost:8891' + # shellcheck disable=SC2016 + sed -i -E \ + -e 's|^(smtpd_milters =.*)|\1 \$dkim_milter|g' \ + -e 's|^(non_smtpd_milters =.*)|\1 \$dkim_milter|g' \ + /etc/postfix/main.cf + + # check if any keys are available + if [[ -e /tmp/docker-mailserver/opendkim/KeyTable ]] + then + cp -a /tmp/docker-mailserver/opendkim/* /etc/opendkim/ + _log 'trace' "DKIM keys added for: $(find /etc/opendkim/keys/ -maxdepth 1 -type f -printf '%f ')" + chown -R opendkim:opendkim /etc/opendkim/ + chmod -R 0700 /etc/opendkim/keys/ + else + _log 'debug' 'OpenDKIM enabled but no DKIM key(s) provided' + fi + + # setup nameservers parameter from /etc/resolv.conf if not defined + if ! grep -q '^Nameservers' /etc/opendkim.conf + then + local NAMESERVER_IPS + NAMESERVER_IPS=$(grep '^nameserver' /etc/resolv.conf | awk -F " " '{print $2}' | paste -sd ',' -) + echo "Nameservers ${NAMESERVER_IPS}" >>/etc/opendkim.conf + _log 'trace' "Nameservers added to '/etc/opendkim.conf'" + fi + fi + + if [[ ${ENABLE_OPENDMARC} -eq 1 ]] + then + # TODO when disabling SPF is possible, add a check whether DKIM and SPF is disabled + # for DMARC to work, you should have at least one enabled + # (see RFC 7489 https://www.rfc-editor.org/rfc/rfc7489#page-24) + _log 'trace' "Adding OpenDMARC to Postfix's milters" + postconf 'dmarc_milter = inet:localhost:8893' + # Make sure to append the OpenDMARC milter _after_ the OpenDKIM milter! + # shellcheck disable=SC2016 + sed -i -E 's|^(smtpd_milters =.*)|\1 \$dmarc_milter|g' /etc/postfix/main.cf + fi +} + +function _setup_dmarc_hostname +{ + _log 'debug' 'Setting up DMARC' + sed -i -e \ + "s|^AuthservID.*$|AuthservID ${HOSTNAME}|g" \ + -e "s|^TrustedAuthservIDs.*$|TrustedAuthservIDs ${HOSTNAME}|g" \ + /etc/opendmarc.conf +} diff --git a/target/scripts/startup/setup.d/dovecot.sh b/target/scripts/startup/setup.d/dovecot.sh new file mode 100644 index 00000000..175ba370 --- /dev/null +++ b/target/scripts/startup/setup.d/dovecot.sh @@ -0,0 +1,215 @@ +#!/bin/bash + +function _setup_dovecot +{ + _log 'debug' 'Setting up Dovecot' + + cp -a /usr/share/dovecot/protocols.d /etc/dovecot/ + # disable pop3 (it will be eventually enabled later in the script, if requested) + mv /etc/dovecot/protocols.d/pop3d.protocol /etc/dovecot/protocols.d/pop3d.protocol.disab + mv /etc/dovecot/protocols.d/managesieved.protocol /etc/dovecot/protocols.d/managesieved.protocol.disab + sed -i -e 's|#ssl = yes|ssl = yes|g' /etc/dovecot/conf.d/10-master.conf + sed -i -e 's|#port = 993|port = 993|g' /etc/dovecot/conf.d/10-master.conf + sed -i -e 's|#port = 995|port = 995|g' /etc/dovecot/conf.d/10-master.conf + sed -i -e 's|#ssl = yes|ssl = required|g' /etc/dovecot/conf.d/10-ssl.conf + sed -i 's|^postmaster_address = .*$|postmaster_address = '"${POSTMASTER_ADDRESS}"'|g' /etc/dovecot/conf.d/15-lda.conf + + if ! grep -q -E '^stats_writer_socket_path=' /etc/dovecot/dovecot.conf + then + printf '\n%s\n' 'stats_writer_socket_path=' >>/etc/dovecot/dovecot.conf + fi + + # set mail_location according to mailbox format + case "${DOVECOT_MAILBOX_FORMAT}" in + + ( 'sdbox' | 'mdbox' ) + _log 'trace' "Dovecot ${DOVECOT_MAILBOX_FORMAT} format configured" + sed -i -e \ + "s|^mail_location = .*$|mail_location = ${DOVECOT_MAILBOX_FORMAT}:\/var\/mail\/%d\/%n|g" \ + /etc/dovecot/conf.d/10-mail.conf + + _log 'trace' 'Enabling cron job for dbox purge' + mv /etc/cron.d/dovecot-purge.disabled /etc/cron.d/dovecot-purge + chmod 644 /etc/cron.d/dovecot-purge + ;; + + ( * ) + _log 'trace' 'Dovecot default format (maildir) configured' + sed -i -e 's|^mail_location = .*$|mail_location = maildir:\/var\/mail\/%d\/%n|g' /etc/dovecot/conf.d/10-mail.conf + ;; + + esac + + # enable Managesieve service by setting the symlink + # to the configuration file Dovecot will actually find + if [[ ${ENABLE_MANAGESIEVE} -eq 1 ]] + then + _log 'trace' 'Sieve management enabled' + mv /etc/dovecot/protocols.d/managesieved.protocol.disab /etc/dovecot/protocols.d/managesieved.protocol + fi + + # copy pipe and filter programs, if any + rm -f /usr/lib/dovecot/sieve-filter/* + rm -f /usr/lib/dovecot/sieve-pipe/* + [[ -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/ + + # create global sieve directories + mkdir -p /usr/lib/dovecot/sieve-global/before + mkdir -p /usr/lib/dovecot/sieve-global/after + + if [[ -f /tmp/docker-mailserver/before.dovecot.sieve ]] + then + cp /tmp/docker-mailserver/before.dovecot.sieve /usr/lib/dovecot/sieve-global/before/50-before.dovecot.sieve + sievec /usr/lib/dovecot/sieve-global/before/50-before.dovecot.sieve + else + rm -f /usr/lib/dovecot/sieve-global/before/50-before.dovecot.sieve /usr/lib/dovecot/sieve-global/before/50-before.dovecot.svbin + fi + + if [[ -f /tmp/docker-mailserver/after.dovecot.sieve ]] + then + cp /tmp/docker-mailserver/after.dovecot.sieve /usr/lib/dovecot/sieve-global/after/50-after.dovecot.sieve + sievec /usr/lib/dovecot/sieve-global/after/50-after.dovecot.sieve + else + rm -f /usr/lib/dovecot/sieve-global/after/50-after.dovecot.sieve /usr/lib/dovecot/sieve-global/after/50-after.dovecot.svbin + fi + + # sieve will move spams to .Junk folder when SPAMASSASSIN_SPAM_TO_INBOX=1 and MOVE_SPAM_TO_JUNK=1 + if [[ ${SPAMASSASSIN_SPAM_TO_INBOX} -eq 1 ]] && [[ ${MOVE_SPAM_TO_JUNK} -eq 1 ]] + then + _log 'debug' 'Spam messages will be moved to the Junk folder' + cp /etc/dovecot/sieve/before/60-spam.sieve /usr/lib/dovecot/sieve-global/before/ + sievec /usr/lib/dovecot/sieve-global/before/60-spam.sieve + else + rm -f /usr/lib/dovecot/sieve-global/before/60-spam.sieve /usr/lib/dovecot/sieve-global/before/60-spam.svbin + fi + + chown docker:docker -R /usr/lib/dovecot/sieve* + chmod 550 -R /usr/lib/dovecot/sieve* + chmod -f +x /usr/lib/dovecot/sieve-pipe/* +} + + +function _setup_dovecot_quota +{ + _log 'debug' 'Setting up Dovecot quota' + + # Dovecot quota is disabled when using LDAP or SMTP_ONLY or when explicitly disabled. + if [[ ${ACCOUNT_PROVISIONER} != 'FILE' ]] || [[ ${SMTP_ONLY} -eq 1 ]] || [[ ${ENABLE_QUOTAS} -eq 0 ]] + then + # disable dovecot quota in docevot confs + if [[ -f /etc/dovecot/conf.d/90-quota.conf ]] + then + mv /etc/dovecot/conf.d/90-quota.conf /etc/dovecot/conf.d/90-quota.conf.disab + sed -i \ + "s|mail_plugins = \$mail_plugins quota|mail_plugins = \$mail_plugins|g" \ + /etc/dovecot/conf.d/10-mail.conf + sed -i \ + "s|mail_plugins = \$mail_plugins imap_quota|mail_plugins = \$mail_plugins|g" \ + /etc/dovecot/conf.d/20-imap.conf + fi + + # disable quota policy check in postfix + sed -i "s|check_policy_service inet:localhost:65265||g" /etc/postfix/main.cf + else + if [[ -f /etc/dovecot/conf.d/90-quota.conf.disab ]] + then + mv /etc/dovecot/conf.d/90-quota.conf.disab /etc/dovecot/conf.d/90-quota.conf + sed -i \ + "s|mail_plugins = \$mail_plugins|mail_plugins = \$mail_plugins quota|g" \ + /etc/dovecot/conf.d/10-mail.conf + sed -i \ + "s|mail_plugins = \$mail_plugins|mail_plugins = \$mail_plugins imap_quota|g" \ + /etc/dovecot/conf.d/20-imap.conf + fi + + local MESSAGE_SIZE_LIMIT_MB=$((POSTFIX_MESSAGE_SIZE_LIMIT / 1000000)) + local MAILBOX_LIMIT_MB=$((POSTFIX_MAILBOX_SIZE_LIMIT / 1000000)) + + sed -i \ + "s|quota_max_mail_size =.*|quota_max_mail_size = ${MESSAGE_SIZE_LIMIT_MB}$([[ ${MESSAGE_SIZE_LIMIT_MB} -eq 0 ]] && echo "" || echo "M")|g" \ + /etc/dovecot/conf.d/90-quota.conf + + sed -i \ + "s|quota_rule = \*:storage=.*|quota_rule = *:storage=${MAILBOX_LIMIT_MB}$([[ ${MAILBOX_LIMIT_MB} -eq 0 ]] && echo "" || echo "M")|g" \ + /etc/dovecot/conf.d/90-quota.conf + + if [[ -d /tmp/docker-mailserver ]] && [[ ! -f /tmp/docker-mailserver/dovecot-quotas.cf ]] + then + _log 'trace' "'/tmp/docker-mailserver/dovecot-quotas.cf' is not provided. Using default quotas." + : >/tmp/docker-mailserver/dovecot-quotas.cf + fi + + # enable quota policy check in postfix + sed -i -E \ + "s|(reject_unknown_recipient_domain)|\1, check_policy_service inet:localhost:65265|g" \ + /etc/postfix/main.cf + fi +} + +function _setup_dovecot_local_user +{ + [[ ${SMTP_ONLY} -eq 1 ]] && return 0 + [[ ${ACCOUNT_PROVISIONER} == 'FILE' ]] || return 0 + + _log 'debug' 'Setting up Dovecot Local User' + + if [[ ! -f /tmp/docker-mailserver/postfix-accounts.cf ]] + then + _log 'trace' "No mail accounts to create - '/tmp/docker-mailserver/postfix-accounts.cf' is missing" + fi + + function __wait_until_an_account_is_added_or_shutdown + { + local SLEEP_PERIOD='10' + + for (( COUNTER = 11 ; COUNTER >= 0 ; COUNTER-- )) + do + if [[ $(grep -cE '.+@.+\|' /tmp/docker-mailserver/postfix-accounts.cf 2>/dev/null || printf '%s' '0') -ge 1 ]] + then + return 0 + else + _log 'warn' "You need at least one mail account to start Dovecot ($(( ( COUNTER + 1 ) * SLEEP_PERIOD ))s left for account creation before shutdown)" + sleep "${SLEEP_PERIOD}" + fi + done + + _shutdown 'No accounts provided - Dovecot could not be started' + } + + __wait_until_an_account_is_added_or_shutdown + + _create_accounts +} + +function _setup_dovecot_inet_protocols +{ + local PROTOCOL + + _log 'trace' 'Setting up DOVECOT_INET_PROTOCOLS option' + + # https://dovecot.org/doc/dovecot-example.conf + if [[ ${DOVECOT_INET_PROTOCOLS} == "ipv4" ]] + then + PROTOCOL='*' # IPv4 only + elif [[ ${DOVECOT_INET_PROTOCOLS} == "ipv6" ]] + then + PROTOCOL='[::]' # IPv6 only + else + # Unknown value, panic. + dms_panic__invalid_value 'DOVECOT_INET_PROTOCOLS' "${DOVECOT_INET_PROTOCOLS}" + fi + + sedfile -i "s|^#listen =.*|listen = ${PROTOCOL}|g" /etc/dovecot/dovecot.conf +} + +function _setup_dovecot_dhparam +{ + _setup_dhparam 'Dovecot' '/etc/dovecot/dh.pem' +} + +function _setup_dovecot_hostname +{ + _log 'debug' 'Applying hostname to Dovecot' + sed -i "s|^#hostname =.*$|hostname = '${HOSTNAME}'|g" /etc/dovecot/conf.d/15-lda.conf +} diff --git a/target/scripts/startup/setup.d/fetchmail.sh b/target/scripts/startup/setup.d/fetchmail.sh new file mode 100644 index 00000000..d17a9d4c --- /dev/null +++ b/target/scripts/startup/setup.d/fetchmail.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +function _setup_fetchmail +{ + _log 'trace' 'Preparing Fetchmail configuration' + + local CONFIGURATION FETCHMAILRC + + CONFIGURATION='/tmp/docker-mailserver/fetchmail.cf' + FETCHMAILRC='/etc/fetchmailrc' + + if [[ -f ${CONFIGURATION} ]] + then + cat /etc/fetchmailrc_general "${CONFIGURATION}" >"${FETCHMAILRC}" + else + cat /etc/fetchmailrc_general >"${FETCHMAILRC}" + fi + + chmod 700 "${FETCHMAILRC}" + chown fetchmail:root "${FETCHMAILRC}" +} + +function _setup_fetchmail_parallel +{ + _log 'trace' 'Setting up Fetchmail parallel' + mkdir /etc/fetchmailrc.d/ + + # Split the content of /etc/fetchmailrc into + # smaller fetchmailrc files per server [poll] entries. Each + # separate fetchmailrc file is stored in /etc/fetchmailrc.d + # + # The sole purpose for this is to work around what is known + # as the Fetchmail IMAP idle issue. + function _fetchmailrc_split + { + local FETCHMAILRC='/etc/fetchmailrc' + local FETCHMAILRCD='/etc/fetchmailrc.d' + local DEFAULT_FILE="${FETCHMAILRCD}/defaults" + + if [[ ! -r ${FETCHMAILRC} ]] + then + _log 'warn' "File '${FETCHMAILRC}' not found" + return 1 + fi + + if [[ ! -d ${FETCHMAILRCD} ]] + then + if ! mkdir "${FETCHMAILRCD}" + then + _log 'warn' "Unable to create folder '${FETCHMAILRCD}'" + return 1 + fi + fi + + local COUNTER=0 SERVER=0 + while read -r LINE + do + if [[ ${LINE} =~ poll ]] + then + # If we read "poll" then we reached a new server definition + # We need to create a new file with fetchmail defaults from + # /etc/fetcmailrc + COUNTER=$(( COUNTER + 1 )) + SERVER=1 + cat "${DEFAULT_FILE}" >"${FETCHMAILRCD}/fetchmail-${COUNTER}.rc" + echo "${LINE}" >>"${FETCHMAILRCD}/fetchmail-${COUNTER}.rc" + elif [[ ${SERVER} -eq 0 ]] + then + # We have not yet found "poll". Let's assume we are still reading + # the default settings from /etc/fetchmailrc file + echo "${LINE}" >>"${DEFAULT_FILE}" + else + # Just the server settings that need to be added to the specific rc.d file + echo "${LINE}" >>"${FETCHMAILRCD}/fetchmail-${COUNTER}.rc" + fi + done < <(_get_valid_lines_from_file "${FETCHMAILRC}") + + rm "${DEFAULT_FILE}" + } + + _fetchmailrc_split + + local COUNTER=0 + for RC in /etc/fetchmailrc.d/fetchmail-*.rc + do + COUNTER=$(( COUNTER + 1 )) + cat >"/etc/supervisor/conf.d/fetchmail-${COUNTER}.conf" << EOF +[program:fetchmail-${COUNTER}] +startsecs=0 +autostart=false +autorestart=true +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log +user=fetchmail +command=/usr/bin/fetchmail -f ${RC} -v --nodetach --daemon %(ENV_FETCHMAIL_POLL)s -i /var/lib/fetchmail/.fetchmail-UIDL-cache --pidfile /var/run/fetchmail/%(program_name)s.pid +EOF + chmod 700 "${RC}" + chown fetchmail:root "${RC}" + done + + supervisorctl reread + supervisorctl update +} diff --git a/target/scripts/startup/setup.d/ldap.sh b/target/scripts/startup/setup.d/ldap.sh new file mode 100644 index 00000000..fce2e309 --- /dev/null +++ b/target/scripts/startup/setup.d/ldap.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +function _setup_ldap +{ + _log 'debug' 'Setting up LDAP' + _log 'trace' 'Checking for custom configs' + + for i in 'users' 'groups' 'aliases' 'domains' + do + local FPATH="/tmp/docker-mailserver/ldap-${i}.cf" + if [[ -f ${FPATH} ]] + then + cp "${FPATH}" "/etc/postfix/ldap-${i}.cf" + fi + done + + _log 'trace' 'Starting to override configs' + + local FILES=( + /etc/postfix/ldap-users.cf + /etc/postfix/ldap-groups.cf + /etc/postfix/ldap-aliases.cf + /etc/postfix/ldap-domains.cf + /etc/postfix/ldap-senders.cf + /etc/postfix/maps/sender_login_maps.ldap + ) + + for FILE in "${FILES[@]}" + do + [[ ${FILE} =~ ldap-user ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_USER}" + [[ ${FILE} =~ ldap-group ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_GROUP}" + [[ ${FILE} =~ ldap-aliases ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_ALIAS}" + [[ ${FILE} =~ ldap-domains ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_DOMAIN}" + [[ ${FILE} =~ ldap-senders ]] && export LDAP_QUERY_FILTER="${LDAP_QUERY_FILTER_SENDERS}" + [[ -f ${FILE} ]] && _replace_by_env_in_file 'LDAP_' "${FILE}" + done + + _log 'trace' "Configuring Dovecot LDAP" + + declare -A DOVECOT_LDAP_MAPPING + + DOVECOT_LDAP_MAPPING['DOVECOT_BASE']="${DOVECOT_BASE:="${LDAP_SEARCH_BASE}"}" + DOVECOT_LDAP_MAPPING['DOVECOT_DN']="${DOVECOT_DN:="${LDAP_BIND_DN}"}" + DOVECOT_LDAP_MAPPING['DOVECOT_DNPASS']="${DOVECOT_DNPASS:="${LDAP_BIND_PW}"}" + DOVECOT_LDAP_MAPPING['DOVECOT_URIS']="${DOVECOT_URIS:="${DOVECOT_HOSTS:="${LDAP_SERVER_HOST}"}"}" + + # Add protocol to DOVECOT_URIS so that we can use dovecot's "uris" option: + # https://doc.dovecot.org/configuration_manual/authentication/ldap/ + if [[ ${DOVECOT_LDAP_MAPPING["DOVECOT_URIS"]} != *'://'* ]] + then + DOVECOT_LDAP_MAPPING['DOVECOT_URIS']="ldap://${DOVECOT_LDAP_MAPPING["DOVECOT_URIS"]}" + fi + + # Default DOVECOT_PASS_FILTER to the same value as DOVECOT_USER_FILTER + DOVECOT_LDAP_MAPPING['DOVECOT_PASS_FILTER']="${DOVECOT_PASS_FILTER:="${DOVECOT_USER_FILTER}"}" + + for VAR in "${!DOVECOT_LDAP_MAPPING[@]}" + do + export "${VAR}=${DOVECOT_LDAP_MAPPING[${VAR}]}" + done + + _replace_by_env_in_file 'DOVECOT_' '/etc/dovecot/dovecot-ldap.conf.ext' + + _log 'trace' 'Enabling Dovecot LDAP authentication' + + 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 + + _log 'trace' "Configuring LDAP" + + if [[ -f /etc/postfix/ldap-users.cf ]] + then + postconf 'virtual_mailbox_maps = ldap:/etc/postfix/ldap-users.cf' + else + _log 'warn' "'/etc/postfix/ldap-users.cf' not found" + fi + + if [[ -f /etc/postfix/ldap-domains.cf ]] + then + postconf 'virtual_mailbox_domains = /etc/postfix/vhost, ldap:/etc/postfix/ldap-domains.cf' + else + _log 'warn' "'/etc/postfix/ldap-domains.cf' not found" + fi + + if [[ -f /etc/postfix/ldap-aliases.cf ]] && [[ -f /etc/postfix/ldap-groups.cf ]] + then + postconf 'virtual_alias_maps = ldap:/etc/postfix/ldap-aliases.cf, ldap:/etc/postfix/ldap-groups.cf' + else + _log 'warn' "'/etc/postfix/ldap-aliases.cf' and / or '/etc/postfix/ldap-groups.cf' not found" + fi + + # shellcheck disable=SC2016 + sed -i 's|mydestination = \$myhostname, |mydestination = |' /etc/postfix/main.cf + + return 0 +} diff --git a/target/scripts/startup/setup.d/log.sh b/target/scripts/startup/setup.d/log.sh new file mode 100644 index 00000000..5c9d3cc6 --- /dev/null +++ b/target/scripts/startup/setup.d/log.sh @@ -0,0 +1,110 @@ +#!/bin/bash + +function _setup_logrotate +{ + _log 'debug' 'Setting up logrotate' + + LOGROTATE='/var/log/mail/mail.log\n{\n compress\n copytruncate\n delaycompress\n' + + case "${LOGROTATE_INTERVAL}" in + ( 'daily' ) + _log 'trace' 'Setting postfix logrotate interval to daily' + LOGROTATE="${LOGROTATE} rotate 4\n daily\n" + ;; + + ( 'weekly' ) + _log 'trace' 'Setting postfix logrotate interval to weekly' + LOGROTATE="${LOGROTATE} rotate 4\n weekly\n" + ;; + + ( 'monthly' ) + _log 'trace' 'Setting postfix logrotate interval to monthly' + LOGROTATE="${LOGROTATE} rotate 4\n monthly\n" + ;; + + ( * ) + _log 'warn' 'LOGROTATE_INTERVAL not found in _setup_logrotate' + ;; + + esac + + echo -e "${LOGROTATE}}" >/etc/logrotate.d/maillog +} + +function _setup_mail_summary +{ + local ENABLED_MESSAGE + ENABLED_MESSAGE="Enabling Postfix log summary reports with recipient '${PFLOGSUMM_RECIPIENT}'" + + case "${PFLOGSUMM_TRIGGER}" in + ( 'daily_cron' ) + _log 'debug' "${ENABLED_MESSAGE}" + _log 'trace' 'Creating daily cron job for pflogsumm report' + + cat >/etc/cron.daily/postfix-summary << EOF +#!/bin/bash + +/usr/local/bin/report-pflogsumm-yesterday ${HOSTNAME} ${PFLOGSUMM_RECIPIENT} ${PFLOGSUMM_SENDER} +EOF + + chmod +x /etc/cron.daily/postfix-summary + ;; + + ( 'logrotate' ) + _log 'debug' "${ENABLED_MESSAGE}" + _log 'trace' 'Add postrotate action for pflogsumm report' + sed -i \ + "s|}| postrotate\n /usr/local/bin/postfix-summary ${HOSTNAME} ${PFLOGSUMM_RECIPIENT} ${PFLOGSUMM_SENDER}\n endscript\n}\n|" \ + /etc/logrotate.d/maillog + ;; + + ( 'none' ) + _log 'debug' 'Postfix log summary reports disabled' + ;; + + ( * ) + _log 'warn' "Invalid value for PFLOGSUMM_TRIGGER: '${PFLOGSUMM_TRIGGER}'" + ;; + + esac +} + +function _setup_logwatch +{ + echo 'LogFile = /var/log/mail/freshclam.log' >>/etc/logwatch/conf/logfiles/clam-update.conf + echo "MailFrom = ${LOGWATCH_SENDER}" >>/etc/logwatch/conf/logwatch.conf + echo "Mailer = \"sendmail -t -f ${LOGWATCH_SENDER}\"" >>/etc/logwatch/conf/logwatch.conf + + case "${LOGWATCH_INTERVAL}" in + ( 'daily' | 'weekly' ) + _log 'debug' "Enabling logwatch reports with recipient '${LOGWATCH_RECIPIENT}'" + _log 'trace' "Creating ${LOGWATCH_INTERVAL} cron job for logwatch reports" + + local LOGWATCH_FILE INTERVAL + + LOGWATCH_FILE="/etc/cron.${LOGWATCH_INTERVAL}/logwatch" + INTERVAL='--range Yesterday' + + if [[ ${LOGWATCH_INTERVAL} == 'weekly' ]] + then + INTERVAL="--range 'between -7 days and -1 days'" + fi + + cat >"${LOGWATCH_FILE}" << EOF +#!/bin/bash + +/usr/sbin/logwatch ${INTERVAL} --hostname ${HOSTNAME} --mailto ${LOGWATCH_RECIPIENT} +EOF + chmod 744 "${LOGWATCH_FILE}" + ;; + + ( 'none' ) + _log 'debug' 'Logwatch reports disabled.' + ;; + + ( * ) + _log 'warn' "Invalid value for LOGWATCH_INTERVAL: '${LOGWATCH_INTERVAL}'" + ;; + + esac +} diff --git a/target/scripts/startup/setup.d/networking.sh b/target/scripts/startup/setup.d/networking.sh new file mode 100644 index 00000000..febd703a --- /dev/null +++ b/target/scripts/startup/setup.d/networking.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +function _setup_mailname +{ + _log 'debug' "Setting up mailname and creating '/etc/mailname'" + echo "${DOMAINNAME}" >/etc/mailname +} + +function _setup_docker_permit +{ + _log 'debug' 'Setting up PERMIT_DOCKER option' + + local CONTAINER_IP CONTAINER_NETWORK + + unset CONTAINER_NETWORKS + declare -a CONTAINER_NETWORKS + + CONTAINER_IP=$(ip addr show "${NETWORK_INTERFACE}" | \ + grep 'inet ' | sed 's|[^0-9\.\/]*||g' | cut -d '/' -f 1) + CONTAINER_NETWORK=$(echo "${CONTAINER_IP}" | cut -d '.' -f1-2).0.0 + + if [[ -z ${CONTAINER_IP} ]] + then + _log 'error' 'Detecting the container IP address failed' + dms_panic__misconfigured 'NETWORK_INTERFACE' 'Network Setup [docker_permit]' + fi + + while read -r IP + do + CONTAINER_NETWORKS+=("${IP}") + done < <(ip -o -4 addr show type veth | grep -E -o '[0-9\.]+/[0-9]+') + + function __clear_postfix_mynetworks + { + _log 'trace' "Clearing Postfix's 'mynetworks'" + postconf "mynetworks =" + } + + function __add_to_postfix_mynetworks + { + local NETWORK_TYPE=$1 + local NETWORK=$2 + + _log 'trace' "Adding ${NETWORK_TYPE} (${NETWORK}) to Postfix 'main.cf:mynetworks'" + _adjust_mtime_for_postfix_maincf + postconf "$(postconf | grep '^mynetworks =') ${NETWORK}" + [[ ${ENABLE_OPENDMARC} -eq 1 ]] && echo "${NETWORK}" >>/etc/opendmarc/ignore.hosts + [[ ${ENABLE_OPENDKIM} -eq 1 ]] && echo "${NETWORK}" >>/etc/opendkim/TrustedHosts + } + + case "${PERMIT_DOCKER}" in + ( 'none' ) + __clear_postfix_mynetworks + ;; + + ( 'connected-networks' ) + for CONTAINER_NETWORK in "${CONTAINER_NETWORKS[@]}" + do + CONTAINER_NETWORK=$(_sanitize_ipv4_to_subnet_cidr "${CONTAINER_NETWORK}") + __add_to_postfix_mynetworks 'Docker Network' "${CONTAINER_NETWORK}" + done + ;; + + ( 'container' ) + __add_to_postfix_mynetworks 'Container IP address' "${CONTAINER_IP}/32" + ;; + + ( 'host' ) + __add_to_postfix_mynetworks 'Host Network' "${CONTAINER_NETWORK}/16" + ;; + + ( 'network' ) + __add_to_postfix_mynetworks 'Docker IPv4 Subnet' '172.16.0.0/12' + ;; + + ( * ) + _log 'warn' "Invalid value for PERMIT_DOCKER: '${PERMIT_DOCKER}'" + __clear_postfix_mynetworks + ;; + + esac +} diff --git a/target/scripts/startup/setup.d/postfix.sh b/target/scripts/startup/setup.d/postfix.sh new file mode 100644 index 00000000..61917a43 --- /dev/null +++ b/target/scripts/startup/setup.d/postfix.sh @@ -0,0 +1,149 @@ +#!/bin/bash + +function _setup_postfix_sizelimits +{ + _log 'trace' "Configuring Postfix message size limit to '${POSTFIX_MESSAGE_SIZE_LIMIT}'" + postconf "message_size_limit = ${POSTFIX_MESSAGE_SIZE_LIMIT}" + + _log 'trace' "Configuring Postfix mailbox size limit to '${POSTFIX_MAILBOX_SIZE_LIMIT}'" + postconf "mailbox_size_limit = ${POSTFIX_MAILBOX_SIZE_LIMIT}" + + _log 'trace' "Configuring Postfix virtual mailbox size limit to '${POSTFIX_MAILBOX_SIZE_LIMIT}'" + postconf "virtual_mailbox_limit = ${POSTFIX_MAILBOX_SIZE_LIMIT}" +} + +function _setup_postfix_access_control +{ + _log 'trace' 'Configuring user access' + + if [[ -f /tmp/docker-mailserver/postfix-send-access.cf ]] + then + sed -i 's|smtpd_sender_restrictions =|smtpd_sender_restrictions = check_sender_access texthash:/tmp/docker-mailserver/postfix-send-access.cf,|' /etc/postfix/main.cf + fi + + if [[ -f /tmp/docker-mailserver/postfix-receive-access.cf ]] + then + sed -i 's|smtpd_recipient_restrictions =|smtpd_recipient_restrictions = check_recipient_access texthash:/tmp/docker-mailserver/postfix-receive-access.cf,|' /etc/postfix/main.cf + fi +} + +function _setup_postfix_sasl +{ + if [[ ${ENABLE_SASLAUTHD} -eq 1 ]] && [[ ! -f /etc/postfix/sasl/smtpd.conf ]] + then + cat >/etc/postfix/sasl/smtpd.conf << EOF +pwcheck_method: saslauthd +mech_list: plain login +EOF + fi + + if [[ ${ENABLE_SASLAUTHD} -eq 0 ]] && [[ ${SMTP_ONLY} -eq 1 ]] + then + sed -i -E \ + 's|^smtpd_sasl_auth_enable =.*|smtpd_sasl_auth_enable = no|g' \ + /etc/postfix/main.cf + sed -i -E \ + 's|^ -o smtpd_sasl_auth_enable=.*| -o smtpd_sasl_auth_enable=no|g' \ + /etc/postfix/master.cf + fi +} + +function _setup_postfix_aliases +{ + _log 'debug' 'Setting up Postfix aliases' + _create_aliases +} + +function _setup_postfix_vhost +{ + _log 'debug' 'Setting up Postfix vhost' + _create_postfix_vhost +} + +function _setup_postfix_inet_protocols +{ + _log 'trace' 'Setting up POSTFIX_INET_PROTOCOLS option' + postconf "inet_protocols = ${POSTFIX_INET_PROTOCOLS}" +} + + +function _setup_postfix_virtual_transport +{ + _log 'trace' "Changing Postfix virtual transport to '${POSTFIX_DAGENT}'" + # Default value in main.cf should be 'lmtp:unix:/var/run/dovecot/lmtp' + postconf "virtual_transport = ${POSTFIX_DAGENT}" +} + +function _setup_postfix_override_configuration +{ + _log 'debug' 'Overriding / adjusting Postfix configuration with user-supplied values' + + if [[ -f /tmp/docker-mailserver/postfix-main.cf ]] + then + cat /tmp/docker-mailserver/postfix-main.cf >>/etc/postfix/main.cf + _adjust_mtime_for_postfix_maincf + + # do not directly output to 'main.cf' as this causes a read-write-conflict + postconf -n >/tmp/postfix-main-new.cf 2>/dev/null + + mv /tmp/postfix-main-new.cf /etc/postfix/main.cf + _adjust_mtime_for_postfix_maincf + _log 'trace' "Adjusted '/etc/postfix/main.cf' according to '/tmp/docker-mailserver/postfix-main.cf'" + else + _log 'trace' "No extra Postfix settings loaded because optional '/tmp/docker-mailserver/postfix-main.cf' was not provided" + fi + + if [[ -f /tmp/docker-mailserver/postfix-master.cf ]] + then + while read -r LINE + do + if [[ ${LINE} =~ ^[0-9a-z] ]] + then + postconf -P "${LINE}" + fi + done < /tmp/docker-mailserver/postfix-master.cf + _log 'trace' "Adjusted '/etc/postfix/master.cf' according to '/tmp/docker-mailserver/postfix-master.cf'" + else + _log 'trace' "No extra Postfix settings loaded because optional '/tmp/docker-mailserver/postfix-master.cf' was not provided" + fi +} + +function _setup_postfix_relay_hosts +{ + _setup_relayhost +} + +function _setup_postfix_dhparam +{ + _setup_dhparam 'Postfix' '/etc/postfix/dhparams.pem' +} + +function _setup_dnsbl_disable +{ + _log 'debug' 'Disabling postscreen DNS block lists' + postconf 'postscreen_dnsbl_action = ignore' + postconf 'postscreen_dnsbl_sites = ' +} + +function _setup_postfix_smtputf8 +{ + _log 'trace' "Disabling Postfix's smtputf8 support" + postconf 'smtputf8_enable = no' +} + +function _setup_SRS +{ + _log 'debug' 'Setting up SRS' + + postconf 'sender_canonical_maps = tcp:localhost:10001' + postconf "sender_canonical_classes = ${SRS_SENDER_CLASSES}" + postconf 'recipient_canonical_maps = tcp:localhost:10002' + postconf 'recipient_canonical_classes = envelope_recipient,header_recipient' +} + +function _setup_postfix_hostname +{ + _log 'debug' 'Applying hostname and domainname to Postfix' + postconf "myhostname = ${HOSTNAME}" + postconf "mydomain = ${DOMAINNAME}" +} diff --git a/target/scripts/startup/setup.d/saslauthd.sh b/target/scripts/startup/setup.d/saslauthd.sh new file mode 100644 index 00000000..1a6488d0 --- /dev/null +++ b/target/scripts/startup/setup.d/saslauthd.sh @@ -0,0 +1,47 @@ +#!/bin/bash + + +function _setup_saslauthd +{ + _log 'debug' 'Setting up SASLAUTHD' + + if [[ ! -f /etc/saslauthd.conf ]] + then + _log 'trace' 'Creating /etc/saslauthd.conf' + cat > /etc/saslauthd.conf << EOF +ldap_servers: ${SASLAUTHD_LDAP_SERVER} + +ldap_auth_method: ${SASLAUTHD_LDAP_AUTH_METHOD} +ldap_bind_dn: ${SASLAUTHD_LDAP_BIND_DN} +ldap_bind_pw: ${SASLAUTHD_LDAP_PASSWORD} + +ldap_search_base: ${SASLAUTHD_LDAP_SEARCH_BASE} +ldap_filter: ${SASLAUTHD_LDAP_FILTER} + +ldap_start_tls: ${SASLAUTHD_LDAP_START_TLS} +ldap_tls_check_peer: ${SASLAUTHD_LDAP_TLS_CHECK_PEER} + +${SASLAUTHD_LDAP_TLS_CACERT_FILE} +${SASLAUTHD_LDAP_TLS_CACERT_DIR} +${SASLAUTHD_LDAP_PASSWORD_ATTR} +${SASLAUTHD_LDAP_MECH} + +ldap_referrals: yes +log_level: 10 +EOF + fi + + sed -i \ + -e "/^[^#].*smtpd_sasl_type.*/s/^/#/g" \ + -e "/^[^#].*smtpd_sasl_path.*/s/^/#/g" \ + /etc/postfix/master.cf + + sed -i \ + -e "/smtpd_sasl_path =.*/d" \ + -e "/smtpd_sasl_type =.*/d" \ + -e "/dovecot_destination_recipient_limit =.*/d" \ + /etc/postfix/main.cf + + gpasswd -a postfix sasl >/dev/null +} + diff --git a/target/scripts/startup/setup.d/security/misc.sh b/target/scripts/startup/setup.d/security/misc.sh new file mode 100644 index 00000000..92db3912 --- /dev/null +++ b/target/scripts/startup/setup.d/security/misc.sh @@ -0,0 +1,265 @@ +#!/bin/bash + +function _setup_security_stack +{ + _log 'debug' 'Setting up Security Stack' + + # recreate auto-generated file + local DMS_AMAVIS_FILE=/etc/amavis/conf.d/61-dms_auto_generated + + echo "# WARNING: this file is auto-generated." >"${DMS_AMAVIS_FILE}" + echo "use strict;" >>"${DMS_AMAVIS_FILE}" + + # SpamAssassin + if [[ ${ENABLE_SPAMASSASSIN} -eq 0 ]] + then + _log 'debug' 'SpamAssassin is disabled' + echo "@bypass_spam_checks_maps = (1);" >>"${DMS_AMAVIS_FILE}" + elif [[ ${ENABLE_SPAMASSASSIN} -eq 1 ]] + then + _log 'debug' 'Enabling and configuring SpamAssassin' + + # shellcheck disable=SC2016 + sed -i -r 's|^\$sa_tag_level_deflt (.*);|\$sa_tag_level_deflt = '"${SA_TAG}"';|g' /etc/amavis/conf.d/20-debian_defaults + + # shellcheck disable=SC2016 + sed -i -r 's|^\$sa_tag2_level_deflt (.*);|\$sa_tag2_level_deflt = '"${SA_TAG2}"';|g' /etc/amavis/conf.d/20-debian_defaults + + # shellcheck disable=SC2016 + sed -i -r 's|^\$sa_kill_level_deflt (.*);|\$sa_kill_level_deflt = '"${SA_KILL}"';|g' /etc/amavis/conf.d/20-debian_defaults + + if [[ ${SA_SPAM_SUBJECT} == 'undef' ]] + then + # shellcheck disable=SC2016 + sed -i -r 's|^\$sa_spam_subject_tag (.*);|\$sa_spam_subject_tag = undef;|g' /etc/amavis/conf.d/20-debian_defaults + else + # shellcheck disable=SC2016 + sed -i -r 's|^\$sa_spam_subject_tag (.*);|\$sa_spam_subject_tag = '"'${SA_SPAM_SUBJECT}'"';|g' /etc/amavis/conf.d/20-debian_defaults + fi + + # activate short circuits when SA BAYES is certain it has spam or ham. + if [[ ${SA_SHORTCIRCUIT_BAYES_SPAM} -eq 1 ]] + then + # automatically activate the Shortcircuit Plugin + sed -i -r 's|^# loadplugin Mail::SpamAssassin::Plugin::Shortcircuit|loadplugin Mail::SpamAssassin::Plugin::Shortcircuit|g' /etc/spamassassin/v320.pre + sed -i -r 's|^# shortcircuit BAYES_99|shortcircuit BAYES_99|g' /etc/spamassassin/local.cf + fi + + if [[ ${SA_SHORTCIRCUIT_BAYES_HAM} -eq 1 ]] + then + # automatically activate the Shortcircuit Plugin + sed -i -r 's|^# loadplugin Mail::SpamAssassin::Plugin::Shortcircuit|loadplugin Mail::SpamAssassin::Plugin::Shortcircuit|g' /etc/spamassassin/v320.pre + sed -i -r 's|^# shortcircuit BAYES_00|shortcircuit BAYES_00|g' /etc/spamassassin/local.cf + fi + + if [[ -e /tmp/docker-mailserver/spamassassin-rules.cf ]] + then + cp /tmp/docker-mailserver/spamassassin-rules.cf /etc/spamassassin/ + fi + + if [[ ${SPAMASSASSIN_SPAM_TO_INBOX} -eq 1 ]] + then + _log 'trace' 'Configuring Spamassassin/Amavis to send SPAM to inbox' + + sed -i "s|\$final_spam_destiny.*=.*$|\$final_spam_destiny = D_PASS;|g" /etc/amavis/conf.d/49-docker-mailserver + sed -i "s|\$final_bad_header_destiny.*=.*$|\$final_bad_header_destiny = D_PASS;|g" /etc/amavis/conf.d/49-docker-mailserver + else + _log 'trace' 'Configuring Spamassassin/Amavis to bounce SPAM' + + sed -i "s|\$final_spam_destiny.*=.*$|\$final_spam_destiny = D_BOUNCE;|g" /etc/amavis/conf.d/49-docker-mailserver + sed -i "s|\$final_bad_header_destiny.*=.*$|\$final_bad_header_destiny = D_BOUNCE;|g" /etc/amavis/conf.d/49-docker-mailserver + fi + + if [[ ${ENABLE_SPAMASSASSIN_KAM} -eq 1 ]] + then + _log 'trace' 'Configuring Spamassassin KAM' + local SPAMASSASSIN_KAM_CRON_FILE=/etc/cron.daily/spamassassin_kam + + sa-update --import /etc/spamassassin/kam/kam.sa-channels.mcgrail.com.key + + cat >"${SPAMASSASSIN_KAM_CRON_FILE}" <<"EOF" +#!/bin/bash + +RESULT=$(sa-update --gpgkey 24C063D8 --channel kam.sa-channels.mcgrail.com 2>&1) +EXIT_CODE=${?} + +# see https://spamassassin.apache.org/full/3.1.x/doc/sa-update.html#exit_codes +if [[ ${EXIT_CODE} -ge 4 ]] +then + echo -e "Updating SpamAssassin KAM failed:\n${RESULT}\n" >&2 + exit 1 +fi + +exit 0 + +EOF + + chmod +x "${SPAMASSASSIN_KAM_CRON_FILE}" + fi + fi + + # ClamAV + if [[ ${ENABLE_CLAMAV} -eq 0 ]] + then + _log 'debug' 'ClamAV is disabled' + echo '@bypass_virus_checks_maps = (1);' >>"${DMS_AMAVIS_FILE}" + elif [[ ${ENABLE_CLAMAV} -eq 1 ]] + then + _log 'debug' 'Enabling ClamAV' + fi + + echo '1; # ensure a defined return' >>"${DMS_AMAVIS_FILE}" + chmod 444 "${DMS_AMAVIS_FILE}" + + # Fail2ban + if [[ ${ENABLE_FAIL2BAN} -eq 1 ]] + then + _log 'debug' 'Enabling Fail2Ban' + + if [[ -e /tmp/docker-mailserver/fail2ban-fail2ban.cf ]] + then + cp /tmp/docker-mailserver/fail2ban-fail2ban.cf /etc/fail2ban/fail2ban.local + fi + + if [[ -e /tmp/docker-mailserver/fail2ban-jail.cf ]] + then + cp /tmp/docker-mailserver/fail2ban-jail.cf /etc/fail2ban/jail.d/user-jail.local + fi + else + # disable logrotate config for fail2ban if not enabled + rm -f /etc/logrotate.d/fail2ban + fi + + # fix cron.daily for spamassassin + sed -i \ + 's|invoke-rc.d spamassassin reload|/etc/init\.d/spamassassin reload|g' \ + /etc/cron.daily/spamassassin + + # Amavis + if [[ ${ENABLE_AMAVIS} -eq 1 ]] + then + _log 'debug' 'Enabling Amavis' + if [[ -f /tmp/docker-mailserver/amavis.cf ]] + then + cp /tmp/docker-mailserver/amavis.cf /etc/amavis/conf.d/50-user + fi + + sed -i -E \ + "s|(log_level).*|\1 = ${AMAVIS_LOGLEVEL};|g" \ + /etc/amavis/conf.d/49-docker-mailserver + fi +} + +function _setup_amavis +{ + if [[ ${ENABLE_AMAVIS} -eq 1 ]] + then + _log 'debug' 'Setting up Amavis' + + cat /etc/dms/postfix/master.d/postfix-amavis.cf >>/etc/postfix/master.cf + postconf 'content_filter = smtp-amavis:[127.0.0.1]:10024' + + sed -i \ + "s|^#\$myhostname = \"mail.example.com\";|\$myhostname = \"${HOSTNAME}\";|" \ + /etc/amavis/conf.d/05-node_id + else + _log 'debug' 'Disabling Amavis cron job' + mv /etc/cron.d/amavisd-new /etc/cron.d/amavisd-new.disabled + chmod 0 /etc/cron.d/amavisd-new.disabled + + if [[ ${ENABLE_CLAMAV} -eq 1 ]] && [[ ${ENABLE_RSPAMD} -eq 0 ]] + then + _log 'warn' 'ClamAV will not work when Amavis & rspamd are disabled. Enable either Amavis or rspamd to fix it.' + fi + + if [[ ${ENABLE_SPAMASSASSIN} -eq 1 ]] + then + _log 'warn' 'Spamassassin will not work when Amavis is disabled. Enable Amavis to fix it.' + fi + fi +} + +function _setup_fail2ban +{ + _log 'debug' 'Setting up Fail2Ban' + + if [[ ${FAIL2BAN_BLOCKTYPE} != 'reject' ]] + then + echo -e '[Init]\nblocktype = drop' >/etc/fail2ban/action.d/nftables-common.local + fi + + echo '[Definition]' >/etc/fail2ban/filter.d/custom.conf +} + +function _setup_postgrey +{ + _log 'debug' 'Configuring Postgrey' + + sedfile -i -E \ + 's|(^smtpd_recipient_restrictions =.*)|\1, check_policy_service inet:127.0.0.1:10023|' \ + /etc/postfix/main.cf + + sed -i -e \ + "s|\"--inet=127.0.0.1:10023\"|\"--inet=127.0.0.1:10023 --delay=${POSTGREY_DELAY} --max-age=${POSTGREY_MAX_AGE} --auto-whitelist-clients=${POSTGREY_AUTO_WHITELIST_CLIENTS}\"|" \ + /etc/default/postgrey + + TEXT_FOUND=$(grep -c -i 'POSTGREY_TEXT' /etc/default/postgrey) + + if [[ ${TEXT_FOUND} -eq 0 ]] + then + printf 'POSTGREY_TEXT=\"%s\"\n\n' "${POSTGREY_TEXT}" >>/etc/default/postgrey + fi + + if [[ -f /tmp/docker-mailserver/whitelist_clients.local ]] + then + cp -f /tmp/docker-mailserver/whitelist_clients.local /etc/postgrey/whitelist_clients.local + fi + + if [[ -f /tmp/docker-mailserver/whitelist_recipients ]] + then + cp -f /tmp/docker-mailserver/whitelist_recipients /etc/postgrey/whitelist_recipients + fi +} + +function _setup_postfix_postscreen +{ + _log 'debug' 'Configuring Postscreen' + sed -i \ + -e "s|postscreen_dnsbl_action = enforce|postscreen_dnsbl_action = ${POSTSCREEN_ACTION}|" \ + -e "s|postscreen_greet_action = enforce|postscreen_greet_action = ${POSTSCREEN_ACTION}|" \ + -e "s|postscreen_bare_newline_action = enforce|postscreen_bare_newline_action = ${POSTSCREEN_ACTION}|" /etc/postfix/main.cf +} + + +function _setup_clamav_sizelimit +{ + _log 'trace' "Setting ClamAV message scan size limit to '${CLAMAV_MESSAGE_SIZE_LIMIT}'" + sedfile -i "s/^MaxFileSize.*/MaxFileSize ${CLAMAV_MESSAGE_SIZE_LIMIT}/" /etc/clamav/clamd.conf +} + + +function _setup_spoof_protection +{ + _log 'trace' 'Configuring spoof protection' + sed -i \ + 's|smtpd_sender_restrictions =|smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch,|' \ + /etc/postfix/main.cf + + if [[ ${ACCOUNT_PROVISIONER} == 'LDAP' ]] + then + if [[ -z ${LDAP_QUERY_FILTER_SENDERS} ]] + then + postconf 'smtpd_sender_login_maps = ldap:/etc/postfix/ldap-users.cf ldap:/etc/postfix/ldap-aliases.cf ldap:/etc/postfix/ldap-groups.cf' + else + postconf 'smtpd_sender_login_maps = ldap:/etc/postfix/ldap-senders.cf' + fi + else + if [[ -f /etc/postfix/regexp ]] + then + postconf 'smtpd_sender_login_maps = unionmap:{ texthash:/etc/postfix/virtual, hash:/etc/aliases, pcre:/etc/postfix/maps/sender_login_maps.pcre, pcre:/etc/postfix/regexp }' + else + postconf 'smtpd_sender_login_maps = texthash:/etc/postfix/virtual, hash:/etc/aliases, pcre:/etc/postfix/maps/sender_login_maps.pcre' + fi + fi +} + diff --git a/target/scripts/helpers/setup-rspamd.sh b/target/scripts/startup/setup.d/security/rspamd.sh similarity index 93% rename from target/scripts/helpers/setup-rspamd.sh rename to target/scripts/startup/setup.d/security/rspamd.sh index 9fe8d0ba..3425bbbd 100644 --- a/target/scripts/helpers/setup-rspamd.sh +++ b/target/scripts/startup/setup.d/security/rspamd.sh @@ -1,5 +1,16 @@ #!/bin/bash +function _setup_rspamd +{ + _log 'warn' 'Rspamd integration is work in progress - expect (breaking) changes at any time' + _log 'debug' 'Enabling Rspamd' + + __rspamd__preflight_checks + __rspamd__adjust_postfix_configuration + __rspamd__disable_default_modules + __rspamd__handle_modules_configuration +} + # Just a helper to prepend the log messages with `(Rspamd setup)` so # users know exactly where the message originated from. # @@ -36,8 +47,10 @@ function __rspamd__preflight_checks # `smtpd_milters` in `main.cf`. function __rspamd__adjust_postfix_configuration { + postconf 'rspamd_milter = inet:localhost:11332' + # shellcheck disable=SC2016 - sed -i -E 's|^(smtpd_milters =.*)|\1 inet:localhost:11332|g' /etc/postfix/main.cf + sed -i -E 's|^(smtpd_milters =.*)|\1 \$rspamd_milter|g' /etc/postfix/main.cf } # Helper for explicitly enabling or disabling a specific module. diff --git a/target/scripts/helpers/variables.sh b/target/scripts/startup/variables-stack.sh similarity index 97% rename from target/scripts/helpers/variables.sh rename to target/scripts/startup/variables-stack.sh index d224465d..bdb57091 100644 --- a/target/scripts/helpers/variables.sh +++ b/target/scripts/startup/variables-stack.sh @@ -4,7 +4,14 @@ declare -A VARS # shellcheck disable=SC2034 -declare -a FUNCS_SETUP FUNCS_FIX FUNCS_CHECK FUNCS_MISC DAEMONS_START +declare -a FUNCS_FIX FUNCS_CHECK FUNCS_MISC DAEMONS_START + +function _early_variables_setup +{ + _obtain_hostname_and_domainname + _environment_variables_backwards_compatibility + _environment_variables_general_setup +} # This function handles variables that are deprecated. This allows a # smooth transition period, without the need of removing a variable diff --git a/test/tests/parallel/set1/spam_virus/rspamd.bats b/test/tests/parallel/set1/spam_virus/rspamd.bats index 96137b3d..f7b1f23a 100644 --- a/test/tests/parallel/set1/spam_virus/rspamd.bats +++ b/test/tests/parallel/set1/spam_virus/rspamd.bats @@ -42,7 +42,7 @@ function setup_file() { function teardown_file() { _default_teardown ; } @test "Postfix's main.cf was adjusted" { - _run_in_container grep -F 'smtpd_milters = inet:localhost:11332' /etc/postfix/main.cf + _run_in_container grep -F 'smtpd_milters = $rspamd_milter' /etc/postfix/main.cf assert_success }