diff options
Diffstat (limited to '')
-rw-r--r-- | contrib/README | 4 | ||||
-rw-r--r-- | contrib/README.check_perms_grsecurity | 14 | ||||
-rw-r--r-- | contrib/auto | 116 | ||||
-rw-r--r-- | contrib/check_perms_grsecurity.py | 180 | ||||
-rw-r--r-- | contrib/mailman.mc | 143 | ||||
-rw-r--r-- | contrib/majordomo2mailman.pl | 691 | ||||
-rw-r--r-- | contrib/mm-handler | 236 | ||||
-rw-r--r-- | contrib/mm-handler.readme | 151 | ||||
-rw-r--r-- | contrib/qmail-to-mailman.py | 115 | ||||
-rw-r--r-- | contrib/rotatelogs.py | 103 | ||||
-rw-r--r-- | contrib/virtusertable | 37 |
11 files changed, 1790 insertions, 0 deletions
diff --git a/contrib/README b/contrib/README new file mode 100644 index 00000000..3e475032 --- /dev/null +++ b/contrib/README @@ -0,0 +1,4 @@ +This directory contains unofficial contributed scripts and extensions +to Mailman. They are unsupported by the Mailman developers. If you +have questions or problems with them, please contact the contribution +author directly. diff --git a/contrib/README.check_perms_grsecurity b/contrib/README.check_perms_grsecurity new file mode 100644 index 00000000..6d1d0389 --- /dev/null +++ b/contrib/README.check_perms_grsecurity @@ -0,0 +1,14 @@ +The check_perms_grsecurity.py script, if copied in your installed +~mailman/bin/ directory and run from there will modify permissions of +files so that Mailman with extra restrictions imposed by linux kernel security +patches like securelinux/openwall in 2.2.x or grsecurity in 2.4.x + +The way it works is that it makes sure that the UID of any script that +touches config.pck is `mailman'. What this means however is that +scripts in ~mailman/bin will now only work if run as user mailman or +root (the script then changes its UID and GID to mailman). +To make grsecurity happy, we remove the group writeable bit on a directories +that contain binaries. + +Enjoy +Marc MERLIN <marc_soft@merlins.org>/<marc_bts@vasoftware.com> - 2001/12/10 diff --git a/contrib/auto b/contrib/auto new file mode 100644 index 00000000..6ac7eba0 --- /dev/null +++ b/contrib/auto @@ -0,0 +1,116 @@ +# -*- python -*- +# +# Copyright (C) 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. + +"""Automatically send a message to a mailing list. +""" + +# To use with Postfix, set the following in your main.cf file: +# +# recipient_delimiter = + +# luser_relay = mm+$user@yourdomain.com +# owner_request_special = no + +import sys +import os +import time + +import paths +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import MailList +from Mailman import Errors +from Mailman.Queue.sbcache import get_switchboard +from Mailman.Logging.Utils import LogStdErr + +# Error code if it's really not a Mailman list addr destination +EX_NOUSER = 67 + +LogStdErr('auto', 'auto') + +DISPOSE_MAP = {None : 'tolist', + 'request': 'torequest', + 'admin' : 'toadmin', + 'owner' : 'toadmin', + } + + + +def fqdn_listname(listname, hostname): + return ('%s@%s' % (listname, hostname)).lower() + + + +def main(): + # Postfix sets some environment variables based on information gleaned + # from the original message. This is the most direct way to figure out + # which list the message was intended for. + extension = os.environ.get('EXTENSION', '').lower() + i = extension.rfind('-') + if i < 0: + listname = extension + subdest = 'tolist' + else: + missing = [] + listname = extension[:i] + subdest = DISPOSE_MAP.get(extension[i+1:], missing) + if not Utils.list_exists(listname) or subdest is missing: + # must be a list that has a `-' in it's name + listname = extension + subdest = 'tolist' + if not listname: + print >> sys.stderr, 'Empty list name (someone being subversive?)' + return EX_NOUSER + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError: + print >> sys.stderr, 'List not found:', listname + return EX_NOUSER + + # Make sure that the domain part of the incoming address matches the + # domain of the mailing list. Actually, it's possible that one or the + # other is more fully qualified, and thus longer. So we split the domains + # by dots, reverse them and make sure that whatever parts /are/ defined + # for both are equivalent. + domain = os.environ.get('DOMAIN', '').lower() + domainp = domain.split('.') + hostname = mlist.host_name.split('.') + domainp.reverse() + hostname.reverse() + for ca, cb in zip(domainp, hostname): + if ca <> cb: + print >> sys.stderr, 'Domain mismatch: %s@%s (expected @%s)' \ + % (listname, domain, mlist.host_name) + return EX_NOUSER + + if subdest is None: + print >> sys.stderr, 'Bad sub-destination:', extension + return EX_NOUSER + + inq = get_switchboard(mm_cfg.INQUEUE_DIR) + inq.enqueue(sys.stdin.read(), + listname=listname, + received_time=time.time(), + _plaintext=1, + **{subdest: 1}) + return 0 + + + +if __name__ == '__main__': + code = main() + sys.exit(code) diff --git a/contrib/check_perms_grsecurity.py b/contrib/check_perms_grsecurity.py new file mode 100644 index 00000000..7b27a19f --- /dev/null +++ b/contrib/check_perms_grsecurity.py @@ -0,0 +1,180 @@ +#! @PYTHON@ +# +# 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. + +"""Fixes for running Mailman under the `secure-linux' patch or grsecurity. + +Run check_perms -f and only then check_perms_grsecurity.py -f +Note that you will have to re-run this script after a mailman upgrade and +that check_perms will undo part of what this script does + +If you use Solar Designer's secure-linux patch, it prevents a process from +linking (hard link) to a file it doesn't own. +Grsecurity (http://grsecurity.net/) can have the same restriction depending +on how it was built, including other restrictions like preventing you to run +a program if it is located in a directory writable by a non root user. + +As a result Mailman has to be changed so that the whole tree is owned by +Mailman, and the CGIs and some of the programs in the bin tree (the ones +that lock config.pck files) are SUID Mailman. The idea is that config.pck +files have to be owned by the mailman UID and only touched by programs that +are UID mailman. +At the same time, We have to make sure that at least 3 directories under +~mailman aren't writable by mailman: mail, cgi-bin, and bin + +Binary commands that are changed to be SUID mailman are also made unreadable +and unrunnable by people who aren't in the mailman group. This shouldn't +affect much since most of those commands would fail work if you weren't part +of the mailman group anyway. +Scripts in ~mailman/bin/ are not made suid or sgid, they need to be run by +user mailman or root to work. + +Marc <marc_soft@merlins.org>/<marc_bts@vasoftware.com> +2000/10/27 - Initial version for secure_linux/openwall and mailman 2.0 +2001/12/09 - Updated version for grsecurity and mailman 2.1 +""" + +import sys +import os +import paths +import re +import glob +import pwd +import grp +from Mailman import mm_cfg +from Mailman.mm_cfg import MAILMAN_USER, MAILMAN_GROUP +from stat import * + +# Directories that we don't want writable by mailman. +dirstochownroot= ( 'mail', 'cgi-bin', 'bin' ) + +# Those are the programs that we patch so that they insist being run under the +# mailman uid or as root. +binfilestopatch= ( 'add_members', 'change_pw', 'check_db', 'clone_member', + 'config_list', 'newlist', 'qrunner', 'remove_members', + 'rmlist', 'sync_members', 'update', 'withlist' ) + +def main(argv): + binpath = paths.prefix + '/bin/' + droplib = binpath + 'CheckFixUid.py' + + if len(argv) < 2 or argv[1] != "-f": + print __doc__ + sys.exit(1) + + print "Making select directories owned and writable by root only" + for dir in dirstochownroot: + dirpath = paths.prefix + '/' + dir + os.chown(dirpath, 0, MAILMAN_GID) + os.chmod(dirpath, 02755) + print dirpath + + print + + file = paths.prefix + '/data/last_mailman_version' + print "Making" + file + "owned by mailman (not root)" + uid = pwd.getpwnam(MAILMAN_USER)[2] + gid = grp.getgrnam(MAILMAN_GROUP)[2] + os.chown(file, uid, gid) + print + + if not os.path.exists(droplib): + print "Creating " + droplib + fp = open(droplib, 'w', 0644) + fp.write("""import sys +import os +import grp, pwd +from Mailman.mm_cfg import MAILMAN_USER, MAILMAN_GROUP + +class CheckFixUid: + if os.geteuid() == 0: + uid = pwd.getpwnam(MAILMAN_USER)[2] + gid = grp.getgrnam(MAILMAN_GROUP)[2] + os.setgid(gid) + os.setuid(uid) + if os.geteuid() != uid: + print "You need to run this script as root or mailman because it was configured to run" + print "on a linux system with a security patch which restricts hard links" + sys.exit() +""") + fp.close() + else: + print "Skipping creation of " + droplib + + + print "\nMaking cgis setuid mailman" + cgis = glob.glob(paths.prefix + '/cgi-bin/*') + + for file in cgis: + print file + os.chown(file, uid, gid) + os.chmod(file, 06755) + + print "\nMaking mail wrapper setuid mailman" + file= paths.prefix + '/mail/mailman' + os.chown(file, uid, gid) + os.chmod(file, 06755) + print file + + print "\nEnsuring that all config.db/pck files are owned by Mailman" + cdbs = glob.glob(paths.prefix + '/lists/*/config.db*') + cpcks = glob.glob(paths.prefix + '/lists/*/config.pck*') + + for file in cdbs + cpcks: + stat = os.stat(file) + if (stat[ST_UID] != uid or stat[ST_GID] != gid): + print file + os.chown(file, uid, gid) + + print "\nPatching mailman scripts to change the uid to mailman" + + for script in binfilestopatch: + filefd = open(script, "r") + file = filefd.readlines() + filefd.close() + + patched = 0 + try: + file.index("import CheckFixUid\n") + print "Not patching " + script + ", already patched" + except ValueError: + file.insert(file.index("import paths\n")+1, "import CheckFixUid\n") + for i in range(len(file)-1, 0, -1): + object=re.compile("^([ ]*)main\(").search(file[i]) + # Special hack to support patching of update + object2=re.compile("^([ ]*).*=[ ]*main\(").search(file[i]) + if object: + print "Patching " + script + file.insert(i, + object.group(1) + "CheckFixUid.CheckFixUid()\n") + patched=1 + break + if object2: + print "Patching " + script + file.insert(i, + object2.group(1) + "CheckFixUid.CheckFixUid()\n") + patched=1 + break + + if patched==0: + print "Warning, file "+script+" couldn't be patched." + print "If you use it, mailman may not function properly" + else: + filefd=open(script, "w") + filefd.writelines(file) + +main(sys.argv) diff --git a/contrib/mailman.mc b/contrib/mailman.mc new file mode 100644 index 00000000..fc39edbd --- /dev/null +++ b/contrib/mailman.mc @@ -0,0 +1,143 @@ +dnl +dnl *** EXAMPLE *** sendmail.mc file for a Mailman list server using +dnl mm-handler to deal with list operations (in place of aliases). +dnl This is what I actually use on my site. +dnl +dnl $Id: mailman.mc 4287 2001-10-27 02:30:51Z bwarsaw $ +dnl + + +dnl +dnl First you need to define your general characteristics. You +dnl should know what these settings should be at your site -- I +dnl only know what they should be at mine. +dnl +OSTYPE(solaris2)dnl +DOMAIN(generic)dnl + +dnl +dnl You can keep the old alias files for back-compatibility, but it's +dnl probably better not to as this can become a point of confusion +dnl later. +dnl +define(`ALIAS_FILE', `/etc/mail/aliases,/etc/mail/lists') + +dnl +dnl I use procmail for local delivery, because it's smart to have a +dnl local delivery mailer, even if you don't (ordinarily) do any local +dnl delivery. The Solaris local delivery mailer is part of its sendmail +dnl package. I pkgrmed the sendmail packages so that system upgrades +dnl don't kill my sendmail.com sendmail, so mail.local is unavailable, +dnl so I throw procmail in here even though it never gets used. +dnl +define(`PROCMAIL_MAILER_PATH', `/opt/bin/procmail') +FEATURE(`local_procmail') +MAILER(`local') + +dnl +dnl Miscellaneous tuning. Not relevant to Mailman. +dnl +define(`confCONNECTION_RATE_THROTTLE', 5) +define(`confMAX_MESSAGE_SIZE', `5000000') +define(`confNO_RCPT_ACTION', `add-to-undisclosed') +define(`confME_TOO', `True') +define(`confDOUBLE_BOUNCE_ADDRESS', `mailer-daemon') + +dnl +dnl Privacy options. Also not relevant. +dnl +define(`confPRIVACY_FLAGS', `authwarnings,needvrfyhelo,noexpn,noreceipts,restrictmailq') + + +dnl +dnl Mm-handler works by mailertabling all addresses on your list +dnl server hostname(s) through the mm-handler mailer. Mailertable +dnl maps mail domains to mailer types. I want a mailertable to map +dnl listtest.uchicago.edu to the mm-handler mailer, but we need to +dnl specifically request this functionality in the .mc file. +dnl +FEATURE(`mailertable', `hash -o /etc/mail/mailertable') + +dnl +dnl This leads to an immediate and important side-effect: "local" +dnl addresses, and notably RFC-specified addresses such as postmaster, +dnl are assumed by sendmail to be lists! Since aliases are not processed +dnl for domaintabled domains, we must use a virtusertable to reroute +dnl such addresses. +dnl +FEATURE(`virtusertable', `hash -o /etc/mail/virtusertable') + +dnl +dnl By default, sendmail applies virtusertable mapping, if at all, for +dnl all interfaces for which it accepts mail -- i.e., all domains in +dnl $=w. Mm-handler relies on your having a single domain (hostname) +dnl that serves only lists, with no users. To avoid potential namespace +dnl conflicts, you need not to have this list domain included in $=w. +dnl As a result, virtuser mapping does not apply for the Mailman +dnl list domain. However, you can pre-empt this rule by defining +dnl $={VirtHost}: if there are domains in this class, they will be +dnl mapped before $=w is mapped. +dnl +dnl VIRTUSER_DOMAIN() defines this class. +dnl +VIRTUSER_DOMAIN(`nospam.uchicago.edu listtest.uchicago.edu listhost.uchicago.edu') + +dnl +dnl On a related point: by default, Sendmail probes for open IP +dnl interfaces, and adds their hostnames to $=w. Although Sendmail does +dnl virtusertable mapping for members of $=w, it doesn't do mailertable +dnl mapping for them, because they're considered "local". This tells +dnl Sendmail not to probe interfaces for local hosts, and it's critical +dnl if your Mailman domain is actually an IP address (with an A record, +dnl not just CNAME or MX) on your server. +dnl +define(`confDONT_PROBE_INTERFACES', `True') + + +dnl +dnl Even though my actual hostname is foobar, tell the world that I'm +dnl listtest.uchicago.edu. +dnl +FEATURE(`limited_masquerade') +MASQUERADE_AS(`listtest.uchicago.edu') + + +dnl +dnl Access control is a useful feature for blocking abusers and relays +dnl and such. +dnl +FEATURE(`access_db') + + +dnl +dnl This allows you to block access for individual recipents through +dnl the same access database as is used for blocking sender hosts and +dnl addresses. +dnl +FEATURE(`blacklist_recipients') + + +dnl +dnl Other local mailers... +dnl +MAILER(`smtp') +MAILER(`procmail') + + +dnl +dnl Our Mailman-specific local mailer. +dnl +MAILER_DEFINITIONS +#################################### +### New Mailer specifications ### +#################################### + +## Special flags! See +## http://www.sendmail.org/~ca/email/doc8.10/op-sh-5.html#sh-5.4 +## Note especially the absence of the "m" and "n" flags. THIS IS +## IMPORTANT: mm-handler assumes this behavior to avoid having to know +## too much about address parsing and other RFC-2822 mail details. + +Mmailman, P=/etc/mail/mm-handler, F=rDFMhlqSu, U=mailman:other, + S=EnvFromL, R=EnvToL/HdrToL, + A=mm-handler $h $u diff --git a/contrib/majordomo2mailman.pl b/contrib/majordomo2mailman.pl new file mode 100644 index 00000000..c874862e --- /dev/null +++ b/contrib/majordomo2mailman.pl @@ -0,0 +1,691 @@ +#!/usr/bin/perl -w + +# majordomo2mailman.pl - Migrate Majordomo mailing lists to Mailman 2.0 +# Copyright (C) 2002 Heiko Rommel (rommel@suse.de) + +# BAW: Note this probably needs to be upgraded to work with MM2.1 + +# +# License: +# +# 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 1, or (at your option) +# any later version. + +# +# Warranty: +# +# There's absolutely no warranty. +# + +# comments on possible debug messages during the conversion: +# +# "not an valid email address" : those addresses are rejected, i.e. not imported into the Mailman list +# "not a numeric value" : such a value will be converted to 0 (z.B. maxlength) +# "already subscribed" : will only once be subscribed on the Mailman list +# "...umbrella..." or "...taboo..." -> Mailman-Admin-Guide + +use strict; +use Getopt::Long; +use Fcntl; +use POSIX qw (tmpnam); + +use vars qw ( + $majordomo $mydomain $myurl + $aliasin $listdir + $aliasout $mailmanbin + $umbrella_member_suffix $private + $newsserver $newsprefix + $susehack $susearchuser + $help $debug $update $all $usagemsg + *FH + %mlaliases %mlowners %mlapprovers + %defaultmlconf %mlconf + %defaultmmconf %mmconf +); + +# +# adjust your site-specific settings here +# + +$mydomain = "my.domain"; +$majordomo = "majordomo"; # the master Majordomo address for your site +$aliasin = "/var/lib/majordomo/aliases"; +$listdir = "/var/lib/majordomo/lists"; +$aliasout = "/tmp/aliases"; +$myurl = "http://my.domain/mailman/"; +$mailmanbin = "/usr/lib/mailman/bin"; +$umbrella_member_suffix = "-owner"; +$private = "yes"; # is this a private/Intranet site ? +$newsserver = "news.my.domain"; +$newsprefix = "intern."; + +$susehack = "no"; +$susearchuser = "archdummy"; + +# +# 0) +# parse the command line arguments +# + +$usagemsg = "usage: majordomo2mailman [-h|--help] [-d|--debug] [-u|--update] < (-a|--all) | list-of-mailinglists >"; + +GetOptions( + "h|help" => \$help, + "d|debug" => \$debug, + "a|all" => \$all, + "u|update" => \$update +) or die "$usagemsg\n"; + +if (defined($help)) { die "$usagemsg\n"; } + +if ((not defined($all)) and (@ARGV<1)) { die "$usagemsg\n"; } + +if ($<) { die "this script must be run as root!\n"; } + +# +# 1) +# build a list of all aliases and extract the name of mailing lists plus their owners +# + +%mlaliases = %mlowners = %mlapprovers = (); + +open (FH, "< $aliasin") or die "can't open $aliasin\n"; + +while (<FH>) { + # first, build a list of all active aliases and their resolution + if (/^([^\#:]+)\s*:\s*(.*)$/) { + $mlaliases{$1} = $2; + } +} + +my $mlalias; +for $mlalias (keys %mlaliases) { + # if we encounter an alias with :include: as expansion + # it is save to assume that the alias has the form + # <mailinglist>-outgoing - + # that way we find the names of all active mailing lists + if ($mlaliases{$mlalias} =~ /\:include\:/) { + my $ml; + ($ml = $mlalias) =~ s/-outgoing//g; + $mlowners{$ml} = $mlaliases{"owner-$ml"}; + $mlapprovers{$ml} = $mlaliases{"$ml-approval"}; + } +} + +close (FH); + +# +# 2) +# for each list read the Majordomo configuration params +# and create a Mailman clone +# + +my $ml; +for $ml ((defined ($all)) ? sort keys %mlowners : @ARGV) { + + init_defaultmlconf($ml); + %mlconf = %defaultmlconf; + + init_defaultmmconf($ml); + %mmconf = %defaultmmconf; + + my @privileged; # addresses that are mentioned in restrict_post + my @members; + my ($primaryowner, @secondaryowner); + my ($primaryapprover, @secondaryapprover); + + my ($skey, $terminator); + my $filename; + my @args; + + # + # a) + # parse the configuration file + # + + open (FH, "< $listdir/$ml.config") or die "can't open $listdir/$ml.config\n"; + + while (<FH>) { + # key = value ? + if (/^\s*([^=\#\s]+)\s*=\s*(.*)\s*$/) { + $mlconf{$1} = $2; + } + # key << EOF + # value + # EOF ? + elsif (/^\s*([^<\#\s]+)\s*<<\s*(.*)\s*$/) { + ($skey, $terminator) = ($1, $2); + while (<FH>) { + last if (/^$terminator\s*$/); + $mlconf{$skey} .= $_; + } + chomp $mlconf{$skey}; + } + } + + close (FH); + + # + # b) + # test if there are so-called flag files (clue that this is an old-style Majordomo lists) + # and overwrite previously parsed values + # (stolen from majordomo::config_parse.pl: handle_flag_files()) + # + + if ( -e "$listdir/$ml.private") { + $mlconf{"get_access"} = "closed"; + $mlconf{"index_access"} = "closed"; + $mlconf{"who_access"} = "closed"; + $mlconf{"which_access"} = "closed"; + } + + $mlconf{"subscribe_policy"} = "closed" if ( -e "$listdir/$ml.closed"); + $mlconf{"unsubscribe_policy"} = "closed" if ( -e "$listdir/$ml.closed"); + + if ( -e "$listdir/$ml.auto" && -e "$listdir/$ml.closed") { + print STDERR "sowohl $ml.auto als auch $ml.closed existieren. Wähle $ml.closed\n"; + } + else { + $mlconf{"subscribe_policy"} = "auto" if ( -e"$listdir/$ml.auto"); + $mlconf{"unsubscribe_policy"} = "auto" if ( -e"$listdir/$ml.auto"); + } + + $mlconf{"strip"} = 1 if ( -e "$listdir/$ml.strip"); + $mlconf{"noadvertise"} = "/.*/" if ( -e "$listdir/$ml.hidden"); + + # admin_passwd: + $filename = "$listdir/" . $mlconf{"admin_passwd"}; + if ( -e "$listdir/$ml.passwd" ) { + $mlconf{"admin_passwd"} = read_from_file("$listdir/$ml.passwd"); + } + elsif ( -e "$filename" ) { + $mlconf{"admin_passwd"} = read_from_file("$filename"); + } + # else take it verbatim + + # approve_passwd: + $filename = "$listdir/" . $mlconf{"approve_passwd"}; + if ( -e "$listdir/$ml.passwd" ) { + $mlconf{"approve_passwd"} = read_from_file("$listdir/$ml.passwd"); + } + elsif ( -e "$filename" ) { + $mlconf{"approve_passwd"} = read_from_file("$filename"); + } + # else take it verbatim + + # + # c) + # add some information from additional configuration files + # + + # restrict_post + if (defined ($mlconf{"restrict_post"})) { + @privileged = (); + for $filename (split /\s+/, $mlconf{"restrict_post"}) { + open (FH, "< $listdir/$filename") or die "can't open $listdir/$filename\n"; + push (@privileged, <FH>); + chomp @privileged; + close (FH); + } + } + + if ($susehack =~ m/yes/i) { + @privileged = grep(!/$susearchuser\@$mydomain/i, @privileged); + } + + $mlconf{"privileged"} = \@privileged; + + # members + @members = (); + open (FH, "< $listdir/$ml") or die "can't open $listdir/$ml\n"; + push (@members, <FH>); + chomp @members; + close (FH); + + $mlconf{"gated"} = "no"; + + if ($susehack =~ m/yes/i) { + if (grep(/$susearchuser\@$mydomain/i, @members)) { + $mlconf{"gated"} = "yes"; + } + @members = grep(!/$susearchuser\@$mydomain/i, @members); + } + + $mlconf{"members"} = \@members; + + # intro message + if (open (FH, "< $listdir/$ml.intro")) { + { local $/; $mlconf{"intro"} = <FH>; } + } + else { $mlconf{"intro"} = ""; } + + # info message + if (open (FH, "< $listdir/$ml.info")) { + { local $/; $mlconf{"info"} = <FH>; } + } + else { $mlconf{"info"} = ""; } + + # + # d) + # take over some other params into the configuration table + # + + $mlconf{"name"} = "$ml"; + + ($primaryowner, @secondaryowner) = + expand_alias (split (/\s*,\s*/, aliassub($mlowners{$ml}))); + + ($primaryapprover, @secondaryapprover) = + expand_alias (split (/\s*,\s*/, aliassub($mlapprovers{$ml}))); + + $mlconf{"primaryowner"} = $primaryowner; + $mlconf{"secondaryowner"} = \@secondaryowner; + + $mlconf{"primaryapprover"} = $primaryapprover; + $mlconf{"secondaryapprover"} = \@secondaryapprover; + + # + # debugging output + # + + if (defined ($debug)) { + print "##################### $ml ####################\n"; + for $skey (sort keys %mlconf) { + if (defined ($mlconf{$skey})) { print "$skey = $mlconf{$skey}\n"; } + else { print "$skey = (?)\n"; } + } + my $priv; + for $priv (@privileged) { + print "\t$ml: $priv\n"; + } + } + + # + # e) + # with the help of Mailman commands - create a new list and subscribe the old staff + # + + if (defined($update)) { + print "updating configuration of \"$ml\"\n"; + } + else { + # Mailman lists can initially be only created with one owner + @args = ("$mailmanbin/newlist", "-q", "-o", "$aliasout", "$ml", $mlconf{"primaryowner"}, $mlconf{"admin_passwd"}); + system (@args) == 0 or die "system @args failed: $?"; + } + + # Mailman accepts only subscriber lists > 0 + if (@members > 0) { + $filename = tmpnam(); + open (FH, "> $filename") or die "can't open $filename\n"; + for $skey (@members) { + print FH "$skey" . "\n"; + } + close (FH); + @args = ("$mailmanbin/add_members", "-n", "$filename", "--welcome-msg=n", "$ml"); + system (@args) == 0 or die "system @args failed: $?"; + } + + # + # f) + # "translate" the Majordomo list configuration + # + + m2m(); + + # write the Mailman config + + $filename = tmpnam(); + + open (FH, "> $filename") or die "can't open $filename\n"; + for $skey (sort keys %mmconf) { + print FH "$skey = " . $mmconf{$skey} . "\n"; + } + close (FH); + + @args = ("$mailmanbin/config_list", "-i", "$filename", "$ml"); + system (@args) == 0 or die "system @args failed: $?"; + + unlink($filename) or print STDERR "unable to unlink \"$filename\"!\n"; + +} + +exit 0; + +############# +# subs +############# + +# +# I don't know how to write Perl code +# therefor I need this stupid procedure to cleanly read a value from file +# + +sub read_from_file { + my $value; + local *FH; + + open (FH, "< $_[0]") or die "can't open $_[0]\n"; + $value = <FH>; + chomp $value; + close (FH); + + return $value; +} + + +# +# add "@$mydomain" to each element that does not contain a "@" +# + +sub expand_alias { + return map { (not $_ =~ /@/) ? $_ .= "\@$mydomain" : $_ } @_; +} + +# +# replace the typical owner-majordomo aliases +# + +sub aliassub { + my $string = $_[0]; + + $string =~ s/(owner-$majordomo|$majordomo-owner)/mailman-owner/gi; + + return $string; +} + +# +# default values of Majordomo mailing lists +# (stolen from majordomo::config_parse.pl: %known_keys) +# + +sub init_defaultmlconf { + my $ml = $_[0]; + + %defaultmlconf=( + 'welcome', "yes", + 'announcements', "yes", + 'get_access', "open", + 'index_access', "open", + 'who_access', "open", + 'which_access', "open", + 'info_access', "open", + 'intro_access', "open", + 'advertise', "", + 'noadvertise', "", + 'description', "", + 'subscribe_policy', "open", + 'unsubscribe_policy', "open", + 'mungedomain', "no", + 'admin_passwd', "$ml.admin", + 'strip', "yes", + 'date_info', "yes", + 'date_intro', "yes", + 'archive_dir', "", + 'moderate', "no", + 'moderator', "", + 'approve_passwd', "$ml.pass", + 'sender', "owner-$ml", + 'maxlength', "40000", + 'precedence', "bulk", + 'reply_to', "", + 'restrict_post', "", + 'purge_received', "no", + 'administrivia', "yes", + 'resend_host', "", + 'debug', "no", + 'message_fronter', "", + 'message_footer', "", + 'message_headers', "", + 'subject_prefix', "", + 'taboo_headers', "", + 'taboo_body', "", + 'digest_volume', "1", + 'digest_issue', "1", + 'digest_work_dir', "", + 'digest_name', "$ml", + 'digest_archive', "", + 'digest_rm_footer', "", + 'digest_rm_fronter', "", + 'digest_maxlines', "", + 'digest_maxdays', "", + 'comments', "" + ); +} + + +# +# Mailman mailing list params that are not derived from Majordomo mailing lists params +# (e.g. bounce_matching_headers+forbbiden_posters vs. taboo_headers+taboo_body) +# If you need one of this params to be variable remove it here and add some code to the +# main procedure; additionally, you should compare it with what you have in +# /usr/lib/mailman/Mailman/mm_cfg.py +# + +sub init_defaultmmconf { + + %defaultmmconf=( + 'goodbye_msg', "\'\'", + 'umbrella_list', "0", + 'umbrella_member_suffix', "\'$umbrella_member_suffix\'", + 'send_reminders', "0", + 'admin_immed_notify', "1", + 'admin_notify_mchanges', "0", + 'dont_respond_to_post_requests', "0", + 'obscure_addresses', "1", + 'require_explicit_destination', "1", + 'acceptable_aliases', "\"\"\"\n\"\"\"\n", + 'max_num_recipients', "10", + 'forbidden_posters', "[]", + 'bounce_matching_headers', "\"\"\"\n\"\"\"\n", + 'anonymous_list', "0", + 'nondigestable', "1", + 'digestable', "1", + 'digest_is_default', "0", + 'mime_is_default_digest', "0", + 'digest_size_threshhold', "40", + 'digest_send_periodic', "1", + 'digest_header', "\'\'", + 'bounce_processing', "1", + 'minimum_removal_date', "4", + 'minimum_post_count_before_bounce_action', "3", + 'max_posts_between_bounces', "5", + 'automatic_bounce_action', "3", + 'archive_private', "0", + 'clobber_date', "1", + 'archive_volume_frequency', "1", + 'autorespond_postings', "0", + 'autoresponse_postings_text', "\'\'", + 'autorespond_admin', "0", + 'autoresponse_admin_text', "\'\'", + 'autorespond_requests', "0", + 'autoresponse_request_text', "\'\'", + 'autoresponse_graceperiod', "90" + ); +} + +# +# convert a Majordomo mailing list configuration (%mlconf) into a +# Mailman mailing list configuration (%mmconf) +# only those params are affected which can be derived from Majordomo +# mailing list configurations +# + +sub m2m { + + my $elem; + my $admin; + + $mmconf{"real_name"} = "\'" . $mlconf{"name"} . "\'"; + + # Mailman does not know the difference between owner and approver + for $admin (($mlconf{"primaryowner"}, @{$mlconf{"secondaryowner"}}, + $mlconf{"primaryapprover"}, @{$mlconf{"secondarapprover"}})) { + # merging owners and approvers may result in a loop: + if (lc($admin) ne lc("owner-" . $mlconf{"name"} . "\@" . $mydomain)) { + $mmconf{"owner"} .= ",\'" . "$admin" . "\'"; + } + } + $mmconf{"owner"} =~ s/^,//g; + $mmconf{"owner"} = "\[" . $mmconf{"owner"} . "\]"; + + # remove characters that will break Python + ($mmconf{"description"} = $mlconf{"description"}) =~ s/\'/\\\'/g; + $mmconf{"description"} = "\'" . $mmconf{"description"} . "\'"; + + $mmconf{"info"} = "\"\"\"\n" . $mlconf{"info"} . "\"\"\"\n"; + + $mmconf{"subject_prefix"} = "\'" . $mlconf{"subject_prefix"} . "\'"; + + $mmconf{"welcome_msg"} = "\"\"\"\n" . $mlconf{"intro"} . "\"\"\"\n"; + + # I don't know how to handle this because the reply_to param in the lists + # I had were not configured consistently + if ($mlconf{"reply_to"} =~ /\S+/) { + if ($mlconf{"name"} . "\@" =~ m/$mlconf{"reply_to"}/i) { + $mmconf{"reply_goes_to_list"} = "1"; + $mmconf{"reply_to_address"} = "\'\'"; + } + else { + $mmconf{"reply_goes_to_list"} = "2"; + $mmconf{"reply_to_address"} = "\'" . $mlconf{"reply_to"} . "\'"; + } + } + else { + $mmconf{"reply_goes_to_list"} = "0"; + $mmconf{"reply_to_address"} = "\'\'"; + } + + $mmconf{"administrivia"} = ($mlconf{"administrivia"} =~ m/yes/i) ? "1" : "0"; + $mmconf{"send_welcome_msg"} = ($mlconf{"welcome"} =~ m/yes/i) ? "1" : "0"; + + $mmconf{"max_message_size"} = int ($mlconf{"maxlength"} / 1000); + + $mmconf{"host_name"} = ($mlconf{"resend_host"} =~ /\S+/) ? + $mlconf{"resend_host"} : "\'" . $mydomain . "\'"; + + $mmconf{"web_page_url"} = "\'" . $myurl . "\'"; + + # problematic since Mailman does not know access patterns + # I assume, that if there was given a noadvertise pattern, the + # list shouldn't be visible at all + $mmconf{"advertised"} = ($mlconf{"noadvertise"} =~ /\.\*/) ? "0" : "1"; + + # confirm+approval is much to long winded for private sites + $mmconf{"subscribe_policy"} = + ($mlconf{"subscribe_policy"} =~ m/(open|auto)/i) ? "1" : + ($private =~ m/yes/i) ? "2" : "3"; + + # in case this is a private site allow list visiblity at most + $mmconf{"private_roster"} = + ($mlconf{"who_access"} =~ m/open/i and not $private =~ m/yes/i) ? "0" : + ($mlconf{"who_access"} =~ m/open|list/i) ? "1" : "2"; + + $mmconf{"moderated"} = ($mlconf{"moderate"} =~ m/yes/i) ? "1" : "0"; + # there is no way to a set a separate moderator in Mailman + + # external, since lengthy + mm_posters(); + + if ($mlconf{"message_fronter"} =~ /\S+/) { + $mmconf{"msg_header"} = "\"\"\"\n" . $mlconf{"message_fronter"} . "\"\"\"\n"; + } + else { + $mmconf{"msg_header"} = "\'\'"; + } + + if ($mlconf{"message_footer"} =~ /\S+/) { + $mmconf{"msg_footer"} = "\"\"\"\n" . $mlconf{"message_footer"} . "\"\"\"\n"; + } + else { + $mmconf{"msg_footer"} = "\'\'"; + } + + # gateway to news + $mmconf{"nntp_host"} = "\'" . $newsserver . "\'"; + $mmconf{"linked_newsgroup"} = "\'" . $newsprefix . $mlconf{"name"} . "\'"; + + if ($mlconf{"gated"} =~ m/yes/i) { + $mmconf{"gateway_to_news"} = "1"; + $mmconf{"gateway_to_mail"} = "1"; + $mmconf{"archive"} = "1"; + } + else { + $mmconf{"gateway_to_news"} = "0"; + $mmconf{"gateway_to_mail"} = "0"; + $mmconf{"archive"} = "0"; + } + + # print warnings if this seems to be an umbrella list + for $elem (@{$mlconf{"privileged"}}, @{$mlconf{"members"}}) { + $elem =~ s/\@$mydomain//gi; + if (defined($mlaliases{$elem . $umbrella_member_suffix})) { + print STDERR "\"" . $mlconf{"name"} . + "\" possibly forms part off/is an umbrella list, since \"$elem\" is a local mailing list alias\n"; + } + } + + # print warnings if we encountered a Taboo-Header or Taboo-Body + if ($mlconf{"taboo_headers"} =~ /\S+/ or $mlconf{"taboo_body"} =~ /\S+/) { + print STDERR "\"" . $mlconf{"name"} . "\" taboo_headers or taboo_body seem to be set - please check manually.\n"; + } +} + +# +# with some set theory on the member and priviliged list try to determine the params +# $mmconf{"member_posting_only"} and $mmconf{"posters"} +# + +sub mm_posters { + if ($mlconf{"restrict_post"} =~ /\S+/) { + my %privileged = (); + my %members = (); + my $key; + + foreach $key (@{$mlconf{"privileged"}}) { $privileged{$key} = "OK"; } + foreach $key (@{$mlconf{"members"}}) { $members{$key} = "OK"; } + + # are all members privileged, too ? + my $included = 1; + foreach $key (keys %members) { + if (not exists $privileged{$key}) { + $included = 0; + last; + } + } + if ($included) { + $mmconf{"member_posting_only"} = "1"; + + # posters = privileged - members: + my %diff = %privileged; + foreach $key (keys %members) { + delete $diff{$key} if exists $members{$key}; + } + + $mmconf{"posters"} = ""; + for $key (sort keys %diff) { + $mmconf{"posters"} .= ",\'" . $key . "\'"; + } + $mmconf{"posters"} =~ s/^,//g; + $mmconf{"posters"} = "[" . $mmconf{"posters"} . "]"; + } + else { + $mmconf{"member_posting_only"} = "0"; + + # posters = privileged: + $mmconf{"posters"} = ""; + for $key (sort keys %privileged) { + $mmconf{"posters"} .= ",\'" . $key . "\'"; + } + $mmconf{"posters"} =~ s/^,//g; + $mmconf{"posters"} = "[" . $mmconf{"posters"} . "]"; + } + } + else { + $mmconf{"member_posting_only"} = "0"; + $mmconf{"posters"} = "[]"; + } +} + diff --git a/contrib/mm-handler b/contrib/mm-handler new file mode 100644 index 00000000..0535cbfb --- /dev/null +++ b/contrib/mm-handler @@ -0,0 +1,236 @@ +#!/usr/local/bin/perl +## +## Sendmail mailer for Mailman +## +## Simulates these aliases: +## +##testlist: "|/home/mailman/mail/mailman post testlist" +##testlist-admin: "|/home/mailman/mail/mailman admin testlist" +##testlist-bounces: "|/home/mailman/mail/mailman bounces testlist" +##testlist-confirm: "|/home/mailman/mail/mailman confirm testlist" +##testlist-join: "|/home/mailman/mail/mailman join testlist" +##testlist-leave: "|/home/mailman/mail/mailman leave testlist" +##testlist-owner: "|/home/mailman/mail/mailman owner testlist" +##testlist-request: "|/home/mailman/mail/mailman request testlist" +##testlist-subscribe: "|/home/mailman/mail/mailman subscribe testlist" +##testlist-unsubscribe: "|/home/mailman/mail/mailman unsubscribe testlist" +##owner-testlist: testlist-owner + +## Some assembly required. +$MMWRAPPER = "/home/mailman/mail/mailman"; +$MMLISTDIR = "/home/mailman/lists"; +$SENDMAIL = "/usr/lib/sendmail -oem -oi"; +$VERSION = '$Id: mm-handler 5100 2002-04-05 19:41:09Z bwarsaw $'; + +## Comment this if you offer local user addresses. +$NOUSERS = "\nPersonal e-mail addresses are not offered by this server."; + +# uncomment for debugging.... +#$DEBUG = 1; + +use FileHandle; +use Sys::Hostname; +use Socket; + +($VERS_STR = $VERSION) =~ s/^\$\S+\s+(\S+),v\s+(\S+\s+\S+\s+\S+).*/\1 \2/; + +$BOUNDARY = sprintf("%08x-%d", time, time % $$); + +## Informative, non-standard rejection letter +sub mail_error { + my ($in, $to, $list, $server, $reason) = @_; + my $sendmail; + + if ($server && $server ne "") { + $servname = $server; + } else { + $servname = "This server"; + $server = &get_ip_addr; + } + + #$sendmail = new FileHandle ">/tmp/mm-$$"; + $sendmail = new FileHandle "|$SENDMAIL $to"; + if (!defined($sendmail)) { + print STDERR "$0: cannot exec \"$SENDMAIL\"\n"; + exit (-1); + } + + $sendmail->print ("From: MAILER-DAEMON\@$server +To: $to +Subject: Returned mail: List unknown +Mime-Version: 1.0 +Content-type: multipart/mixed; boundary=\"$BOUNDARY\" +Content-Disposition: inline + +--$BOUNDARY +Content-Type: text/plain; charset=us-ascii +Content-Description: Error processing your mail +Content-Disposition: inline + +Your mail for $list could not be sent: + $reason + +For a list of publicly-advertised mailing lists hosted on this server, +visit this URL: + http://$server/ + +If this does not resolve your problem, you may write to: + postmaster\@$server +or + mailman-owner\@$server + + +$servname delivers e-mail to registered mailing lists +and to the administrative addresses defined and required by IETF +Request for Comments (RFC) 2142 [1]. +$NOUSERS + +The Internet Engineering Task Force [2] (IETF) oversees the development +of open standards for the Internet community, including the protocols +and formats employed by Internet mail systems. + +For your convenience, your original mail is attached. + + +[1] Crocker, D. \"Mailbox Names for Common Services, Roles and + Functions\". http://www.ietf.org/rfc/rfc2142.txt + +[2] http://www.ietf.org/ + +--$BOUNDARY +Content-Type: message/rfc822 +Content-Description: Your undelivered mail +Content-Disposition: attachment + +"); + + while ($_ = <$in>) { + $sendmail->print ($_); + } + + $sendmail->print ("\n"); + $sendmail->print ("--$BOUNDARY--\n"); + + close($sendmail); +} + +## Get my IP address, in case my sendmail doesn't tell me my name. +sub get_ip_addr { + my $host = hostname; + my $ip = gethostbyname($host); + return inet_ntoa($ip); +} + +## Split an address into its base list name and the appropriate command +## for the relevant function. +sub split_addr { + my ($addr) = @_; + my ($list, $cmd); + my @validfields = qw(admin bounces confirm join leave owner request + subscribe unsubscribe); + + if ($addr =~ /(.*)-(.*)\+.*$/) { + $list = $1; + $cmd = "$2"; + } else { + $addr =~ /(.*)-(.*)$/; + $list = $1; + $cmd = $2; + } + if (grep /^$cmd$/, @validfields) { + if ($list eq "owner") { + $list = $cmd; + $cmd = "owner"; + } + } else { + $list = $addr; + $cmd = "post"; + } + + return ($list, $cmd); +} + +## The time, formatted as for an mbox's "From_" line. +sub mboxdate { + my ($time) = @_; + my @days = qw(Sun Mon Tue Wed Thu Fri Sat); + my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = + localtime($time); + + ## Two-digit year handling complies with RFC 2822 (section 4.3), + ## with the addition that three-digit years are accommodated. + if ($year < 50) { + $year += 2000; + } elsif ($year < 1900) { + $year += 1900; + } + + return sprintf ("%s %s %2d %02d:%02d:%02d %d", + $days[$wday], $months[$mon], $mday, + $hour, $min, $sec, $year); +} + +BEGIN: { + $sender = undef; + $server = undef; + @to = (); + while ($#ARGV >= 0) { + if ($ARGV[0] eq "-r") { + $sender = $ARGV[1]; + shift @ARGV; + } elsif (!defined($server)) { + $server = $ARGV[0]; + } else { + push(@to, $ARGV[0]); + } + shift @ARGV; + } + + if ($DEBUG) { + $to = join(',', @to); + print STDERR "to: $to\n"; + print STDERR "sender: $sender\n"; + print STDERR "server: $server\n"; + exit(-1); + } + +ADDR: for $addr (@to) { + $prev = undef; + $list = $addr; + + $cmd= "post"; + if (! -f "$MMLISTDIR/$list/config.pck") { + ($list, $cmd) = &split_addr($list); + if (! -f "$MMLISTDIR/$list/config.pck") { + $was_to = $addr; + $was_to .= "\@$server" if ("$server" ne ""); + mail_error(\*STDIN, $sender, $was_to, $server, + "no list named \"$list\" is known by $server"); + next ADDR; + } + } + + $wrapper = new FileHandle "|$MMWRAPPER $cmd $list"; + if (!defined($wrapper)) { + ## Defer? + print STDERR "$0: cannot exec ", + "\"$MMWRAPPER $cmd $list\": deferring\n"; + exit (-1); + } + + # Don't need these without the "n" flag on the mailer def.... + #$date = &mboxdate(time); + #$wrapper->print ("From $sender $date\n"); + + # ...because we use these instead. + $from_ = <STDIN>; + $wrapper->print ($from_); + + $wrapper->print ("X-Mailman-Handler: $VERSION\n"); + while (<STDIN>) { + $wrapper->print ($_); + } + close($wrapper); + } +} diff --git a/contrib/mm-handler.readme b/contrib/mm-handler.readme new file mode 100644 index 00000000..a406e33e --- /dev/null +++ b/contrib/mm-handler.readme @@ -0,0 +1,151 @@ +Contributed by David Champion <dgc@uchicago.edu> +See also ../README.SENDMAIL + +What? +----- +Mm-handler is a mail delivery agent (MDA) -- a "mailer", in Sendmail +lingo. Its function is to assume authority for messages destined to +Mailman lists, so that they're off sendmail's hands, and you (the site +administrator) don't need to maintain databases of aliases and such. It +takes a small bit of work to set up, but once that's done, you'll never +need to mess with aliases for Mailman's sake again. + +When? +----- +The only further catch is that mm-handler is only really useful when +it mostly "owns" its mail domain (the hostname part after an e-mail +address's "@" symbol) -- when you can dedicate the mail domain to +Mailman. If you have a limited set of exceptions -- a few users, for +example -- you can still use it, but for sites with a dynamic or even +mix of users (or forwarders) and lists, it might not gain you much. + +How do you know whether mm-handler is appropriate for you? Let's look +at some examples. If you're running lists off your primary DNS domain +name, you probably have a mix of lists and users in your namespace. Take +python.org, for example: it hosts Mailman lists, and it hosts users' +personal accounts. There aren't a whole bunch of either, but the ratio +is probably fairly near 1:1. Mm-handler is not very useful here, because +there's no simple way to separate user addresses from list addresses -- +not to mention that mm-handler is written in perl, so that's just bad +form. + +This begs two different, complementary situations. A hypothetical +domain, incidents.int, is used mostly for mailing lists. It's a +front-end site, and not a general user mail service. There might be +a couple of user addresses -- system administrators and such -- but +these are few in number and easily adjusted manually by the site +administrator. The 250 mailing lists at the site are much more dynamic, +and a much bigger pain to keep track of by editing an alias file. This +site can easily benefit from mm-handler. + +Inversely, mail.aero, another hypothetical domain, provides POP mail +service to employees of the aerospace industry. Its addresses are +almost entirely users, although it maintains a few mailing lists for +convenience. This site has nothing to gain from mm-handler. It's much +easier to maintain an alias file of 10 lists than to dedicate the domain +to Mailman, and put all 10,000 aerospace workers in a user table. + +Those are the trickier cases. The case where mm-handler really works +best is when you can dedicate a single hostname under your DNS domain +to mailing lists, and host no user accounts there. At the University of +Chicago, we do this with listhost.uchicago.edu. SourceForge does this, +too, although I don't believe they use Sendmail. + +If your site is like that, you should read on. + +How? +---- +Set-up isn't all that complicated. I've included a file here called +"mailman.mc". This is the m4 file that I use on my list server, and you +can likely use it with few changes at your site. It's well-annotated; +the rationale for each parameter (or set of parameters) is provided in +m4 (ahem) comments. + +So, the simple steps are as follows: + +1. Copy mailman.mc, and make any changes you need at your site. You + DEFINITELY need some changes. There are hostnames in there that you + need to adjust, and chances are that you'll need to change some other + parameters (like the host OS), too. [1] + +2. Install mm-handler. Because my server's sendmail-related files live + in /etc/mail, I keep mm-handler there, too. YMMV. + +3. Edit mm-handler, and make any changes you need at your site. You + probably want to change $MMWRAPPER and $MMLISTDIR at line 14, and you + *might* want to take a look at the helpful boilerplate text beginning + at line 64. (This text is sent whenever someone tries to send mail to + a nonexistent list address on your mail domain.) + +4. You should set up a virtusertable. (See mailman.mc for an + explanation.) There's an example of a good, minimal virtusertable + in this distribution. The virtusertable begins as a text file named + "virtusertable", stored in the same directory as all the other + Sendmail files, but it's converted to a map file for Sendmail's use. + Install the virtusertable, and (re)make the map file. [2] + +5. You absolutely must have a mailertable, or all of this goes nowhere. + Like virtusertable, the mailertable is a map that begins as text and + gets converted. It's named "mailertable", and it's probably pretty + simple. Mine looks like this: + + listtest.uchicago.edu mailman:listtest.uchicago.edu + + This says: assign all incoming mail (that was not intercepted by the + virtusertable) and that is in the listtest.uchicago.edu domain to the + "mailman" mailer, and tell the "mailman" mailer that the hostname + we're using is "listtest.uchicago.edu". You can support multiple + virtual hosts using mm-handler just by placing corresponding lines in + mailertable. + + Be sure to make this map, too! + +6. The mailer definition (see the end of mailman.mc, or your own .mc + file) for mm-handler sets the user/group that mm-handler will run + under. (I use mailman:other.) Be sure that mm-handler is executable + by this user or group. You almost certainly need the user to be the + same as the Mailman user, and this user is almost always called + "mailman", so you probably shouldn't change the defaults. + +7. Generate your new sendmail.cf file. See the sendmail documentation if + you're not familiar with this procedure. [1] + +8. Stop sendmail on your list server, if you haven't already. Install + the new sendmail.cf file wherever your sendmail.cf file belongs. + (This depends on how sendmail was compiled, but most systems support + using /etc/sendmail.cf.) + +9. Cross your fingers and restart sendmail. + +A. Barry warns that Mailman now needs you to modify your + Mailman/mm_cfg.py file, adding this line: + + MTA = None + + This tells Mailman that it doesn't need to do anything special when + it creates or deletes mailing lists through the web. For more + information, take a look at the comments for this variable in your + Defaults.py file (but never edit this file -- always edit mm_cfg.py + instead!). + +That's it! With any luck, you're fully functional. + +-- +[1] The .mc file is the standard, supported way of configuring sendmail. + I'm not going to get into this here, and I'm not going to tell + you how to write raw sendmail.cf stuff, because if you need to do + it this way then you need something more comprehensive than I can + provide. If you need help with the .mc -> .cf process, I recommend + these links: + + http://www.sendmail.org/~ca/email/setup1.html + http://www.sendmail.org/~ca/email/doc8.9/README.cf.html + http://www.hserus.net/sendmail.html + +[2] This is often done with something like "makemap hash + /etc/virtusertable </etc/virtusertable", but it could be different + on your server. Consult the sendmail documentation if you do not + know. + +-- +$Id: mm-handler.readme 4287 2001-10-27 02:30:51Z bwarsaw $ diff --git a/contrib/qmail-to-mailman.py b/contrib/qmail-to-mailman.py new file mode 100644 index 00000000..76f25225 --- /dev/null +++ b/contrib/qmail-to-mailman.py @@ -0,0 +1,115 @@ +#! @PYTHON@ + +# Configuration variables - Change these for your site if necessary. +MailmanHome = "@prefix@"; # Mailman home directory. +MailmanOwner = "postmaster@localhost"; # Postmaster and abuse mail recepient. +# End of configuration variables. + +# qmail-to-mailman.py +# +# Interface mailman to a qmail virtual domain. Does not require the creation +# of _any_ aliases to connect lists to your mail system. +# +# Bruce Perens, bruce@perens.com, March 1999. +# This is free software under the GNU General Public License. +# +# This script is meant to be called from ~mailman/.qmail-default . It catches +# all mail to a virtual domain, in my case "lists.hams.com". It looks at the +# recepient for each mail message and decides if the mail is addressed to a +# valid list or not, and bounces the message with a helpful suggestion if it's +# not addressed to a list. It decides if it is a posting, a list command, or +# mail to the list administrator, by checking for the -admin, -owner, and +# -request addresses. It will recognize a list as soon as the list is created, +# there is no need to add _any_ aliases for any list. It recognizes mail to +# postmaster, mailman-owner, abuse, mailer-daemon, root, and owner, and +# routes those mails to MailmanOwner as defined in the configuration +# variables, above. +# +# INSTALLATION: +# +# Install this file as ~mailman/qmail-to-mailman.py +# +# To configure a virtual domain to connect to mailman, create these files: +# +# ~mailman/.qmail-default +# |preline @PYTHON@ @prefix@/mail-in.py +# +# /var/qmail/control/virtualdomains: +# DOMAIN.COM:mailman +# +# Note: "preline" is a QMail program which ensures a Unix "From " header is +# on the message. Archiving will break without this. +# +# Replace DOMAIN.COM above with the name of the domain to be connected to +# Mailman. Note that _all_ mail to that domain will go to Mailman, so you +# don't want to put the name of your main domain here. In my case, I created +# lists.hams.com for Mailman, and I use hams.com as my regular domain. +# +# After you edit /var/qmail/control/virtualdomains, kill and restart qmail. +# + +import sys, os, re, string + +def main(): + os.nice(5) # Handle mailing lists at non-interactive priority. + + os.chdir(MailmanHome + "/lists") + + try: + local = os.environ["LOCAL"] + except: + # This might happen if we're not using qmail. + sys.stderr.write("LOCAL not set in environment?\n") + sys.exit(100) + + local = string.lower(local) + local = re.sub("^mailman-","",local) + + names = ("root", "postmaster", "mailer-daemon", "mailman-owner", "owner", + "abuse") + for i in names: + if i == local: + os.execv("/var/qmail/bin/qmail-inject", + ("/var/qmail/bin/qmail-inject", MailmanOwner)) + sys.exit(0) + + type = "post" + types = (("-admin$", "bounces"), + ("-bounces$", "bounces"), + ("-join$", "join"), + ("-leave$", "leave"), + ("-owner$", "owner"), + ("-request$", "request")) + + for i in types: + if re.search(i[0],local): + type = i[1] + local = re.sub(i[0],"",local) + + if os.path.exists(local): + os.execv(MailmanHome + "/mail/mailman", + (MailmanHome + "/mail/mailman", type, local)) + else: + bounce() + sys.exit(111) + +def bounce(): + bounce_message = """\ +TO ACCESS THE MAILING LIST SYSTEM: Start your web browser on +http://%s/ +That web page will help you subscribe or unsubscribe, and will +give you directions on how to post to each mailing list.\n""" + sys.stderr.write(bounce_message % (os.environ["HOST"])) + sys.exit(100) + +try: + sys.exit(main()) +except SystemExit, argument: + sys.exit(argument) + +except Exception, argument: + info = sys.exc_info() + trace = info[2] + sys.stderr.write("%s %s\n" % (sys.exc_type, argument)) + sys.stderr.write("Line %d\n" % (trace.tb_lineno)) + sys.exit(111) # Soft failure, try again later. diff --git a/contrib/rotatelogs.py b/contrib/rotatelogs.py new file mode 100644 index 00000000..b88cb802 --- /dev/null +++ b/contrib/rotatelogs.py @@ -0,0 +1,103 @@ +#! @PYTHON@ +# +# Copyright (C) 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. + +"""Check the error logs and send any which have information in them. + +If any log entries exist, a message is sent to the mailman owner address +and the logs are rotated. +""" + +# GETTING STARTED +# +# Run this program as root from cron, preferably at least daily. Running +# as root is optional, but will preserve the various modes and ownerships +# of log files in "~mailman/logs". If any entries are in "errors" or +# "smtp-errors", they will be mailed to the mailman owner address. +# +# Set COMPRESS_LOGFILES_WITH in mm_cfg.py to "gzip" to get rotated logfiles +# to be compressed. +# +# Hacked from some existing Mailman code by +# Sean Reifschneider <jafo-mailman@tummy.com> +# Please direct questions on this to the above address. +# + +showLines = 100 # lines of log messages to display before truncating + +import sys, os, string, time, errno +import paths +from Mailman import mm_cfg, Utils +import fileinput, socket, time, stat + +# Work around known problems with some RedHat cron daemons +import signal +signal.signal(signal.SIGCHLD, signal.SIG_DFL) + + +newLogfiles = [] +text = [] +text.append('Mailman Log Report') +text.append('Generated: %s' % time.ctime(time.time())) +text.append('Host: %s' % socket.gethostname()) +text.append('') + +logDate = time.strftime('%Y%m%d-%H%M%S', time.localtime(time.time())) +textSend = 0 +for log in ( 'error', 'smtp-failures' ): + fileName = os.path.join(mm_cfg.LOG_DIR, log) + + # rotate file if it contains any data + stats = os.stat(fileName) + if stats[stat.ST_SIZE] < 1: continue + fileNameNew = '%s.%s' % ( fileName, logDate ) + newLogfiles.append(fileNameNew) + os.rename(fileName, fileNameNew) + open(fileName, 'w') + os.chmod(fileName, stat.S_IMODE(stats[stat.ST_MODE])) + try: os.chown(fileName, stats[stat.ST_UID], stats[stat.ST_GID]) + except OSError: pass # permission denied, DOH! + + textSend = 1 + tmp = '# FILE: %s #' % fileNameNew + text.append('#' * len(tmp)) + text.append(tmp) + text.append('#' * len(tmp)) + text.append('') + + linesLeft = showLines # e-mail first linesLeft of log files + for line in fileinput.input(fileNameNew): + if linesLeft == 0: + text.append('[... truncated ...]') + break + linesLeft = linesLeft - 1 + line = string.rstrip(line) + text.append(line) + text.append('') + +# send message if we've actually found anything +if textSend: + text = string.join(text, '\n') + '\n' + siteowner = Utils.get_site_email() + Utils.SendTextToUser( + 'Mailman Log Report -- %s' % time.ctime(time.time()), + text, siteowner, siteowner) + +# compress any log-files we made +if hasattr(mm_cfg, 'COMPRESS_LOGFILES_WITH') and mm_cfg.COMPRESS_LOGFILES_WITH: + for file in newLogfiles: + os.system(mm_cfg.COMPRESS_LOGFILES_WITH % file) diff --git a/contrib/virtusertable b/contrib/virtusertable new file mode 100644 index 00000000..eb5eb744 --- /dev/null +++ b/contrib/virtusertable @@ -0,0 +1,37 @@ +## +## Example virtusertable for use with a Mailman site running mm-handler. +## +## $Id: virtusertable 4287 2001-10-27 02:30:51Z bwarsaw $ +## + +## +## My server's hostname is nospam, but we don't honor that as a +## Mailman mail domain. Anything @nospam.uchicago.edu should be +## forwarded to our master Mailman admin address. +## +@nospam.uchicago.edu mailman-owner@midway.uchicago.edu + +## +## Redirect mail to the standard Mailman admin addresses to the +## master admin address. (Midway.uchicago.edu is our site's central +## mail-routing server, and it carries aliases for maintenance groups. +## Not a good plan to entrust Mailman maintenance mail to Mailman.) +## +mailman@listhost.uchicago.edu mailman-owner@midway.uchicago.edu +mailman-owner@listhost.uchicago.edu mailman-owner@midway.uchicago.edu + +## +## These addresses are required or recommended either by convention +## or by RFC 2142, "Mailbox Names for Common Services, Roles and +## Functions". Honor them. +## +MAILER-DAEMON@listhost.uchicago.edu mailman-owner@midway.uchicago.edu +postmaster@listhost.uchicago.edu mailman-owner@midway.uchicago.edu +webmaster@listhost.uchicago.edu mailman-owner@midway.uchicago.edu +abuse@listhost.uchicago.edu sun-admin@midway.uchicago.edu +root@listhost.uchicago.edu sun-admin@midway.uchicago.edu + +## +## If I had a need, I could put user accounts in here, too. +## +dgc@listhost.uchicago.edu dgc@where.my.mail.really.goes |