diff options
author | <> | 2003-01-02 05:25:50 +0000 |
---|---|---|
committer | <> | 2003-01-02 05:25:50 +0000 |
commit | b132a73f15e432eaf43310fce9196ca0c0651465 (patch) | |
tree | c15f816ba7c4de99fef510e3bd75af0890d47441 /Mailman/MTA | |
download | mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.gz mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.xz mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.zip |
This commit was manufactured by cvs2svn to create branch
'Release_2_1-maint'.
Diffstat (limited to 'Mailman/MTA')
-rw-r--r-- | Mailman/MTA/.cvsignore | 1 | ||||
-rw-r--r-- | Mailman/MTA/Makefile.in | 69 | ||||
-rw-r--r-- | Mailman/MTA/Manual.py | 135 | ||||
-rw-r--r-- | Mailman/MTA/Postfix.py | 344 | ||||
-rw-r--r-- | Mailman/MTA/Utils.py | 79 | ||||
-rw-r--r-- | Mailman/MTA/__init__.py | 15 |
6 files changed, 643 insertions, 0 deletions
diff --git a/Mailman/MTA/.cvsignore b/Mailman/MTA/.cvsignore new file mode 100644 index 00000000..f3c7a7c5 --- /dev/null +++ b/Mailman/MTA/.cvsignore @@ -0,0 +1 @@ +Makefile diff --git a/Mailman/MTA/Makefile.in b/Mailman/MTA/Makefile.in new file mode 100644 index 00000000..42a6fcc5 --- /dev/null +++ b/Mailman/MTA/Makefile.in @@ -0,0 +1,69 @@ +# Copyright (C) 1998,1999,2000,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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# NOTE: Makefile.in is converted into Makefile by the configure script +# in the parent directory. Once configure has run, you can recreate +# the Makefile by running just config.status. + +# Variables set by configure + +VPATH= @srcdir@ +srcdir= @srcdir@ +bindir= @bindir@ +prefix= @prefix@ +exec_prefix= @exec_prefix@ + +CC= @CC@ +CHMOD= @CHMOD@ +INSTALL= @INSTALL@ + +DEFS= @DEFS@ + +# Customizable but not set by configure + +OPT= @OPT@ +CFLAGS= $(OPT) $(DEFS) +PACKAGEDIR= $(prefix)/Mailman/MTA +SHELL= /bin/sh + +MODULES= *.py + +# Modes for directories and executables created by the install +# process. Default to group-writable directories but +# user-only-writable for executables. +DIRMODE= 775 +EXEMODE= 755 +FILEMODE= 644 +INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE) + + +# Rules + +all: + +install: + for f in $(MODULES); \ + do \ + $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(PACKAGEDIR); \ + done + +finish: + +clean: + +distclean: + -rm *.pyc + -rm Makefile diff --git a/Mailman/MTA/Manual.py b/Mailman/MTA/Manual.py new file mode 100644 index 00000000..dd9127cc --- /dev/null +++ b/Mailman/MTA/Manual.py @@ -0,0 +1,135 @@ +# 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Creation/deletion hooks for manual /etc/aliases files.""" + +import sys +from cStringIO import StringIO + +from Mailman import mm_cfg +from Mailman import Message +from Mailman import Utils +from Mailman.Queue.sbcache import get_switchboard +from Mailman.i18n import _ +from Mailman.MTA.Utils import makealiases + + + +# no-ops for interface compliance +def makelock(): + class Dummy: + def lock(self): + pass + def unlock(self, unconditionally=0): + pass + return Dummy() + + +def clear(): + pass + + + +# nolock argument is ignored, but exists for interface compliance +def create(mlist, cgi=0, nolock=0): + if mlist is None: + return + listname = mlist.internal_name() + fieldsz = len(listname) + len('-unsubscribe') + if cgi: + # If a list is being created via the CGI, the best we can do is send + # an email message to mailman-owner requesting that the proper aliases + # be installed. + sfp = StringIO() + print >> sfp, _("""\ +The mailing list `%(listname)s' has been created via the through-the-web +interface. In order to complete the activation of this mailing list, the +proper /etc/aliases (or equivalent) file must be updated. The program +`newaliases' may also have to be run. + +Here are the entries for the /etc/aliases file: +""") + outfp = sfp + else: + print _(""" +To finish creating your mailing list, you must edit your /etc/aliases (or +equivalent) file by adding the following lines, and possibly running the +`newaliases' program: + +## %(listname)s mailing list""") + outfp = sys.stdout + # Common path + for k, v in makealiases(listname): + print >> outfp, k + ':', ((fieldsz - len(k)) * ' '), v + # If we're using the command line interface, we're done. For ttw, we need + # to actually send the message to mailman-owner now. + if not cgi: + print >> outfp + return + # Send the message to the site -owner so someone can do something about + # this request. + siteowner = Utils.get_site_email(extra='owner') + # Should this be sent in the site list's preferred language? + msg = Message.UserNotification( + siteowner, siteowner, + _('Mailing list creation request for list %(listname)s'), + sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) + outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) + outq.enqueue(msg, recips=[siteowner]) + + + +def remove(mlist, cgi=0): + listname = mlist.internal_name() + fieldsz = len(listname) + len('-unsubscribe') + if cgi: + # If a list is being removed via the CGI, the best we can do is send + # an email message to mailman-owner requesting that the appropriate + # aliases be deleted. + sfp = StringIO() + print >> sfp, _("""\ +The mailing list `%(listname)s' has been removed via the through-the-web +interface. In order to complete the de-activation of this mailing list, the +appropriate /etc/aliases (or equivalent) file must be updated. The program +`newaliases' may also have to be run. + +Here are the entries in the /etc/aliases file that should be removed: +""") + outfp = sfp + else: + print _(""" +To finish removing your mailing list, you must edit your /etc/aliases (or +equivalent) file by removing the following lines, and possibly running the +`newaliases' program: + +## %(listname)s mailing list""") + outfp = sys.stdout + # Common path + for k, v in makealiases(listname): + print >> outfp, k + ':', ((fieldsz - len(k)) * ' '), v + # If we're using the command line interface, we're done. For ttw, we need + # to actually send the message to mailman-owner now. + if not cgi: + print >> outfp + return + siteowner = Utils.get_site_email(extra='owner') + # Should this be sent in the site list's preferred language? + msg = Message.UserNotification( + siteowner, siteowner, + _('Mailing list removal request for list %(listname)s'), + sfp.getvalue(), mm_cfg.DEFAULT_SERVER_LANGUAGE) + outq = get_switchboard(mm_cfg.OUTQUEUE_DIR) + outq.enqueue(msg, recips=[siteowner]) diff --git a/Mailman/MTA/Postfix.py b/Mailman/MTA/Postfix.py new file mode 100644 index 00000000..24c1c7e5 --- /dev/null +++ b/Mailman/MTA/Postfix.py @@ -0,0 +1,344 @@ +# 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Creation/deletion hooks for the Postfix MTA. +""" + +import os +import time +import errno +import pwd +import grp +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 +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') + + + +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) + + + +def makelock(): + return LockFile.LockFile(LOCKFILE) + + +def _zapfile(filename): + # Truncate the file w/o messing with the file permissions, but only if it + # already exists. + if os.path.exists(filename): + fp = open(filename, 'w') + fp.close() + + +def clear(): + _zapfile(ALIASFILE) + _zapfile(VIRTFILE) + + + +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') + # 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 aliases.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. +""" + print >> fp, '# The ultimate loop stopper address' + print >> fp, '%s: %s' % (loopaddr, loopmbox) + print >> fp + # 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. + if mlist is None: + return + listname = mlist.internal_name() + fieldsz = len(listname) + len('-unsubscribe') + # 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): + # Format the text file nicely + print >> fp, k + ':', ((fieldsz - len(k)) * ' ') + v + # Finish the text file stanza + print >> fp, '# STANZA END:', listname + print >> fp + + + +def _addvirtual(mlist, fp): + listname = mlist.internal_name() + fieldsz = len(listname) + len('-unsubscribe') + hostname = mlist.host_name + # Set up the mailman-loop address + loopaddr = Utils.get_site_email(mlist.host_name, extra='loop') + loopdest = Utils.ParseEmail(loopaddr)[0] + # 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. +# +# Note that you should already have this virtual domain set up properly in +# your Postfix installation. See README.POSTFIX for details. + +# LOOP ADDRESSES START +%s\t%s +# LOOP ADDRESSES END +""" % (loopaddr, loopdest) + # 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) + # Format the text file nicely + print >> fp, fqdnaddr, ((fieldsz - len(k)) * ' '), k + # Finish the text file stanza + print >> fp, '# STANZA END:', listname + print >> fp + + + +# Blech. +def _check_for_virtual_loopaddr(mlist, filename): + loopaddr = Utils.get_site_email(mlist.host_name, extra='loop') + loopdest = Utils.ParseEmail(loopaddr)[0] + infp = open(filename) + omask = os.umask(007) + try: + outfp = open(filename + '.tmp', 'w') + finally: + os.umask(omask) + try: + # Find the start of the loop address block + while 1: + line = infp.readline() + if not line: + break + outfp.write(line) + if line.startswith('# LOOP ADDRESSES START'): + break + # Now see if our domain has already been written + while 1: + line = infp.readline() + if not line: + break + if line.startswith('# LOOP ADDRESSES END'): + # It hasn't + print >> outfp, '%s\t%s' % (loopaddr, loopdest) + outfp.write(line) + break + elif line.startswith(loopaddr): + # We just found it + outfp.write(line) + break + else: + # This isn't our loop address, so spit it out and continue + outfp.write(line) + outfp.writelines(infp.readlines()) + finally: + infp.close() + outfp.close() + os.rename(filename + '.tmp', filename) + + + +def _do_create(mlist, textfile, func): + # Crack open the plain text file + 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) + try: + func(mlist, fp) + finally: + fp.close() + # Now double check the virtual plain text file + if func is _addvirtual: + _check_for_virtual_loopaddr(mlist, textfile) + + +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, ALIASFILE, _addlist) + if mlist and mlist.host_name in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS: + _do_create(mlist, VIRTFILE, _addvirtual) + _update_maps() + finally: + if lock: + lock.unlock(unconditionally=1) + + + +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: + omask = os.umask(007) + try: + outfp = open(textfile + '.tmp', 'w') + finally: + os.umask(omask) + filteroutp = 0 + start = '# STANZA START: ' + listname + end = '# STANZA END: ' + listname + while 1: + 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 + # filtering out a stanza, we're just looking for the proper begin + # marker. + if filteroutp: + if line.startswith(end): + filteroutp = 0 + # Discard the trailing blank line, but don't worry if + # we're at the end of the file. + infp.readline() + # Otherwise, ignore the line + else: + if line.startswith(start): + # Filter out this stanza + filteroutp = 1 + else: + outfp.write(line) + # Close up shop, and rotate the files + finally: + infp.close() + outfp.close() + os.rename(textfile+'.tmp', textfile) + + +def remove(mlist, cgi=0): + # Acquire the global list database lock + lock = makelock() + lock.lock() + try: + _do_remove(mlist, ALIASFILE, 0) + if mlist.host_name in mm_cfg.POSTFIX_STYLE_VIRTUAL_DOMAINS: + _do_remove(mlist, VIRTFILE, 1) + # Regenerate the alias and map files + _update_maps() + finally: + lock.unlock(unconditionally=1) + + + +def checkperms(state): + targetmode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP + for file in ALIASFILE, VIRTFILE: + if state.VERBOSE: + print _('checking permissions on %(file)s') + stat = None + try: + stat = os.stat(file) + except OSError, e: + if e.errno <> errno.ENOENT: + raise + if stat and (stat[ST_MODE] & targetmode) <> targetmode: + state.ERRORS += 1 + octmode = oct(stat[ST_MODE]) + print _('%(file)s permissions must be 066x (got %(octmode)s)'), + if state.FIX: + print _('(fixing)') + os.chmod(file, stat[ST_MODE] | targetmode) + else: + print + # Make sure the corresponding .db files are owned by the Mailman user. + # We don't need to check the group ownership of the file, since + # check_perms checks this itself. + dbfile = file + '.db' + stat = None + try: + stat = os.stat(dbfile) + except OSError, e: + if e.errno <> errno.ENOENT: + raise + continue + if state.VERBOSE: + print _('checking ownership of %(dbfile)s') + user = mm_cfg.MAILMAN_USER + ownerok = stat[ST_UID] == pwd.getpwnam(user)[2] + if not ownerok: + try: + owner = pwd.getpwuid(stat[ST_UID])[0] + except KeyError: + owner = 'uid %d' % stat[ST_UID] + print _('%(dbfile)s owned by %(owner)s (must be owned by %(user)s') + state.ERRORS += 1 + if state.FIX: + print _('(fixing)') + uid = pwd.getpwnam(user)[2] + gid = grp.getgrnam(mm_cfg.MAILMAN_GROUP)[2] + os.chown(dbfile, uid, gid) + else: + print diff --git a/Mailman/MTA/Utils.py b/Mailman/MTA/Utils.py new file mode 100644 index 00000000..f55a1ed3 --- /dev/null +++ b/Mailman/MTA/Utils.py @@ -0,0 +1,79 @@ +# 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Utilities for list creation/deletion hooks.""" + +import os +import pwd + +from Mailman import mm_cfg + + + +def getusername(): + username = os.environ.get('USER') or os.environ.get('LOGNAME') + if not username: + import pwd + username = pwd.getpwuid(os.getuid())[0] + if not username: + username = '<unknown>' + return username + + + +def _makealiases_mailprog(listname): + wrapper = os.path.join(mm_cfg.WRAPPER_DIR, 'mailman') + # Most of the list alias extensions are quite regular. I.e. if the + # message is delivered to listname-foobar, it will be filtered to a + # program called foobar. There are two exceptions: + # + # 1) Messages to listname (no extension) go to the post script. + # 2) Messages to listname-admin go to the bounces script. This is for + # backwards compatibility and may eventually go away (we really have no + # need for the -admin address anymore). + # + # Seed this with the special cases. + aliases = [(listname, '"|%s post %s"' % (wrapper, listname)), + ] + for ext in ('admin', 'bounces', 'confirm', 'join', 'leave', 'owner', + 'request', 'subscribe', 'unsubscribe'): + aliases.append(('%s-%s' % (listname, ext), + '"|%s %s %s"' % (wrapper, ext, listname))) + return aliases + + + +def _makealiases_maildir(listname): + maildir = mm_cfg.MAILDIR_DIR + if not maildir.endswith('/'): + maildir += '/' + # Deliver everything using maildir style. This way there's no mail + # program, no forking and no wrapper necessary! + # + # Note, don't use this unless your MTA leaves the envelope recipient in + # Delivered-To:, Envelope-To:, or Apparently-To: + aliases = [(listname, maildir)] + for ext in ('admin', 'bounces', 'confirm', 'join', 'leave', 'owner', + 'request', 'subscribe', 'unsubscribe'): + aliases.append(('%s-%s' % (listname, ext), maildir)) + return aliases + + + +if mm_cfg.USE_MAILDIR: + makealiases = _makealiases_maildir +else: + makealiases = _makealiases_mailprog diff --git a/Mailman/MTA/__init__.py b/Mailman/MTA/__init__.py new file mode 100644 index 00000000..55cd5826 --- /dev/null +++ b/Mailman/MTA/__init__.py @@ -0,0 +1,15 @@ +# 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |