summaryrefslogtreecommitdiff
path: root/Mailman/MTA/Postfix.py
diff options
context:
space:
mode:
authorbwarsaw2002-02-09 03:50:52 +0000
committerbwarsaw2002-02-09 03:50:52 +0000
commitd8d3d0e580cb1a2fb47a8454c25e13ad444faeb5 (patch)
tree1c2061b6754e1d346bd4a634f4dffab6d76ed656 /Mailman/MTA/Postfix.py
parent975e43f8ca04346b7e391b263c8169c71ff5aaf2 (diff)
downloadmailman-d8d3d0e580cb1a2fb47a8454c25e13ad444faeb5.tar.gz
mailman-d8d3d0e580cb1a2fb47a8454c25e13ad444faeb5.tar.zst
mailman-d8d3d0e580cb1a2fb47a8454c25e13ad444faeb5.zip
Diffstat (limited to 'Mailman/MTA/Postfix.py')
-rw-r--r--Mailman/MTA/Postfix.py217
1 files changed, 62 insertions, 155 deletions
diff --git a/Mailman/MTA/Postfix.py b/Mailman/MTA/Postfix.py
index f39c52bb9..de3156406 100644
--- a/Mailman/MTA/Postfix.py
+++ b/Mailman/MTA/Postfix.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001 by the Free Software Foundation, Inc.
+# Copyright (C) 2001,2002 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
@@ -15,74 +15,45 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Creation/deletion hooks for the Postfix MTA.
-
-Note: only hash: type maps are currently supported.
"""
import os
-import socket
import time
import errno
import pwd
-import fcntl
from stat import *
-# Python's BerkeleyDB support is simply broken, IMO. The best advice I can
-# give is that if you are having problems, download and install PyBSDDB3, from
-# pybsddb.sf.net, install it, and use it by (possibly) editing the following
-# lines.
-try:
- import bsddb
-except ImportError:
- import bsddb3 as bsddb
-
from Mailman import mm_cfg
from Mailman import Utils
from Mailman import LockFile
from Mailman.i18n import _
from Mailman.MTA.Utils import makealiases
+from Mailman.Logging.Syslog import syslog
LOCKFILE = os.path.join(mm_cfg.LOCK_DIR, 'creator')
+ALIASFILE = os.path.join(mm_cfg.DATA_DIR, 'aliases')
+VIRTFILE = os.path.join(mm_cfg.DATA_DIR, 'virtual-mailman')
-TEXTFILE = os.path.join(mm_cfg.DATA_DIR, 'aliases')
-DBFILE = TEXTFILE + '.db'
-VTEXTFILE = os.path.join(mm_cfg.DATA_DIR, 'virtual-mailman')
-VDBFILE = VTEXTFILE + '.db'
+
+def _update_maps():
+ msg = 'command failed: %s (status: %s, %s)'
+ acmd = mm_cfg.POSTFIX_ALIAS_CMD + ' ' + ALIASFILE
+ status = (os.system(acmd) >> 8) & 0xff
+ if status:
+ errstr = os.strerror(status)
+ syslog('error', msg, acmd, status, errstr)
+ raise RuntimeError, msg % (acmd, status, errstr)
+ if os.path.exists(VIRTFILE):
+ vcmd = mm_cfg.POSTFIX_MAP_CMD + ' ' + VIRTFILE
+ status = (os.system(vcmd) >> 8) & 0xff
+ if status:
+ errstr = os.strerror(status)
+ syslog('error', msg, vcmd, status, errstr)
+ raise RuntimeError, msg % (vcmd, status, errstr)
-# 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 bsddb.hashopen()
-# which translates to passing the O_EXLOCK flag to the underlying open call,
-# for systems that support O_EXLOCK.
-#
-# Unfortunately, Linux is one of those systems that don't support O_EXLOCK.
-# To make matters worse, the default bsddb module in Python gives us no access
-# to the file descriptor of the database file, so we cannot hashopen(), dig
-# out the fd, then use fcntl.flock() to acquire the lock. Bummer. :(
-#
-# Another approach would be to do a file dance to assure exclusivity.
-# I.e. when we add or remove list entries, we actually create a new .tmp file,
-# write all the entries to that file, and then rename() that file to
-# aliases.db. The problem with /that/ approach though is that we can't get
-# the file ownership right on the tmp file. That's because the process adding
-# or removing entries may be Joe Shmoe who's a member of the `mailman' group,
-# or it may be a cgi process run as `nobody' or `apache'. Or eventually, it
-# may be run via a mail program or any number of other processes. Without a
-# setuid program we simply can't assure that the ownership of the tmp file
-# will be the same as the ownership of the original file, and /that/ we must
-# absolutely guarantee. Postfix runs mail programs under the uid of the owner
-# of the aliases.db file.
-#
-# The best solution I can come up with involves opening the aliases.db file
-# with bsddb.hashopen() first, then do a built-in open(), digout the fd from
-# the file object and flock() it. We have to open with bsddb.hashopen() first
-# in case the file doesn't exist yet, but we can't write to it until we get
-# the lock. Blech. If anybody has a better, portable, solution, I'm all
-# ears.
-
def makelock():
return LockFile.LockFile(LOCKFILE)
@@ -95,23 +66,13 @@ def _zapfile(filename):
fp.close()
-def _zapdb(filename):
- if os.path.exists(filename):
- db = bsddb.hashopen(filename, 'c')
- for k in db.keys():
- del db[k]
- db.close()
-
-
def clear():
_zapfile(TEXTFILE)
_zapfile(VTEXTFILE)
- _zapdb(DBFILE)
- _zapdb(VDBFILE)
-def addlist(mlist, db, fp):
+def _addlist(mlist, fp):
# Set up the mailman-loop address
loopaddr = Utils.ParseEmail(Utils.get_site_email(extra='loop'))[0]
loopmbox = os.path.join(mm_cfg.DATA_DIR, 'owner-bounces.mbox')
@@ -128,11 +89,6 @@ def addlist(mlist, db, fp):
print >> fp, '# The ultimate loop stopper address'
print >> fp, '%s: %s' % (loopaddr, loopmbox)
print >> fp
- # 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()
# Bootstrapping. bin/genaliases must be run before any lists are created,
# but if no lists exist yet then mlist is None. The whole point of the
# exercise is to get the minimal aliases.db file into existance.
@@ -143,14 +99,8 @@ def addlist(mlist, db, fp):
# The text file entries get a little extra info
print >> fp, '# STANZA START:', listname
print >> fp, '# CREATED:', time.ctime(time.time())
- # Add the loop stopper address
- db[loopaddr + '\0'] = loopmbox + '\0'
# Now add all the standard alias entries
for k, v in makealiases(listname):
- # 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[k + '\0'] = v + '\0'
# Format the text file nicely
print >> fp, k + ':', ((fieldsz - len(k) + 1) * ' '), v
# Finish the text file stanza
@@ -159,7 +109,7 @@ def addlist(mlist, db, fp):
-def addvirtual(mlist, db, fp):
+def _addvirtual(mlist, fp):
listname = mlist.internal_name()
fieldsz = len(listname) + len('request')
hostname = mlist.host_name
@@ -183,18 +133,12 @@ def addvirtual(mlist, db, fp):
%s\t%s
# LOOP ADDRESSES END
""" % (loopaddr, loopdest)
- # Add the loop address entry to the db file
- db[loopaddr + '\0'] = loopdest + '\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) * ' '), '\t', k
# Finish the text file stanza
@@ -204,14 +148,9 @@ def addvirtual(mlist, db, fp):
# Blech.
-def check_for_virtual_loopaddr(mlist, db, filename):
+def _check_for_virtual_loopaddr(mlist, filename):
loopaddr = Utils.get_site_email(mlist.host_name, extra='loop')
loopdest = Utils.ParseEmail(loopaddr)[0]
- # If the loop address is already in the database, we don't need to add it
- # to the plain text file, but if it isn't, then we do!
- if db.has_key(loopaddr + '\0'):
- # It's already there
- return
infp = open(filename)
omask = os.umask(007)
try:
@@ -232,41 +171,27 @@ def check_for_virtual_loopaddr(mlist, db, filename):
infp.close()
outfp.close()
os.rename(filename + '.tmp', filename)
- db[loopaddr + '\0'] = loopdest + '\0'
-def do_create(mlist, dbfile, textfile, func):
- lockfp = None
+def _do_create(mlist, textfile, func):
+ # Crack open the plain text file
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()
- db = bsddb.hashopen(dbfile, 'c')
- lockfp = open(dbfile)
- fcntl.flock(lockfp.fileno(), fcntl.LOCK_EX)
- # Crack open the plain text file
+ fp = open(textfile, 'r+')
+ except IOError, e:
+ if e.errno <> errno.ENOENT: raise
+ omask = os.umask(007)
try:
- fp = open(textfile, 'r+')
- except IOError, e:
- if e.errno <> errno.ENOENT: raise
- omask = os.umask(007)
- try:
- fp = open(textfile, 'w+')
- finally:
- os.umask(omask)
- func(mlist, db, fp)
- # And flush everything out to disk
- fp.close()
+ fp = open(textfile, 'w+')
+ finally:
+ os.umask(omask)
+ try:
+ func(mlist, fp)
# Now double check the virtual plain text file
- if func is addvirtual:
- check_for_virtual_loopaddr(mlist, db, textfile)
- db.sync()
- db.close()
+ if func is _addvirtual:
+ _check_for_virtual_loopaddr(mlist, textfile)
finally:
- if lockfp:
- fcntl.flock(lockfp.fileno(), fcntl.LOCK_UN)
- lockfp.close()
+ fp.close()
def create(mlist, cgi=0, nolock=0):
@@ -277,45 +202,28 @@ def create(mlist, cgi=0, nolock=0):
lock.lock()
# Do the aliases file, which need to be done in any case
try:
- do_create(mlist, DBFILE, TEXTFILE, addlist)
+ _do_create(mlist, ALIASFILE, _addlist)
if mlist and mlist.host_name in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS:
- do_create(mlist, VDBFILE, VTEXTFILE, addvirtual)
+ _do_create(mlist, VIRTFILE, _addvirtual)
+ _update_maps()
finally:
if lock:
lock.unlock(unconditionally=1)
-def do_remove(mlist, dbfile, textfile, virtualp):
- lockfp = None
+def _do_remove(mlist, textfile, virtualp):
+ listname = mlist.internal_name()
+ # Now do our best to filter out the proper stanza from the text file.
+ # The text file better exist!
+ outfp = None
+ try:
+ 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
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)
- fcntl.flock(lockfp.fileno(), fcntl.LOCK_EX)
- db = bsddb.hashopen(dbfile, 'c')
- for k, v in makealiases(listname):
- try:
- del db[k + '\0']
- except KeyError:
- pass
- 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!
- try:
- 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')
@@ -328,10 +236,10 @@ def do_remove(mlist, dbfile, textfile, virtualp):
line = infp.readline()
if not line:
break
- # If we're filtering out a stanza, just look for the end marker
- # and filter out everything in between. If we're not in the
- # middle of filter out a stanza, we're just looking for the proper
- # begin marker.
+ # If we're filtering out a stanza, just look for the end marker and
+ # filter out everything in between. If we're not in the middle of
+ # filtering out a stanza, we're just looking for the proper begin
+ # marker.
if filteroutp:
if line.startswith(end):
filteroutp = 0
@@ -345,14 +253,11 @@ def do_remove(mlist, dbfile, textfile, virtualp):
filteroutp = 1
else:
outfp.write(line)
- # Close up shop, and rotate the files
+ # Close up shop, and rotate the files
+ finally:
infp.close()
outfp.close()
- os.rename(textfile+'.tmp', textfile)
- finally:
- if lockfp:
- fcntl.flock(lockfp.fileno(), fcntl.LOCK_UN)
- lockfp.close()
+ os.rename(textfile+'.tmp', textfile)
def remove(mlist, cgi=0):
@@ -360,9 +265,11 @@ def remove(mlist, cgi=0):
lock = makelock()
lock.lock()
try:
- do_remove(mlist, DBFILE, TEXTFILE, 0)
+ _do_remove(mlist, ALIASFILE, 0)
if mlist.host_name in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS:
- do_remove(mlist, VDBFILE, VTEXTFILE, 1)
+ _do_remove(mlist, VIRTFILE, 1)
+ # Regenerate the alias and map files
+ _update_maps()
finally:
lock.unlock(unconditionally=1)