git » chasquid » next » tree

[next] / etc / chasquid / hooks / post-data

#!/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" && exit 1
			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