From c7e420646617594ca149865a61a90bbd33dd4460 Mon Sep 17 00:00:00 2001 From: Sven Kauber Date: Mon, 6 Feb 2017 09:21:18 +0000 Subject: [PATCH] Added greylisting using postgrey (#495) * Added greylisting using postgrey * Updated the documentation --- Dockerfile | 8 ++ Makefile | 13 ++- README.md | 25 ++++++ target/postgrey/postgrey | 6 ++ target/postgrey/postgrey.init | 142 ++++++++++++++++++++++++++++++ target/start-mailserver.sh | 33 +++++++ test/email-templates/postgrey.txt | 12 +++ test/tests.bats | 53 +++++++++++ 8 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 target/postgrey/postgrey create mode 100644 target/postgrey/postgrey.init create mode 100644 test/email-templates/postgrey.txt diff --git a/Dockerfile b/Dockerfile index 5743d79e..29e2da6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,6 +39,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update -q --fix-missing && \ rsyslog \ sasl2-bin \ spamassassin \ + postgrey \ unzip \ && \ curl -sk http://neuro.debian.net/lists/trusty.de-m.libre > /etc/apt/sources.list.d/neurodebian.sources.list && \ @@ -70,6 +71,13 @@ COPY target/postfix/ldap-users.cf target/postfix/ldap-groups.cf target/postfix/l # Enables Spamassassin CRON updates RUN sed -i -r 's/^(CRON)=0/\1=1/g' /etc/default/spamassassin +#Enables Postgrey +COPY target/postgrey/postgrey /etc/default/postgrey +COPY target/postgrey/postgrey.init /etc/init.d/postgrey +RUN chmod 755 /etc/init.d/postgrey +RUN mkdir /var/run/postgrey +RUN chown postgrey:postgrey /var/run/postgrey + # Enables Amavis RUN sed -i -r 's/#(@| \\%)bypass/\1bypass/g' /etc/amavis/conf.d/15-content_filter_mode RUN adduser clamav amavis && adduser amavis clamav diff --git a/Makefile b/Makefile index a80509f5..c77d2067 100644 --- a/Makefile +++ b/Makefile @@ -119,6 +119,16 @@ run: -e POSTFIX_DAGENT=lmtp:127.0.0.1:24 \ -h mail.my-domain.com -t $(NAME) sleep 30 + docker run -d --name mail_with_postgrey \ + -v "`pwd`/test/config":/tmp/docker-mailserver \ + -v "`pwd`/test":/tmp/docker-mailserver-test \ + -e ENABLE_POSTGREY=1 \ + -e POSTGREY_DELAY=15 \ + -e POSTGREY_MAX_AGE=35 \ + -e POSTGREY_TEXT="Delayed by postgrey" \ + -h mail.my-domain.com -t $(NAME) + sleep 20 + fixtures: cp config/postfix-accounts.cf config/postfix-accounts.cf.bak @@ -162,7 +172,8 @@ clean: ldap_for_mail \ mail_with_ldap \ mail_with_imap \ - mail_lmtp_ip + mail_lmtp_ip \ + mail_with_postgrey @if [ -f config/postfix-accounts.cf.bak ]; then\ rm -f config/postfix-accounts.cf ;\ diff --git a/README.md b/README.md index aa70bc7c..f97e4534 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Includes: - opendmarc - fail2ban - fetchmail +- postgrey - basic [sieve support](https://github.com/tomav/docker-mailserver/wiki/Configure-Sieve-filters) using dovecot - [LetsEncrypt](https://letsencrypt.org/) and self-signed certificates - persistent data and state (but think about backups!) @@ -62,6 +63,7 @@ services: - ENABLE_SPAMASSASSIN=1 - ENABLE_CLAMAV=1 - ENABLE_FAIL2BAN=1 + - ENABLE_POSTGREY=1 - ONE_DIR=1 - DMS_DEBUG=0 cap_add: @@ -211,6 +213,29 @@ Otherwise, `iptables` won't be able to ban IPs. - **empty** => postmaster@domain.com - => Specify the postmaster address +#### ENABLE_POSTGREY + + - **0** => `postgrey` is disabled + - 1 => `postgrey` is enabled + +##### POSTGREY_DELAY + + - **300** => greylist for N seconds + +Note: This postgrey setting needs `ENABLE_POSTGREY=1` + +##### POSTGREY_MAX_AGE + + - **35** => delete entries older than N days since the last time that they have been seen + +Note: This postgrey setting needs `ENABLE_POSTGREY=1` + +##### POSTGREY_TEXT + + - **Delayed by postgrey** => response when a mail is greylisted + +Note: This postgrey setting needs `ENABLE_POSTGREY=1` + ##### ENABLE_SASLAUTHD - **0** => `saslauthd` is disabled diff --git a/target/postgrey/postgrey b/target/postgrey/postgrey new file mode 100644 index 00000000..30d6fa88 --- /dev/null +++ b/target/postgrey/postgrey @@ -0,0 +1,6 @@ +# you may want to set +# --delay=N how long to greylist, seconds (default: 300) +# --max-age=N delete old entries after N days (default: 35) + +POSTGREY_OPTS="--inet=10023" + diff --git a/target/postgrey/postgrey.init b/target/postgrey/postgrey.init new file mode 100644 index 00000000..55f54bd7 --- /dev/null +++ b/target/postgrey/postgrey.init @@ -0,0 +1,142 @@ +#!/bin/sh +# +# postgrey start/stop the postgrey greylisting deamon for postfix +# (priority should be smaller than that of postfix) +# +# Author: (c)2004-2006 Adrian von Bidder +# Based on Debian sarge's 'skeleton' example +# Distribute and/or modify at will. +# +# Version: $Id: postgrey.init 1436 2006-12-07 07:15:03Z avbidder $ +# +### BEGIN INIT INFO +# Provides: postgrey +# Required-Start: $syslog $local_fs $remote_fs +# Required-Stop: $syslog $local_fs $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start/stop the postgrey daemon +### END INIT INFO + +set -e + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/postgrey +DAEMON_NAME=postgrey +DESC="postfix greylisting daemon" +DAEMON_USER=postgrey + +PIDFILE=/var/run/$DAEMON_NAME/$DAEMON_NAME.pid +SCRIPTNAME=/etc/init.d/$DAEMON_NAME + +# Gracefully exit if the package has been removed. +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +# Read config file if it is present. +if [ -r /etc/default/$DAEMON_NAME ] +then + . /etc/default/$DAEMON_NAME +fi + +POSTGREY_OPTS="--pidfile=$PIDFILE --daemonize $POSTGREY_OPTS" +if [ -z "$POSTGREY_TEXT" ]; then + POSTGREY_TEXT_OPT="" +else + POSTGREY_TEXT_OPT="--greylist-text=$POSTGREY_TEXT" +fi + +ret=0 + +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $POSTGREY_OPTS "$POSTGREY_TEXT_OPT" \ + || return 2 +} + +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --user $DAEMON_USER --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --user $DAEMON_USER --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" + +} + +do_reload() +{ + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$DAEMON_NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$DAEMON_NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + reload|force-reload) + [ "$VERBOSE" != no ] && log_daemon_msg "Reloading $DESC" "$DAEMON_NAME" + do_reload + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + restart) + do_stop + do_start + ;; + status) + status_of_proc -p $PIDFILE $DAEMON "$DAEMON_NAME" 2>/dev/null + ret=$? + ;; + + *) + echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload|status}" >&2 + exit 1 + ;; +esac + +exit $ret + diff --git a/target/start-mailserver.sh b/target/start-mailserver.sh index 448c7725..d6fcd273 100644 --- a/target/start-mailserver.sh +++ b/target/start-mailserver.sh @@ -14,6 +14,10 @@ DEFAULT_VARS["ENABLE_FAIL2BAN"]="${ENABLE_FAIL2BAN:="0"}" DEFAULT_VARS["ENABLE_MANAGESIEVE"]="${ENABLE_MANAGESIEVE:="0"}" DEFAULT_VARS["ENABLE_FETCHMAIL"]="${ENABLE_FETCHMAIL:="0"}" DEFAULT_VARS["ENABLE_LDAP"]="${ENABLE_LDAP:="0"}" +DEFAULT_VARS["ENABLE_POSTGREY"]="${ENABLE_POSTGREY:="0"}" +DEFAULT_VARS["POSTGREY_DELAY"]="${POSTGREY_DELAY:="300"}" +DEFAULT_VARS["POSTGREY_MAX_AGE"]="${POSTGREY_MAX_AGE:="35"}" +DEFAULT_VARS["POSTGREY_TEXT"]="${POSTGREY_TEXT:="Delayed by postgrey"}" DEFAULT_VARS["ENABLE_SASLAUTHD"]="${ENABLE_SASLAUTHD:="0"}" DEFAULT_VARS["SMTP_ONLY"]="${SMTP_ONLY:="0"}" DEFAULT_VARS["VIRUSMAILS_DELETE_DELAY"]="${VIRUSMAILS_DELETE_DELAY:="7"}" @@ -90,6 +94,10 @@ function register_functions() { _register_setup_function "_setup_postfix_sasl" fi + if [ "$ENABLE_POSTGREY" = 1 ];then + _register_setup_function "_setup_postgrey" + fi + _register_setup_function "_setup_dkim" _register_setup_function "_setup_ssl" _register_setup_function "_setup_docker_permit" @@ -141,6 +149,12 @@ function register_functions() { # needs to be started before saslauthd _register_start_daemon "_start_daemons_opendkim" _register_start_daemon "_start_daemons_opendmarc" + + #postfix uses postgrey, needs to be started before postfix + if [ "$ENABLE_POSTGREY" = 1 ]; then + _register_start_daemon "_start_daemons_postgrey" + fi + _register_start_daemon "_start_daemons_postfix" if [ "$ENABLE_SASLAUTHD" = 1 ];then @@ -160,6 +174,7 @@ function register_functions() { _register_start_daemon "_start_daemons_clamav" fi + _register_start_daemon "_start_daemons_amavis" ################### << daemon funcs } @@ -487,6 +502,18 @@ function _setup_ldap() { return 0 } +function _setup_postgrey() { + notify 'inf' "Configuring postgrey" + sed -i -e 's/bl.spamcop.net$/bl.spamcop.net, check_policy_service inet:127.0.0.1:10023/' /etc/postfix/main.cf + sed -i -e "s/\"--inet=10023\"/\"--inet=10023 --delay=$POSTGREY_DELAY --max-age=$POSTGREY_MAX_AGE\"/" /etc/default/postgrey + TEXT_FOUND=`grep -i "POSTGREY_TEXT" /etc/default/postgrey | wc -l` + + if [ $TEXT_FOUND -eq 0 ]; then + printf "POSTGREY_TEXT=\"$POSTGREY_TEXT\"\n\n" >> /etc/default/postgrey + fi +} + + function _setup_postfix_sasl() { [ ! -f /etc/postfix/sasl/smtpd.conf ] && cat > /etc/postfix/sasl/smtpd.conf << EOF pwcheck_method: saslauthd @@ -1023,6 +1050,12 @@ function _start_daemons_clamav() { display_startup_daemon "/etc/init.d/clamav-daemon start" } +function _start_daemons_postgrey() { + notify 'task' 'Starting postgrey' 'n' + display_startup_daemon "/etc/init.d/postgrey start" +} + + function _start_daemons_amavis() { notify 'task' 'Starting amavis' 'n' display_startup_daemon "/etc/init.d/amavis start" diff --git a/test/email-templates/postgrey.txt b/test/email-templates/postgrey.txt new file mode 100644 index 00000000..69cd2e72 --- /dev/null +++ b/test/email-templates/postgrey.txt @@ -0,0 +1,12 @@ +HELO mail.external.tld +MAIL FROM: user@external.tld +RCPT TO: user1@localhost.localdomain +DATA +From: Docker Mail Server +To: Existing Local User +Date: Sat, 22 May 2010 07:43:25 -0400 +Subject: Postgrey Test Message +This is a test mail. + +. +QUIT \ No newline at end of file diff --git a/test/tests.bats b/test/tests.bats index b602a83e..2706da59 100644 --- a/test/tests.bats +++ b/test/tests.bats @@ -78,6 +78,59 @@ load 'test_helper/bats-assert/load' assert_success } +# +# postgrey +# + +@test "checking process: postgrey (disabled in default configuration)" { + run docker exec mail /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/postgrey'" + assert_failure +} + +@test "checking postgrey: /etc/postfix/main.cf correctly edited" { + run docker exec mail_with_postgrey /bin/bash -c "grep 'bl.spamcop.net, check_policy_service inet:127.0.0.1:10023' /etc/postfix/main.cf | wc -l" + assert_success + assert_output 1 +} + +@test "checking postgrey: /etc/default/postgrey correctly edited and has the default values" { + run docker exec mail_with_postgrey /bin/bash -c "grep '^POSTGREY_OPTS=\"--inet=10023 --delay=15 --max-age=35\"$' /etc/default/postgrey | wc -l" + assert_success + assert_output 1 + run docker exec mail_with_postgrey /bin/bash -c "grep '^POSTGREY_TEXT=\"Delayed by postgrey\"$' /etc/default/postgrey | wc -l" + assert_success + assert_output 1 +} + +@test "checking process: postgrey (postgrey server enabled)" { + run docker exec mail_with_postgrey /bin/bash -c "ps aux --forest | grep -v grep | grep '/usr/sbin/postgrey'" + assert_success +} + +@test "checking postgrey: there should be a log entry about a new greylisted e-mail user@external.tld in /var/log/mail/mail.log" { + #editing the postfix config in order to ensure that postgrey handles the test e-mail. The other spam checks at smtpd_recipient_restrictionswould interfere with it. + run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/permit_sasl_authenticated.*reject_unauth_destination,$//g' /etc/postfix/main.cf" + run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/reject_unauth_pipelining.*reject_unknown_recipient_domain,$//g' /etc/postfix/main.cf" + run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/reject_rbl_client.*inet:127\.0\.0\.1:10023$//g' /etc/postfix/main.cf" + run docker exec mail_with_postgrey /bin/sh -c "sed -ie 's/smtpd_recipient_restrictions = /smtpd_recipient_restrictions = check_policy_service inet:127.0.0.1:10023/g' /etc/postfix/main.cf" + + run docker exec mail_with_postgrey /bin/sh -c "/etc/init.d/postfix reload" + run docker exec mail_with_postgrey /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/postgrey.txt" + sleep 5 #ensure that the information has been written into the log + run docker exec mail_with_postgrey /bin/bash -c "grep -i 'action=greylist.*user@external\.tld' /var/log/mail/mail.log | wc -l" + assert_success + assert_output 1 +} + +@test "checking postgrey: there should be a log entry about the retried and passed e-mail user@external.tld in /var/log/mail/mail.log" { + sleep 20 #wait 20 seconds so that postgrey would accept the message + run docker exec mail_with_postgrey /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/postgrey.txt" + sleep 8 + run docker exec mail_with_postgrey /bin/sh -c "grep -i 'action=pass, reason=triplet found.*user@external\.tld' /var/log/mail/mail.log | wc -l" + assert_success + assert_output 1 +} + # # imap #