Categories
email postfix python

python content_filter for Postfix (rewriting the subject)

A python script which can be used as a Postfix content_filter to headers email going through it … (example shows changing the Subject).

To use, you’d need to add a content_filter argument to one of the services defined in master.cf as described here

#!/usr/bin/python
from email import Parser
import smtplib
import sys
import logging
from subprocess import Popen, PIPE
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)s %(message)s',
                    filename='/tmp/content-filter.log',
                    filemode='a')

# Get the CLI arguments.
try:
    cli_from = sys.argv[2].lower()
    cli_to = sys.argv[4:]
    logging.debug("To / From : %r" % sys.argv)
except:
    logging.error("Invalid to / from : %r" % sys.argv)
    sys.exit(69) # postfix will bounce the mail. retrying bad args won't work

logging.debug("From : %s, to : %r" % (cli_from, cli_to))
# Get the email content from STDIN.
content = ''.join(sys.stdin.readlines())
p = Parser.Parser()
parsed = p.parsestr(content, True)
#logging.debug("email source : %s" % parsed.as_string())

old_subject = parsed.get('Subject');

# remove the old header, and add a new one.
del parsed['subject']
parsed['Subject'] = "New Subject -- " + old_subject

# convert it back to a big string.
content = str(parsed)
# and let's try reinjecting it into Postfix.
command = ["/usr/sbin/sendmail", "-G", "-i", "-f", cli_from, cli_to]
stdout = ''
stderr = ''
retval = 0
try :
    process = Popen(command, stdin=PIPE)
    (stdout, stderr) = process.communicate(content);
    retval = process.wait()
    if retval == 0:
        logging.debug("Mail resent via sendmail, stdout: %s, stderr: %s" % (stdout, stderr))
        sys.exit(0)    
    else:
        raise Exception("retval not zero - %s" % retval)
except Exception, e:
    print "Error re-injecting via /usr/sbin/sendmail."
    logging.error("Error resending mail %s -- stdout:%s, stderr:%s, retval: %s" % (e, stdout, stderr, retval))
    sys.exit(75) # tempfail, we hope.

8 replies on “python content_filter for Postfix (rewriting the subject)”

getting this error trying this as a “cat FILE | ./filt.py -f — <from"

ERROR Error resending mail execv() arg 2 must contain only strings — stdout:, stderr:, retval: 0

Ideas?

Im having issues getting this to work as expected…. this is my config at the moment, not sure if its right but i tried to make it from what i found around on the net…

master.cf
10025 inet n – n – – smtpd
-o receive_override_options=no_header_body_checks
-o smtpd_recipient_restrictions=${my_switcher_restrictions}
policy unix – n n – 0 spawn user=nobody argv=/etc/postfix/rewrite_filter

main.cf
my_switcher_restrictions = check_policy_service unix:private/policy

header_checks
/^X-Relay-Domain: relay.server.com / FILTER smtp:127.0.0.1:10025

rewrite_filter
#!/usr/bin/python
from email import Parser

import smtplib
import sys
import logging
import re

logging.basicConfig(level=logging.DEBUG,
format=’%(asctime)s %(levelname)s %(message)s’,
filename=’/tmp/content-filter.log’,
filemode=’a’)

# Get the CLI arguments.
try:
cli_from = sys.argv[2].lower()
cli_to = sys.argv[4:]
logging.debug(“To / From : %r” % sys.argv)
except:
logging.error(“Invalid to / from : %r” % sys.argv)
sys.exit(69) # postfix will bounce the mail. retrying bad args won’t work

#logging.debug(“From : %s, to : %r” % (cli_from, cli_to))
# Get the email content from STDIN.
content = ”.join(sys.stdin.readlines())
p = Parser.Parser()
parsed = p.parsestr(content, True)
logging.debug(“email source : %s” % parsed.as_string())

old_received = parsed.get(‘Received’);
search_string = re.compile(ur’for ‘, re.MULTILINE)

new_to = re.search(search_string, old_received)

# remove the old header, and add a new one.
del parsed[‘To’]
del parsed[‘X-Relay-Domain’]
parsed[‘To’] = new_to

# convert it back to a big string.
content = str(parsed)
# and let’s try reinjecting it into Postfix.
command = [“/usr/sbin/sendmail”, “-G”, “-i”, “-f”, cli_from, cli_to]
stdout = ”
stderr = ”
retval = 0
try :
process = Popen(command, stdin=PIPE)
(stdout, stderr) = process.communicate(content);
retval = process.wait()
if retval == 0:
logging.debug(“Mail resent via sendmail, stdout: %s, stderr: %s” % (stdout, stderr))
sys.exit(0)
else:
raise Exception(“retval not zero – %s” % retval)
except Exception, e:
print “Error re-injecting via /usr/sbin/sendmail.”
logging.error(“Error resending mail %s — stdout:%s, stderr:%s, retval: %s” % (e, stdout, stderr, retval))
sys.exit(75) # tempfail, we hope.

the X-Relay-Domain is set on specific emails, and in these cases im trying to pull the “for” email from the Received header (needed as the to field is wrong because of other manipulation im doing)

there are a few issues, the arguments are not set by default, so im not sure how to set them correctly, if i bypass the arguments, the error log contains this and i cant change the values in the header i want to change, note im trying to do this as part of the header_checks, not sure if that will even work…:

DEBUG email source :
request=smtpd_access_policy
protocol_state=RCPT
protocol_name=ESMTP
client_address=127.0.0.1
client_name=localhost
client_port=53555
reverse_client_name=localhost
helo_name=mx1.server.com
sender=sender@server.com
recipient=recipient@gmail.com
recipient_count=0
queue_id=
instance=7fbc.570609ea.237fd.0
size=784
etrn_domain=
stress=
sasl_method=
sasl_username=
sasl_sender=
ccert_subject=
ccert_issuer=
ccert_fingerprint=
ccert_pubkey_fingerprint=
encryption_protocol=TLSv1.2
encryption_cipher=AECDH-AES256-SHA
encryption_keysize=256

Any help would be much appreciated 🙂

Thanks for that code, that was helpful. I inspired myself from it to create the following boilerplate code: https://gist.github.com/Xowap/e998dbdf9cf8022b7a7b99503599a16b

All you have to do (besides configuring Postfix) is to fill the “apply_filter()” function which takes as arguments the from email, list of “to” emails and the content of the email as a “email.message.Message” instance. The function is supposed to return filtered output in the same order.

not sure if you forgot this or its something with the python version your using. but i had to add this to the top of my python file:
from subprocess import Popen, PIPE

Leave a Reply

Your email address will not be published. Required fields are marked *