Skip to content

Instantly share code, notes, and snippets.

@MattBlissett
Created July 11, 2024 09:43
Show Gist options
  • Save MattBlissett/c1f58dcc182423f48e2305b3d91ea44d to your computer and use it in GitHub Desktop.
Save MattBlissett/c1f58dcc182423f48e2305b3d91ea44d to your computer and use it in GitHub Desktop.
Postfix SMTP REJECT non-subscribers for Mailman 3 mailing list

This is an implementation of https://code.launchpad.net/~jimpop/mailman/check_subscriber for Mailman 3.

A Postfix milter checks whether an email address is a subscriber to a mailing list. If it is not, it rejects the mail during the SMTP transaction.

This avoids sending backscatter-bounces, and avoids blackholing messages from non-subscribers — the sender's MTA will generate an error.

In master.cf:

127.0.0.1:26005 inet    n       n       n       -       1       spawn
        user=nobody argv=/etc/postfix/check_mailman_subscriber test@lists.example.org

And in main.cf:

smtpd_restriction_classes = subscribers_test
                
smtpd_sender_restrictions = check_recipient_access hash:/etc/postfix/check_subscribers, permit

subscribers_test = check_sender_access tcp:127.0.0.1:26005, permit

And in check_subscribers:

test@lists.example.org                subscribers_test
#!/usr/bin/python3
#
# Copyright (C) 1998-2024 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
""" Returns a status code (200 == not found, 500 == found, 400 == exception) if an email address is subscribed to the listname at sys.argv[1].
"""
import requests
import sys
import syslog
RESPONSE_200 = "200 REJECT You (%s) must be a subscriber to post to the %s mailing list. Visit https://lists.tdwg.org/ to subscribe. Your email has not been sent.\n"
REST_API = "http://localhost:8001/3.1"
REST_USER = "restadmin"
REST_PASS = "restpass"
def main():
if (sys.stdin.isatty()):
syslog.syslog("check_subscriber: stdin is a tty")
sys.stderr.write("check_subscriber: stdin is a tty")
print("400 ERROR\n")
sys.exit(1)
line = sys.stdin.readline()
if (not sys.argv[1] or not line.startswith("get")):
syslog.syslog("check_subscriber: Arguments invalid")
print("400 ARGUMENTS\n")
sys.exit(1)
items = line.split()
email = items[1].lower()
mlist = sys.argv[1]
try:
r = requests.get('%s/lists/%s/roster/member?fields=email' % (REST_API, mlist), auth=(REST_USER, REST_PASS))
except:
syslog.syslog("check_subscriber: %s Error retrieving members of list '%s'" % (email, mlist))
print("400 REST EXCEPTION\n")
sys.exit(1)
if r.status_code == 404:
syslog.syslog("check_subscriber: %s Error looking up in list '%s'" % (email, mlist))
print("400 LIST NOT FOUND\n")
sys.exit(1)
if r.status_code != 200:
syslog.syslog("check_subscriber: %s Error looking up in list '%s'" % (email, mlist))
print("400 REST ERROR\n")
sys.exit(1)
list_data = r.json()
if 'entries' in list_data:
for e in list_data['entries']:
if 'email' in e and e['email'].lower() == email.lower():
syslog.syslog("check_subscriber: %s Found in list '%s'" % (email, mlist))
print("500 FOUND IN LIST\n")
sys.exit(0)
syslog.syslog("check_subscriber: %s Not-Found in list '%s'" % (email, mlist))
print((RESPONSE_200 % (email, mlist))[:4000])
sys.exit(0)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment