diff options
| -rw-r--r-- | Mailman/MTA/Postfix.py | 138 |
1 files changed, 105 insertions, 33 deletions
diff --git a/Mailman/MTA/Postfix.py b/Mailman/MTA/Postfix.py index b6d7102f9..5e232f9e1 100644 --- a/Mailman/MTA/Postfix.py +++ b/Mailman/MTA/Postfix.py @@ -29,6 +29,7 @@ import fcntl from stat import * from Mailman import mm_cfg +from Mailman import Utils from Mailman import LockFile from Mailman.i18n import _ from Mailman.MTA.Utils import makealiases @@ -39,6 +40,13 @@ LOCKFILE = os.path.join(mm_cfg.LOCK_DIR, 'creator') +def virtual_files(hostname): + textfile = os.path.join(mm_cfg.DATA_DIR, 'virtual-') + hostname + dbfile = textfile + '.db' + return dbfile, textfile + + + # Here's the deal with locking. In order to assure that Postfix doesn't read # the file while we're writing updates, we should drop an exclusive advisory # lock on the file. Ideally, we'd specify the `l' flag to dbhash.open() which @@ -76,7 +84,9 @@ def makelock(): def addlist(mlist, db, fp): listname = mlist.internal_name() fieldsz = len(listname) + len('-request') - # Seek to the end of the file, but if it's empty write the standard + loopaddr = Utils.get_site_email(mlist.host_name, 'loop') + loopmbox = os.path.join(mm_cfg.DATA_DIR, 'owner-bounces.mbox') + # Seek to the end of the text file, but if it's empty write the standard # disclaimer, and the loop catch address. fp.seek(0, 2) if not fp.tell(): @@ -86,12 +96,11 @@ def addlist(mlist, db, fp): # unless you know what you're doing, and can keep the two files properly # in sync. If you screw it up, you're on your own. """ - loopaddr = mm_cfg.MAILMAN_SITE_LIST + '-loop' - loopmbox = os.path.join(mm_cfg.DATA_DIR, 'owner-bounces.mbox') - print >> fp, '# For breaking bounce loops' - print >> fp, '%s: %s' % (loopaddr, loopmbox) + print >> fp, '# For breaking bounce loops to the site list owners' + print >> fp, '%s: %s' % (Utils.ParseEmail(loopaddr)[0], loopmbox) print >> fp - db[loopaddr + '\0'] = loopmbox + '\0' + # Always output the loop-stopper address + db[loopaddr + '\0'] = loopmbox + '\0' # The text file entries get a little extra info print >> fp, '# STANZA START:', listname print >> fp, '# CREATED:', time.ctime(time.time()) @@ -102,7 +111,7 @@ def addlist(mlist, db, fp): # YP_MASTER_NAME. db[k + '\0'] = v + '\0' # Format the text file nicely - print >> fp, k + ':', ((fieldsz - len(k)) * ' '), v + print >> fp, k + ':', ((fieldsz - len(k) + 1) * ' '), v # Always update YP_LAST_MODIFIED db['YP_LAST_MODIFIED'] = '%010d' % time.time() # Add a YP_MASTER_NAME only if there isn't one already @@ -114,30 +123,69 @@ def addlist(mlist, db, fp): -def create(mlist, cgi=0): - # Acquire the global list database lock - lock = makelock() - lock.lock() +def addvirtual(mlist, db, fp): + listname = mlist.internal_name() + fieldsz = len(listname) + len('request') + hostname = mlist.host_name + loopaddr = Utils.get_site_email(mlist.host_name, 'loop') + mailbox, domain = Utils.ParseEmail(loopaddr) + # Seek to the end of the text file, but if it's empty write the standard + # disclaimer, and the loop catch address. + fp.seek(0, 2) + if not fp.tell(): + print >> fp, """\ +# This file is generated by Mailman, and is kept in sync with the binary hash +# file virtual-mailman.db. YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you +# know what you're doing, and can keep the two files properly in sync. If you +# screw it up, you're on your own. +""" + # The entry which designates this as is a Postfix-style virtual map + print >> fp, hostname, '\tIGNORE' + print >> fp, '# For breaking bounce loops to the site list owners' + print >> fp, '%s: %s' % (loopaddr, mailbox) + print >> fp + # Add the magic bit that designates this as a virtual domain + db[hostname + '\0'] = 'IGNORE\0' + # Always output the loop-stopper address + db[loopaddr + '\0'] = loopaddr + '\0' + # The text file entries get a little extra info + print >> fp, '# STANZA START:', listname + print >> fp, '# CREATED:', time.ctime(time.time()) + # Now add all the standard alias entries + for k, v in makealiases(listname): + fqdnaddr = '%s@%s' % (k, hostname) + # Every key and value in the dbhash file as created by Postfix + # must end in a null byte. That is, except YP_LAST_MODIFIED and + # YP_MASTER_NAME. + db[fqdnaddr + '\0'] = k + '\0' + # Format the text file nicely + print >> fp, fqdnaddr + ':', ((fieldsz - len(k) + 1) * ' '), k + # Finish the text file stanza + print >> fp, '# STANZA END:', listname + print >> fp + + + +def do_create(mlist, dbfile, textfile, func): lockfp = None try: # First, open the dbhash file using built-in open so we can acquire an # exclusive lock on it. See the discussion above for why we do it # this way instead of specifying the `l' option to dbhash.open() - lockfp = open(DBFILE) + lockfp = open(dbfile) fcntl.flock(lockfp.fileno(), fcntl.LOCK_EX) - db = dbhash.open(DBFILE, 'c') + db = dbhash.open(dbfile, 'c') # Crack open the plain text file try: - fp = open(TEXTFILE, 'r+') + fp = open(textfile, 'r+') except IOError, e: if e.errno <> errno.ENOENT: raise omask = os.umask(007) try: - fp = open(TEXTFILE, 'w+') + fp = open(textfile, 'w+') finally: os.umask(omask) - - addlist(mlist, db, fp) + func(mlist, db, fp) # And flush everything out to disk db.sync() fp.close() @@ -145,46 +193,59 @@ def create(mlist, cgi=0): if lockfp: fcntl.flock(lockfp.fileno(), fcntl.LOCK_UN) lockfp.close() - lock.unlock(unconditionally=1) + + +def create(mlist, cgi=0, nolock=0): + # Acquire the global list database lock + lock = None + if not nolock: + lock = makelock() + lock.lock() + # Do the aliases file, which need to be done in any case + try: + do_create(mlist, DBFILE, TEXTFILE, addlist) + if mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS: + dbfile, textfile = virtual_files(mlist.host_name) + do_create(mlist, dbfile, textfile, addvirtual) + finally: + if lock: + lock.unlock(unconditionally=1) -def remove(mlist, cgi=0): - # Acquire the global list database lock - lock = LockFile.LockFile(LOCKFILE) - lock.lock() +def do_remove(mlist, dbfile, textfile, virtualp): lockfp = None try: listname = mlist.internal_name() # Crack open the dbhash file, and delete all the entries. See the # discussion above for while we lock the aliases.db file this way. - lockfp = open(DBFILE) + lockfp = open(dbfile) fcntl.flock(lockfp.fileno(), fcntl.LOCK_EX) - db = dbhash.open(DBFILE, 'c') + db = dbhash.open(dbfile, 'c') for k, v in makealiases(listname): try: del db[k + '\0'] except KeyError: pass - # Always update YP_LAST_MODIFIED - db['YP_LAST_MODIFIED'] = '%010d' % time.time() - # Add a YP_MASTER_NAME only if there isn't one already - if not db.has_key('YP_MASTER_NAME'): - db['YP_MASTER_NAME'] = socket.getfqdn() + if not virtualp: + # Always update YP_LAST_MODIFIED, but only for the aliases file + db['YP_LAST_MODIFIED'] = '%010d' % time.time() + # Add a YP_MASTER_NAME only if there isn't one already + if not db.has_key('YP_MASTER_NAME'): + db['YP_MASTER_NAME'] = socket.getfqdn() # And flush the changes to disk db.sync() # Now do our best to filter out the proper stanza from the text file. # The text file better exist! - outfile = TEXTFILE try: - infp = open(TEXTFILE) + infp = open(textfile) except IOError, e: if e.errno <> errno.ENOENT: raise # Otherwise, there's no text file to filter so we're done. return omask = os.umask(007) try: - outfp = open(TEXTFILE + '.tmp', 'w') + outfp = open(textfile + '.tmp', 'w') finally: os.umask(omask) filteroutp = 0 @@ -214,11 +275,22 @@ def remove(mlist, cgi=0): # Close up shop, and rotate the files infp.close() outfp.close() - os.rename(TEXTFILE+'.tmp', TEXTFILE) + os.rename(textfile+'.tmp', textfile) finally: if lockfp: fcntl.flock(lockfp.fileno(), fcntl.LOCK_UN) lockfp.close() + + +def remove(mlist, cgi=0): + # Acquire the global list database lock + lock = makelock() + lock.lock() + try: + do_remove(mlist, DBFILE, TEXTFILE, 0) + if mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS: + do_remove(mlist, VDBFILE, VTEXTFILE, 1) + finally: lock.unlock(unconditionally=1) |
