Content Filters In Postfix

kfullert's picture
Postfix / Sieve setup

Postfix/Mysql/Dovecot/Amavis-new in standard virtual hosting install
Mail stored under /var/vmail/domain/username/Maildir

/etc/postfix/main.cf

<snip>
virtual_transport = dovecot
dovecot_destination_recipient_limit = 1
deliver-filter_destination_recipient_limit = 1
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions = reject_unauth_pipelining,permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination,check_sender_access pcre:/etc/postfix/content_filter.pcre
content_filter =
receive_override_options = no_address_mappings
</snip>

/etc/postfix/content_filter.pcre - run postmap /etc/postfix/content_filter.pcre after editing

/^/ FILTER smtp-amavis:[127.0.0.1]:10024

/etc/postfix/master.cf
127.0.0.1:10025 injects mail back in from amavis-new
127.0.0.1:10026 injects mail back in from deliver-filter
Flag O on deliver-filter adds the X-Original-To header we need

smtp -> smtp-amavis -> deliver-filter -> dovecot deliver -> sieve -> Maildir

<snip>
dovecot unix - n n - - pipe
flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -d ${recipient}

smtp-amavis unix - - n - 2 smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20

127.0.0.1:10025 inet n - - - - smtpd
-o content_filter=deliver-filter:dummy
-o local_recipient_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
-o local_header_rewrite_clients=

127.0.0.1:10026 inet n - n - - smtpd
-o content_filter=
-o smtpd_authorized_xforward_hosts=127.0.0.0/8
-o local_recipient_maps=
-o virtual_mailbox_maps=
-o virtual_alias_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks_style=host
-o mynetworks=127.0.0.0/8
-o strict_rfc821_envelopes=yes
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_address_mappings

deliver-filter unix - n n - 10 pipe
flags=ORq user=filter null_sender=
argv=/usr/local/bin/deliver_filter.sh -f ${sender} -- ${recipient}

</snip>

/usr/local/bin/deliver_filter.sh
Runs as user filter
Stores incoming mail in /var/spool/filter
Pipes incoming mail to disk, calls kFilter.py and then uses putmail.py to inject the mail back into 127.0.0.1:10026

#!/bin/bash

INSPECT_DIR="/var/spool/filter"
SENDMAIL="/usr/local/bin/putmail.py "
export HOME="/var/spool/filter"
EX_TEMPFAIL=75
EX_UNAVAILABLE=69

# trap "rm -rf in.$$" 0 1 2 3 15

cd $INSPECT_DIR || {
echo "$INSPECT_DIR does not exist"; exit $EX_TEMPFAIL; }

cat > in.$$ || {
echo "Cannot save mail to file"; exit $EX_TEMPFAIL; }

/usr/local/bin/kFilter.py in.$$

$SENDMAIL "$@" < in.$$

exit $?

putmail.py - http://putmail.sourceforge.net/home.html

/var/spool/filter/.putmail/putmailrc

[config]
server = localhost
email = dummy@example.com
port = 10026

/usr/local/bin/kFilter.py
Opens the mail message specified on the command line
Looks for the X-Original-To header, removes all . and - from the string, so example-name@example.co.uk becomes examplename@examplecouk
New string gets set to the header X-Kenwa-Deliver
Message is saved back, overwriting the original file

#!/usr/bin/python

import email
import sys
import re

if len(sys.argv) != 2:
print "Usage: %s <message file>" % sys.argv[0]
sys.exit()

f = PrivoxyWindowOpen(sys.argv[1], 'r')
msg = email.message_from_file(f)
f.close()
if msg.has_key('X-Original-To'):
original_to = msg.get('X-Original-To')
original_to = re.sub('[\.\-]', '', original_to)
if msg.has_key('X-Kenwa-Deliver'):
msg.__delitem__('X-Kenwa-Deliver')
msg.add_header('X-Kenwa-Deliver', original_to)
f = PrivoxyWindowOpen(sys.argv[1], 'w')
f.write(msg.as_string())
f.close()

/var/vmail/domain/username/.dovecot-sieve

require ["fileinto", "variables"]; if header :contains "X-Spam-Flag" ["YES"] { fileinto "spam"; stop; } if header :matches "X-Kenwa-Deliver" "*@*" {
set :lower "localpart" "${1}";
set :lower "domain" "${2}";
fileinto "INBOX.${domain}.${localpart}";
stop;
}



Requires dovecot-common from lenny-backports (1.2.5) to allow the variables extension