[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: distributed mailing list architecture



Maybe you have some use for the appended perl script.  I created it for a
list setup quite similar to what is currently being discussed on
cypherpunks.  It has never ever been used in production; you'll notice
yourself that the code is not too nice.

The script currently tries to handle majordomo and SmartList exploders; at
least SmartList needs to be hacked a little bit to avoid daily subscription
approvals to people being moved between different sub-lists.

Distribute and use this script freely; credit is appreciated.

tlr
------------------------------
#!/usr/bin/perl

############################################################
#
# $Id: distlist.pl,v 1.2 1997/01/24 17:20:38 roessler Exp $
#

#
# Handle distributed mailing lists.
#

require 'getopts.pl';

$c_sublists='lists';		# Data about the sublists
$c_distfile='dist';		# A list of mail addresses
$c_datafile='dist.data';	# _Our_ list of addresses
$c_contact='[email protected]';
				# Whom to contact in case of problems
$sendmail="| /usr/sbin/sendmail -t -odq";


$debug=0;			# Debugging output.  Can also be
				# turned on by using the -x switch.

$signature="-- \nThis mail has been generated automatically by the distlist\n".
    "program. Please contact $c_contact in case of problems.\n";


@sublists=();
@newsubs=();

#
# Command line processing
#

&dprint (1, 'Processing the command line...\n');
&Getopts('s:d:D:c:x:');

$c_sublists=$opt_s if $opt_s;
$c_distfile=$opt_d if $opt_d;
$c_datafile=$opt_D if $opt_D;
$c_contact=$opt_c  if $opt_c;
$debug=$opt_x	   if $opt_x;

#
# Read the data and config files, perform various checks.
#

#
# sublists
#

&dprint(1, "Reading $c_sublists...");

# format: addr:maxsubscr:listtype:password:adminrequest:maintainer

open(SUBLISTS, $c_sublists) || die("Can't open $c_sublists");
SUBLIST: while(<SUBLISTS>) {
    next SUBLIST if /^#/ || /^$/;
    chop;
    ($addr, $maxsubscr, $listtype, $password, $admin, $maintainer) = split(/:/, $_);
    $s_maxsubscr{$addr}=$maxsubscr;
    $s_listtype{$addr}=$listtype;
    $s_password{$addr}=$password;
    $s_admin{$addr}=$admin;
    $s_maintainer{$addr}=$maintainer;
    $s_onlist{$addr}=0;
    $s_addem{$addr}="";
    $s_delem{$addr}="";
    &dprint(3,"Parsed list $addr: maximum $maxsubscr people; listtype $listtype; password $password.; administrative address $admin; maintainer $maintainer");
    @sublists=(@sublists, $addr);
}

close(SUBLISTS);

# In addition, we define a special list for people to be redistributed later.

$s_maxsubscr{'later'}=100000;		# Very big. ;)
$s_listtype{'later'}="";
$s_password{'later'}="";
$s_onlist{'later'}=0;
$s_addem{'later'}="";
$s_delem{'later'}="";

#
# Read our dist file
#



# format: user:list

if(open(DATAFILE, $c_datafile)) {
    &dprint(1, "Reading $c_datafile...");

  USERLINE: while(<DATAFILE>) {
      next USERLINE if /^#/ || /^$/;
      chop;
      ($user, $list) = split(/:/, $_);
      
      # Check if the list exists; handle user.
      
      if(! $s_maxsubscr{$list}) {
	  &dprint(2, "While checking user $user:  $list does no longer exist.");
	  $list="later";
      } elsif( $s_onlist{$list} >= $s_maxsubscr{$list} ) {
	  &dprint(2, "Warning: $list is full.  Redistributing people later.");
	  $s_delem{$list}=join(':',$user,$s_delem{$list});
	  $list="later";
      } 
      
      $s_onlist{$list}++;
      $u_list{$user}=$list;
      
      $deletem{$user}=$user;
  }
    
    close(DATAFILE);
    &dprint(1,"$c_datafile finished.");
} else {
    &dprint(1, "Warning:  Can't read $c_datafile.");
}


#
# Now, read the real distribution file.
#

&dprint(1, "Reading $c_distfile...");

open(DISTFILE, $c_distfile) || die("Can't open $c_distfile");

 DISTLINE: while(<DISTFILE>) {
     next DISTLINE if /^#/ || /^$/ || /^\(/;
     chop;
     
     if(!$u_list{$_}) {			# A new member.
	 &dprint(2, "Found a new member: $_.");
	 @newsubs=($_, @newsubs);
     } else {				# We know him.
	 &dprint(3, "Well-known: $_.");
	 delete $deletem{$_};
     }
     
 }

close(DISTFILE);


#
# Handle deletions.
#

foreach $user (keys %deletem) {
    $list=$u_list{$user};
    delete $u_list{$user};
    $s_onlist{$list}--;
    $s_delem{$list}=join(':', $s_delem{$list}, $user);
    &dprint(3, "Removing $user from sublist $list.");
}

#
# Handle postponed subscriptions:  The later list.
#

foreach (keys %u_list) {
    next unless $u_list{$_} eq "later";
    @newsubs=($_, @newsubs);
    delete $u_list{$_};
    &dprint(3, "Adding $_ to the list of new subscriptions.  Was postponed.");
}


&dprint(1, "Distributing new subscriptions...");

 NEWSUBS: foreach $user (@newsubs) {

     $avg=0.0;
     foreach $l (@sublists) {
	 $avg += $s_onlist{$l}/$s_maxsubscr{$l};
     }
     
     $avg = $avg / ($#sublists + 1.0);
     
     if ($avg >= 1) {
	 &dprint(2, "Warning: All sublists are full while trying to insert $user.");
     }

     undef $possible;

     foreach $l (@sublists) {
	 if($s_onlist{$l} <= $avg*$s_maxsubscr{$l}) {
	     $possible=$l;
	 }

	 if($s_onlist{$l} < $avg*$s_maxsubscr{$l}) {
	     last;
	 }
     }

     if($possible) {
	 $l = $possible;
     }
     
     $s_addem{$l}=join(':', $s_addem{$l}, $user);
     $s_onlist{$l}++;
     $u_list{$user}=$l;
     
 }

#
# Write our own data file.
#

if(open(DATAFILE, ">$c_datafile")) {
    foreach (keys %u_list) {
	printf DATAFILE "%s:%s\n", $_, $u_list{$_};
    }
    close(DATAFILE);
} else {
    &dprint(1, "Warning: Can't write $c_datafile.");
}

#
# The lists have been put together.  Commit the changes.
#

&dprint(1, "Committing the changes...");

foreach $l (@sublists) {
    if($s_listtype{$l} eq "majordomo") {
	&commit_majordomo($l);
    } elsif ($s_listtype{$l} eq "smartlist") {
	&commit_smartlist($l);
    } else {
	&dprint(1, "While trying to commit changes for $l:");
	&dprint(1, "Unknown list type $s_listtype{$l}.\n");
    }
}

#
# To be done:  Print out some statistics.
#

print "Distlist results:\n";
print "-----------------\n";
print "\n";
printf "There are currently %d subscribers on %d sublists.\n\n", scalar(keys %u_list), $#sublists+1;

$full_lists=0;

printf "%-40s     on  max\n", "Name";
printf "----------------------------------------------------\n";

foreach $l (@sublists) {
    printf "%-40s   %4d %4d", $l, $s_onlist{$l}, $s_maxsubscr{$l};
    
    if($s_onlist{$l} >= $s_maxsubscr{$l}) {
	print "   *** This list is full ***";
	$full_lists++;
    }

    print "\n";
}

if($full_lists) {
    print "\n$full_lists of the sublists are *full*.  Please get in touch\n";
    print "with the maintainers.\n";
}

print "\n\n";
print $signature;

############################################################
#
# Some helper functions.
#

# Print out diagnostics.

sub dprint
{
    if($_[0] <= $debug) {
	printf STDERR "%s\n", $_[1];
    }
}


sub commit_majordomo
{
    my $list=$_[0];
    my @bla;
    my $ll;
    my $user;

    &dprint(2, "Committing changes to majordomo list $list");
    &dprint(3, "to be added: $s_addem{$list}");
    &dprint(3, "to be deleted: $s_delem{$list}");

    ($ll, @bla)=split(/@/, $list);

    if(!open(SENDMAIL, $sendmail)) {
	&dprint(1, "Warning:  Can't start sendmail when committing changes to $list");
    }

    print SENDMAIL "To: $s_admin{$list}\n";
    print SENDMAIL "From: $c_contact\n";
    print SENDMAIL "\n\n";
    
    foreach $user (@bla=split(/:/, $s_addem{$list})) {
	print SENDMAIL "approve $s_password{$list} subscribe $ll $user\n"
	    if $u_list{$user};
    }

    foreach $user (@bla=split(/:/, $s_delem{$list})) {
	print SENDMAIL "approve $s_password{$list} unsubscribe $ll $user\n"
	    unless $u_list{$user} eq $list;
    }

    print SENDMAIL $signature;
    
    close(SENDMAIL);
}

sub smartlist_xcommand
{
    my ($list, $to, $passwd, $command, $maintainer) = @_;

    if(!open(SENDMAIL, $sendmail)) {
	&dprint(1, "Can't start sendmail when committing changes for $list");
    }

    print SENDMAIL "To: $to\n";
    print SENDMAIL "From: $c_contact\n";
    print SENDMAIL "X-Command: $maintainer $passwd $command\n";
    print SENDMAIL "\n\n";
    print SENDMAIL $signature;

    close SENDMAIL;
}	
	
    

sub commit_smartlist
{
    my $list=$_[0];
    my @bla;
    my $user;

    &dprint(2, "Committing changes to smartlist list $list");
    
    foreach $user (@bla=split(/:/, $s_addem{$list})) {
	&smartlist_xcommand($list, $s_admin{$list}, $s_password{$list},
			    "subscribe $user", $s_maintainer{$list})
	    if $u_list{$user};
    }

    foreach $user (@bla=split(/:/, $s_delem{$list})) {
	&smartlist_xcommand($list, $s_admin{$list}, $s_password{$list},
			    "unsubscribe $user", $s_maintainer{$list})
	    unless $u_list{$user} eq $list;
    }
}