diff options
Diffstat (limited to 'contrib/majordomo2mailman.pl')
-rw-r--r-- | contrib/majordomo2mailman.pl | 691 |
1 files changed, 691 insertions, 0 deletions
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"} = "[]"; + } +} + |