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.
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?
Hi,
What i need write to master.cf and main.cf ? How to run this script ?
I’ve updated the article with a link to the postfix.org documentation which shows how to integrate it into master.cf
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
Thanks! updated.
That code really help me. Thank you