A mailing list server I administer was recently targeted by someone attempting to subscribe to (as far I can tell) every single publicly visible list. I can only assume the reason was spam or similar, but it’s definitely unwanted – the lists are distinct enough in use that multiple requests like this is obviously not right. Of course, for the people who actually run the lists it just looked like a standard subscription request. A few subscribed the address (public lists, seemed fine to them) and some rejected it (private lists for known groups of people).

As is normal, I will generally spend time writing an automated way to fix this than do it manually (150+ lists is a lot to manually check, and it would bore me to tears). This situation was no different, and it’s always possible the same thing will happen again.

So I wrote the following script to do it. It removes pending requests for an address, and any actual subscriptions. It doesn’t remove the address if it’s in ‘confirm’ state at present, as it seems that’s harder to do with the usual Mailman API.

#! /usr/bin/env python

verbose = 0
quiet = 0
dryrun = 1

def syntax():
  print "Syntax: ", sys.argv[0], "<address>"
  sys.exit(1)

import sys

sys.path.insert(0, "/usr/lib/mailman/bin")
import paths
from Mailman import mm_cfg
from Mailman import Errors
from Mailman import MailList
from Mailman import Utils

def list_remove_all(name, email):
  if verbose:
    print name

  m = MailList.MailList(name, lock=1)

  try:
    pending = m.GetSubscriptionIds()
    for id in pending:
      addr = m.GetRecord(id)[1]
      if addr == email:
        if not quiet:
          print "%s: remove pending"%(name)
        if not dryrun:
          m.HandleRequest(id, mm_cfg.DISCARD)

    addrs = m.getMembers()
    for addr in addrs:
      if addr == email:
        if not quiet:
          print "%s: remove member"%(name)
        try:
          if not dryrun:
            m.ApprovedDeleteMember(email, "global_remove_address", 0, 0)
        except NotAMemberError:
          pass

    m.Save()

  finally:
    m.Unlock()

try:
  email = sys.argv[2]
  syntax()
except IndexError:
  pass

try:
  email = sys.argv[1]
except IndexError:
  syntax()

[list_remove_all(list, email) for list in Utils.list_names()]

It’s not finished or polished code; don’t come running for help if it deletes all your list subscribers, etc, and make a backup before you use it! dryrun needs to be set to 0 before it will actually do anything (but still make a backup first). As it’s Python, remember to get the indents correct.

Leave a Reply

Your email address will not be published.