aboutsummaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/README4
-rw-r--r--contrib/README.check_perms_grsecurity14
-rw-r--r--contrib/auto116
-rw-r--r--contrib/check_perms_grsecurity.py180
-rw-r--r--contrib/mailman.mc143
-rw-r--r--contrib/majordomo2mailman.pl691
-rw-r--r--contrib/mm-handler236
-rw-r--r--contrib/mm-handler.readme151
-rw-r--r--contrib/qmail-to-mailman.py115
-rw-r--r--contrib/rotatelogs.py103
-rw-r--r--contrib/virtusertable37
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