#!/usr/bin/python """Maintain mailman-xmail integration automatically. Read cmdalias symlinks to mailman-template.tab and the available mailman lists, and keep them in sync. """ MAILMAN_HOME = '/usr/local/mailman' XMAIL_HOME = '/var/MailRoot' CMD_ALIAS_TEMPLATE = XMAIL_HOME + '/bin/mailman-template.tab' import os, sys SUFFIXES = ('admin', 'bounces', 'confirm', 'join', 'leave', 'owner', 'request', 'subscribe', 'unsubscribe') def readAliasDomains(): """Read the aliasdomains.tab file Returns a dictionary with alias -> real domain entries """ alias_domains = {} for line in open(XMAIL_HOME + '/aliasdomain.tab').readlines(): alias, orig = line.strip().split('\t') alias = alias.strip().strip('"') orig = orig.strip().strip('"') alias_domains[alias] = orig return alias_domains def getMailmanLists(): """List available Mailman lists Return a dictionary of virtual hosts, each entry with a list of mailinglists for the given domain. """ # Insert the Mailman path into the module search path sys.path.insert(0, MAILMAN_HOME) from Mailman import Utils, MailList # Lists, grouped by their virtual domain domain_lists = {} aliases = readAliasDomains() for name in Utils.list_names(): list = MailList.MailList(name, lock=0) host = list.host_name host = aliases.get(host, host) lists = domain_lists.setdefault(host, []) lists.append(list.internal_name()) return domain_lists def stripSuffix(name): """Return the list name, stripped of any alias suffix""" if name.endswith('.tab'): name = name[:-4] for suffix in SUFFIXES: if name.endswith(suffix) and name[-(len(suffix) + 1)] == '-': return name[:-(len(suffix) + 1)] return name def getSymlinkedLists(): """List listnames to which cmdalias symlinks exist. Return a dictionary of virtual hosts, each entry with a list of mailinglists for the given domain. """ domains = () alias_dir = XMAIL_HOME + '/cmdaliases' for entry in os.listdir(alias_dir): if os.path.isdir(alias_dir + '/' + entry): domains += (entry,) # Lists, grouped by their virtual domain domain_lists = {} for domain in domains: lists = domain_lists[domain] = [] domain_dir = alias_dir + '/' + domain for entry in os.listdir(domain_dir): if not os.path.islink(domain_dir + '/' + entry): continue if os.readlink(domain_dir + '/' + entry) != CMD_ALIAS_TEMPLATE: continue name = stripSuffix(entry) if name not in lists: lists.append(name) return domain_lists def createLinks(domain, listname): """Create cmdalias entries for a given domain and listname""" base = '%s/cmdaliases/%s/%s' % (XMAIL_HOME, domain, listname) try: os.symlink(CMD_ALIAS_TEMPLATE, base + '.tab') except OSError: pass for suffix in SUFFIXES: try: os.symlink(CMD_ALIAS_TEMPLATE, '%s-%s.tab' % (base, suffix)) except OSError: pass def removeLinks(domain, listname): """Remove cmdalias entries for a given domain and listname""" base = '%s/cmdaliases/%s/%s' % (XMAIL_HOME, domain, listname) try: os.unlink(base + '.tab') except OSError: pass for suffix in SUFFIXES: try: os.unlink('%s-%s.tab' % (base, suffix)) except OSError: pass def checkLinks(domain, listname): """Verify that all aliases for a give list are symlinked Return 1 when all are there. """ domain_dir = '%s/cmdaliases/%s' % (XMAIL_HOME, domain) entries = os.listdir(domain_dir) if listname + '.tab' not in entries: return 0 for suffix in SUFFIXES: filename = '%s-%s.tab' % (listname, suffix) if filename not in entries: return 0 return 1 def findDeltas(mailmanLists, configuredLists): """Find those mailman lists that are removed or added Returns three lists; one for adding, one for removing, one remainders. Each lists consist of (domain, listname) tuples. """ add = [] # (domain, list) tuples to add links for remove = [] # (domain, list) tuples to remove links for remainder = [] # (domain, list) tuples of lists that remain addDomains = [domain for domain in mailmanLists if domain not in configuredLists] removeDomains = [domain for domain in configuredLists if domain not in mailmanLists] remainingDomains = [domain for domain in configuredLists if domain in mailmanLists] for domain in addDomains: add.extend([(domain, list) for list in mailmanLists[domain]]) for domain in removeDomains: remove.extend([(domain, list) for list in configuredLists[domain]]) for domain in remainingDomains: add.extend([(domain, list) for list in mailmanLists[domain] if list not in configuredLists[domain]]) remove.extend([(domain, list) for list in configuredLists[domain] if list not in mailmanLists[domain]]) remainder.extend([(domain, list) for list in mailmanLists[domain] if list in configuredLists[domain]]) return add, remove, remainder def usage(exit=0): print """\ Usage: %s [options] Keep XMail cmdaliases up-to-date with available mailman lists. Options: -a, --add Only add missing cmdaliases, don't remove any. -r, --remove Only remove cmdaliases for non-existant lists, don't add any. -f, --fix Repair missing cmdaliases for existing lists. -d, --dryrun Only state what we'd be doing, don't make any real changes. -q, --quiet Don't print anything, just do the job. -h, --help This message """ % sys.argv[0] sys.exit(exit) if __name__ == '__main__': import getopt opts, args = getopt.getopt(sys.argv[1:], 'arfdqh', ('add', 'remove', 'fix' 'dryrun', 'quiet', 'help')) if args: usage(2) quiet = 0 dryrun = 0 do_add = 1 do_remove = 1 do_fix = 0 for opt, val in opts: if opt in ('-h', '--help'): usage(1) if opt in ('-a', '--add'): do_add, do_remove = 1, 0 if opt in ('-r', '--remove'): do_add, do_remove = 0, 1 if opt in ('-f', '--fix'): do_fix = 1 if opt in ('-d', '--dryrun'): dryrun = 1 if opt in ('-q', '--quiet'): quiet = 1 if quiet and dryrun: sys.exit(0) mailmanLists = getMailmanLists() configuredLists = getSymlinkedLists() add, remove, remainder = findDeltas(mailmanLists, configuredLists) if not do_add: add = [] if not do_remove: remove = [] if not add and not remove and not do_fix: if not quiet: print "Nothing to do." sys.exit(0) for domain, list in remove: if not quiet: print "Removing %s@%s list" % (list, domain) if not dryrun: removeLinks(domain, list) for domain, list in add: if not quiet: print "Adding %s@%s list" % (list, domain) if not dryrun: createLinks(domain, list) if do_fix: repairs = 0 for domain, list in remainder: if checkLinks(domain, list): continue repairs += 1 if not quiet: print "Repairing %s@%s list" % (list, domain) if not dryrun: createLinks(domain, list) if repairs: if not quiet: print "Fixed %d lists" % repairs else: if not quiet: print "List cmdalias entries were in good shape."