summaryrefslogtreecommitdiff
path: root/Mailman/MTA/Postfix.py
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman/MTA/Postfix.py')
-rw-r--r--Mailman/MTA/Postfix.py138
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)