#!/bin/bash # # This file is an example post-data hook that will run standard filtering # utilities if they are available. # # - greylist (from greylistd) to do greylisting. # - spamc (from Spamassassin) to filter spam. # - rspamc (from rspamd) or chasquid-rspamd to filter spam. # - clamdscan (from ClamAV) to filter virus. # - dkimsign (from driusan/dkim or dkimpy) to do DKIM signing. # # If it exits with code 20, it will be considered a permanent error. # Otherwise, temporary. set -e # Note greylistd needs you to add the user to the "greylist" group: # usermod -a -G greylist mail if [ "$AUTH_AS" == "" ] && [ "$SPF_PASS" == "0" ] && \ command -v greylist >/dev/null && \ groups | grep -q greylist; then REMOTE_IP=$(echo "$REMOTE_ADDR" | rev | cut -d : -f 2- | rev) if ! greylist update "$REMOTE_IP" "$MAIL_FROM" 1>&2; then echo "greylisted, please try again" exit 75 # temporary error fi echo "X-Greylist: pass" fi TF="$(mktemp --tmpdir post-data-XXXXXXXXXX)" trap 'rm "$TF"' EXIT # Save the message to the temporary file, so we can pass it on to the various # filters. cat > "$TF" if command -v spamc >/dev/null; then if ! SL=$(spamc -c - < "$TF") ; then echo "spam detected" exit 20 # permanent fi echo "X-Spam-Score: $SL" fi # Spam filter through rspamd. # # Use chasquid-rspamd (from https://github.com/Thor77/chasquid-rspamd) if # available, otherwise fall back to rspamc. if command -v chasquid-rspamd >/dev/null; then chasquid-rspamd < "$TF" 2>/dev/null elif command -v rspamc >/dev/null; then # Note the actions emitted by rspamc come from the thresholds # configured in /etc/rspamd/actions.conf. # The ones handled here are common defaults, but they might require # adjusting to match your rspamd configuration. # Note that greylisting is disabled in rspamc by design, so the # "greylist" action is ignored here to prevent false rejections. ACTION=$( rspamc < "$TF" 2>/dev/null | grep Action: | cut -d " " -f 2- ) case "$ACTION" in reject) echo "spam detected" exit 20 # permanent error ;; esac echo "X-Spam-Action:" "$ACTION" fi if command -v clamdscan >/dev/null; then if ! clamdscan --no-summary --infected - < "$TF" 1>&2 ; then echo "virus detected" exit 20 # permanent fi echo "X-Virus-Scanned: pass" fi # DKIM sign with either driusan/dkim or dkimpy. # # Do it only if all the following are true: # - User has authenticated. # - dkimsign binary exists. # - domains/$DOMAIN/dkim_selector file exists. # - certs/$DOMAIN/dkim_privkey.pem file exists. # # Note this has not been thoroughly tested, so might need further adjustments. if [ "$AUTH_AS" != "" ] && command -v dkimsign >/dev/null; then DOMAIN=$( echo "$MAIL_FROM" | cut -d '@' -f 2 ) if [ -f "domains/$DOMAIN/dkim_selector" ] \ && [ -f "certs/$DOMAIN/dkim_privkey.pem" ]; then # driusan/dkim and dkimpy both provide the same binary (dkimsign) but # take different arguments, so we need to tell them apart. # This is awful but it should work reasonably well. if dkimsign --help 2>&1 | grep -q -- --identity; then # dkimpy dkimsign \ "$(cat "domains/$DOMAIN/dkim_selector")" \ "$DOMAIN" \ "certs/$DOMAIN/dkim_privkey.pem" \ < "$TF" > "$TF.dkimout" # dkimpy doesn't provide a way to just show the new # headers, so we have to compute the difference. # ALSOCHANGE(test/t-19-dkimpy/config/hooks/post-data) ! diff --changed-group-format='%>' \ --unchanged-group-format='' \ "$TF" "$TF.dkimout" rm "$TF.dkimout" else # driusan/dkim dkimsign -n -hd \ -key "certs/$DOMAIN/dkim_privkey.pem" \ -s "$(cat "domains/$DOMAIN/dkim_selector")" \ -d "$DOMAIN" \ < "$TF" fi fi fi