Skip to content

Instantly share code, notes, and snippets.

@noah
Last active July 7, 2016 14:15
Show Gist options
  • Save noah/468696a5862d19463dd32f3a5e5347b2 to your computer and use it in GitHub Desktop.
Save noah/468696a5862d19463dd32f3a5e5347b2 to your computer and use it in GitHub Desktop.
Programmatically add entries to postfix sender_access file.
#!/usr/bin/env python2
import sys
import smtplib
from subprocess import call
from email.parser import Parser
from email.utils import parseaddr, make_msgid
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# BlockSpammer.py <[email protected]>
#
# MIT license.
#
# Receive an message which contains a forwarded message and (optionally) a
# supplied block message. Parse the forwarded email, extracting recipient and
# a list of email addresses to block. Update the Postfix configuration to
# block those addresses. Email the original recipient back, letting her know
# those addresses are in fact blocked.
#
#
# Needs:
# smtpd_recipient_restrictions =
# check_recipient_access hash:/etc/postfix/recipient_access
#
# containing minimally:
# tehblockaddressss@mydomain FILTER blockspammer:dummy
#
# and
# blockspammer unix - n n - - pipe flags=D user=someuser argv=/usr/bin/sudo /path/to/BlockSpammer.py
#
# and
# tehblockaddressss needs to be in /etc/postfix/aliases
msg = Parser().parse(sys.stdin)
msg_from = msg['From']
# collection of addresses to block
block = set()
# default block message for postfix sender_access REJECT block
block_message = 'blocked'
for part in msg.walk():
# print part.get_content_type(), '*'*80; print part
# multiparts are just containers
if part.get_content_maintype() == 'multipart': continue
if part.get_content_type() == 'message/rfc822': # it's a forwarded message
fmsg = part.get_payload()[0]
# who the forwarded message was ultimately addressed to
real_recipient = fmsg['Delivered-To']
for h in ['From', 'Return-Path']:
a = parseaddr(fmsg[h])[1]
if a: block.add(a)
# MUST break so that additional parts sent by the spammer will not be
# processed. NB this relies on parts being ordered .... ;[
break
else: # it's a message body from me
# override the default (uh oh, found a spammer we really don't like)
block_message = part.get_payload()
block = list(block)
# print 'to', real_recipient
# print 'block', list(block)
# print 'message', block_message
# now add to the postfix block list
blocked = []
BLOCKFILE='/etc/postfix/sender_access'
with open(BLOCKFILE, 'a') as blockfile:
for e in block:
assert "mydomain" not in e
# this works with pcre:/ type tables. change the string format for something else
blockfile.write('/{}/\t\tREJECT {}.\n'.format(e, block_message))
blocked.append(e)
# update the postfix map
call(["/usr/bin/postmap", BLOCKFILE])
# reply to the complaining email
outer = MIMEMultipart("mixed")
rmsg = MIMEText(
'blocked:\n\t{}\nmessage:\n\t{}'.format('\n\t'.join(blocked), block_message), "plain")
rmsg["Message-ID"] = make_msgid()
rmsg["In-Reply-To"] = fmsg["Message-ID"]
rmsg["References"] = fmsg["Message-ID"]
rmsg["Subject"] = "Re: "+ fmsg["Subject"]
rmsg["To"] = real_recipient
rmsg["From"] = "tehblockaddressss@mydomain"
outer.attach(rmsg)
outer.attach(fmsg)
s = smtplib.SMTP('localhost')
s.sendmail("me@mydomain", [real_recipient], rmsg.as_string())
s.quit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment