#!/usr/local/bin/perl
#                                                          # -*-Perl-*-
#============================================================================
# MJ_BUILD_ALIASES
#
# This routine builds a Majordomo aliases using the ownership
# info in the individual list config files. Lists without an
# "owner" defined will not be activated in the aliases file.
# The resulting aliases file can then be combined with other
# system aliases to make a complete aliases file for sendmail.
#
# In addition, the "owner" field is used to automatically define
# a "majordomo-users" mailing list. Addresses listed in the
# %ignore_owners array will not be included in the list.
#
#============================================================================
# THE BEST WAY TO IMPLEMENT:
#
# 1) Create a file called <progname>.args (usually mj_build_aliases.args)
# in the Majordomo directory containing all the arguments that would
# normally be used on the command-line (eg, -a -d -o root). 
#
# 2) Add a cronjob (timing of your choice) for:
# 	/path/to/wrapper mj_build_aliases @
#
# 3) Implement mj_create_list/mj_delete_list (in MajorCool /contrib/).
# These programs automatically support "mj_build_aliases @".
#
# You will be left with a system that updates its aliases upon every
# list add or delete, as well as hourly/daily/cron-ly, based upon the
# configuration you have defined in mj_build_aliases.args.
#
# Cool! (MajorCool!)
#
#============================================================================
# HISTORY
# 
# 2.1  1998/11/09 18:34:22  bhoule
# - Fixed "nobody" expansion on resend command line.
#
# 2.0  1998/02/24 20:34:22  bhoule
# - @filename arg support so build can be called from create/delete
#   scripts without hardcoding any command-line arguments.
# - Added -a/-d archive/digest command line support.
#   ... -n for sendmail 8.8 ,nobody expansion
#   ... -k to keep lists associated with owners in list-owner file
#   ... -s outgoing suffix flag
# - Changed meaning of -O, -o, and -A to make more sense.
#
# 1.3  1997/07/16 15:16:35  ricky
# - Removed RCS tagging of alias file.
#
# 1.2  1997/07/16 15:13:01  ricky
# - Adding a new flag: -m (mail) to send via e-mail a report of changes,
#   computed from the old $aliases file. (Verbose mode will also print
#   the changes on STDERR.
# - Changing the command line options parsing, now it uses Getopts.
#
# 1.1  1997/07/16 15:02:34  ricky
# Initial revision by ricky@ornet.co.il
#
# 1.0
# Released as part of MajorCool package.
#
#============================================================================
$main'program_name	= 'mj_build_aliases';
$MJBA_revision		= ' $Revision: 2.0 $ ';
$MJBA_revision		=~ s/^\D*(\d+\.\d+)\D*$/$1/;
$|			= 0;

# change these to match the programs you might use
#
$mj_resend		= "resend";
$mj_archive		= "archive";
$mj_digest		= "digest";
$mj_log			= "mj_log";
$mj_filter		= "mj_filter";
$mj_owner_bounce	= "mj_owner_bounce";
$mj_list_unavail	= "mj_list-unavail";
#============================================================================
# DEFAULTS
#============================================================================
$cf			= $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf"; 
$aliases		= "majordomo_aliases";
$owner_list		= "majordomo-users";
$owner			= "postmaster";
$suffix			= "outgoing";

# these owners will not be added to the $owner_list list
#
%ignore_owners = (
	'nobody',		1,
	'postmaster',		1,
);

require "getopts.pl";

# This code loosely based on resend's use of '@' files. If the
# first argument is "@filename", we will read the real arguments
# from "filename" and shove them onto the ARGV for processing
# by &Getopts().
#
# If '@' but no filename is apecified, then '@<progname>.args'
# is assumed. This lets us call mj_build_aliases from other
# programs without hardcoding the arguments within.
#
if ($ARGV[0] =~ /^\@/) {
	$fn = shift(@ARGV);
	$fn =~ s/^@//;
	$fn = "$0.args" unless $fn;
	open(AV, $fn) || die("open(AV, \"$fn\"): $!\nStopped");
	undef($/);	# set input field separator
	$av = <AV>;	# read whole file into string
	close(AV);
	@av = split(/\s+/, $av);
	unshift(@ARGV, @av);
	$/ = "\n";
}

die("
Usage: $main'program_name [-C config_file] [-A aliases_file]
		[-O owners_list] [-o owner] [-v] [-f] [-m] [-t] [-n]

Options:
	-C config_file    Majordomo config ($cf)
	-A aliases_file   destination aliases file ($aliases)
	-O owners_list    list-owners list ($owner_list)
	-o owner          Majordomo mail owner ($owner)
	-s suffix         outgoing alias suffix ($suffix)
	-a                create archive aliases
	-d                create digest aliases
	-n                use sendmail 8.8 'nobody' expansion
	-k                keep lists with owners in $owner_list
	-m                mail results to owner
	-f                force rebuild
	-v                verbose
	-t                test only
") unless &Getopts("C:A:O:o:s:adnkmftv");

$cf		= $opt_C if $opt_C;
$aliases	= $opt_A if $opt_A;
$owner_list	= $opt_O if $opt_O;
$owner		= $opt_o if $opt_o;
$suffix		= $opt_s if $opt_s;
$nobody		= ",nobody" if $opt_n;

#============================================================================
# MAJORDOMO INITIALIZATION
#============================================================================
# Read and execute the .cf file
die("$cf not readable; stopped") unless -r $cf;
die("require of majordomo.cf failed $@") unless require $cf;

# Go to the home directory specified by the .cf file
chdir("$homedir");

# All these should be in the standard PERL library
unshift(@INC, $homedir);
require "ctime.pl";		# To get MoY definitions for month abbrevs
require "majordomo_version.pl";	# What version of Majordomo is this?
require "majordomo.pl";		# all sorts of general-purpose Majordomo subs
require "shlock.pl";		# NNTP-style file locking
require "config_parse.pl";	# functions to parse the config files

# Here's where the fun begins...

# check to see if the cf file is valid
die("listdir not defined. Is majordomo.cf being included correctly?")
	if !defined($listdir);

# where do we look for files, by default?
$filedir = $listdir unless defined($filedir);
$filedir_suffix = ".archive" unless defined($filedir_suffix);

#============================================================================
# ALIAS COMMANDS
#============================================================================
$wrapper	= "|$homedir/wrapper";

$majordomo_cmd	= "$wrapper majordomo";		# "-l $list" optional
$digest_cmd	= "$wrapper $mj_digest -rlC";	# "$list", "$list-$suffix"
$resend_cmd	= "$wrapper $mj_resend -l";	# "$list", "$list-$suffix"
$archive_cmd	= "$wrapper $mj_archive -a -f";	# archive-base, freq (-[dmy])
$log_cmd	= "$wrapper $mj_log";		# "$list" needed
$filter_cmd	= "$wrapper $mj_filter";	# no args needed
$bounce_cmd	= "$wrapper $mj_owner_bounce";	# no args needed
$unavail_cmd	= "$wrapper $mj_list_unavail";	# "$list" needed

# check if process already running
#
unless ($opt_t || &shlock("$listdir/.build.LOCK")) {
	printf STDERR "$main'program_name: build already running\n" if $opt_v;
	exit;
}

# check if $listdir or config files have changed
#
if (-r "$ENV{HOME}/$aliases" && !$opt_f) {
	printf STDERR "$main'program_name: checking for file changes\n" if $opt_v;

	unless (`find $listdir \\( -type d -name $listdir -o -type f -name '*.config' \\) -follow -newer $ENV{HOME}/$aliases -print`) {
		print STDERR "$main'program_name: no Majordomo list changes detected\n";
		exit(0);
	}
}

# open alias file
#
unless ($opt_t) {
	printf STDERR "$main'program_name: rebuilding $aliases\n" if $opt_v;
	open(STDOUT, ">$ENV{HOME}/$aliases.tmp");
}

# match all lists
#
local($nlists_total, $nlists_active) = 0;
opendir(RD_DIR, $listdir) || die("opendir failed $!");
@lists = readdir(RD_DIR);
closedir(RD_DIR);

# these are top-level MD aliases
#
printf STDERR "$main'program_name: adding Majordomo aliases\n" if $opt_v;
print <<"EOM";
#-----------------------------------------------------------------------
# BEGIN MAJORDOMO CONFIGURATION ($aliases)
# Built by $main'program_name (Rev. $MJBA_revision)
#-----------------------------------------------------------------------
#
majordomo:		"$majordomo_cmd"
majordomo-owner:	$owner$nobody
owner-majordomo:	$owner$nobody
owner-owner:		$owner$nobody
listmanager:		$owner$nobody
corp-pubs:		"$filter_cmd"
EOM

foreach (sort @lists) {
	local($list) = $_;
	$list =~ s,^.*/,,;				# strip off leading path
	$list =~ /[^-_0-9a-zA-Z]/ && next; # skip non-list files (*.info, etc.)

	# get/make config file
	&get_config($listdir, $list) if !&cf_ck_bool($list, '', 1);
	# make alias entries
	$owner = &do_list_aliases($list);
	if ($owner) {
		$owner =~ tr/A-Z/a-z/;			# match lowercase
		next if $ignore_owners{$owner};		# some we ignore
		$owner_list{$owner} .= "$list,";	# save the owner
	}
}
print <<"EOM";
#-----------------------------------------------------------------------
# END MAJORDOMO CONFIGURATION
#-----------------------------------------------------------------------
EOM
exit(0) if $opt_t;
close(STDOUT);

# Make a backup copy of the $aliases file
#
rename("$ENV{HOME}/$aliases", "$ENV{HOME}/$aliases.bak") 
	if -f "$ENV{HOME}/$aliases";
rename("$ENV{HOME}/$aliases.tmp", "$ENV{HOME}/$aliases");
&mail_diff("$ENV{HOME}/$aliases", "$ENV{HOME}/$aliases.bak") if $opt_m;
printf STDERR "$main'program_name: $nlists_total lists processed ($nlists_active active)\n"
	if $opt_v;

# open owner-list file
#
printf STDERR "$main'program_name: rebuilding $owner_list\n" if $opt_v;
open(FILE, ">$listdir/$owner_list");

foreach (keys(%owner_list)) {
	chop($owner_list{$_});
	print FILE "$_ ".($opt_k ? "($owner_list{$_})\n" : "\n");
}
close(FILE);
unlink("$listdir/.build.LOCK");

# do all additional tasks as background process
#
if (-x "$homedir/mj_background_tasks") {
	printf STDERR "$main'program_name: invoking background tasks\n" if $opt_v;
	system "nohup $homedir/wrapper mj_background_tasks" . ($opt_v ? " -v" : "") . "&";
}

#============================================================================
# MISC FUNCTIONS
#============================================================================

sub do_list_aliases {
	local($list) = shift;
	local($list_owner,$list_moderator,$supplemental);
	$list_owner = $config_opts{$list,'owner'};
	$list_moderator = $config_opts{$list,'moderator'};
	$list_moderator = $list_owner unless ($list_moderator);

	# 'owner' keyword is not implemented! default to Mj owner
	$list_owner = $list_moderator = $owner
		unless defined($config_opts{$list,'owner'});

	# check validity of owner/moderator addresses
	$addrs = "";
	foreach (split(',', $list_owner)) {
		$addrs = join(',', $addrs, &valid_addr($_));
	}
	$list_owner = $addrs; $list_owner =~ s/^,//;
	#
	$addrs = "";
	foreach (split(/,/, $list_moderator)) {
		$addrs = join(',', $addrs, &valid_addr($_));
	}
	$list_moderator = $addrs; $list_moderator =~ s/^,//;

	# Archive and Digest are stubs for work-in-progress
	if (&cf_ck_bool($list,'archive') && $opt_a) {
		local($dir,$freq);
		printf STDERR "$main'program_name: <$list> is archived\n" if $opt_v;
		$dir = $config_opts{$list,'archive_dir'};
		$dir = "$filedir/$list$filedir_suffix" unless $dir;
		unless (-d $dir) {
			printf STDERR "$main'program_name: creating archive directory\n" 
				if $opt_v;
			printf STDERR "$main'program_name: $dir mkdir failed ($!)\n" unless
				mkdir($dir, 0770);
		}
		$freq = $config_opts{$list,'archive_frequency'};
		$freq = "-d" if $freq eq "daily";
		$freq = "-m" if $freq eq "monthly";
		$freq = "-y" if $freq eq "yearly";
		$freq = "-m" unless $freq;	# default
		$supplemental .= ", \"$archive_cmd $dir/archive $freq\"";
	}
	if (&cf_ck_bool($list,'digest') && $opt_d) {
		printf STDERR "$main'program_name: <$list> is digested\n" if $opt_v;
		$supplemental .= ", \"$digest_cmd $list-digest $list-digest-$suffix\"";
	}

	$nlists_total++;
	unless ($list_owner && $list_moderator) {
		printf STDERR "$main'program_name: owner/moderator invalid or not specified for <$list>\n" if $opt_v;
		print <<"EOM";
#
# $list (UNAVAILABLE)
#
$list:		"$unavail_cmd $list"
$list-request:	"$majordomo_cmd -l $list"
EOM
		return;
	}

#---
# Discontinue the new-list feature
#---
#	unless (`wc -l <$listdir/$list` > 0) {
#		printf STDERR "$main'program_name: no members in <$list>\n" 
#		   if $opt_v;
#		print <<"EOM";
##
## $list (NEW)
##
#$list:		"$newlist_cmd $list"
#$list-request:	"$majordomo_cmd -l $list"
#$list-approval:	majordomo-owner@$whereami$nobody
#$list-owner:	majordomo-owner@$whereami$nobody
#owner-$list:	majordomo-owner@$whereami$nobody
#EOM
#		return;
#	}

	$nlists_active++;
	printf STDERR "$main'program_name: active list <$list>\n" if $opt_v;
	print <<"EOM";
#
# $list (ACTIVE)
#
$list:		"$resend_cmd $list $list-$suffix$nobody", "$log_cmd $list"
$list-request:	"$majordomo_cmd -l $list"
$list-$suffix:	:include:$listdir/$list $supplemental
$list-approval:	$list_moderator$nobody
$list-owner:	$list_owner$nobody
$list-$suffix-owner:	$list-owner@$whereami
owner-$list:	$list_owner$nobody
owner-$list-$suffix:	$list-owner@$whereami
EOM
	if ($config_opts{$list,"digest"} && $opt_d) {
		print <<"EOM";
$list-digest:	$list
$list-digest-request:	"$majordomo_cmd -l $list-digest"
$list-digest-$suffix:	:include:$listdir/$list-digest
$list-digest-approval:	$list_moderator$nobody
$list-digest-owner:	$list_owner$nobody
$list-digest-$suffix-owner:	$list-owner@$whereami
owner-$list-digest:	$list_owner$nobody
owner-$list-digest-$suffix:	$list-owner@$whereami
EOM
	}
	return $list_owner;
}

sub valid_addr {
	local($addr) = shift;
	return $addr;
	$_ = $addr;
	# all comparisons in l/c
	tr/A-Z/a-z/;
	# remove local domain names
	s/sandiegoca.ncr.com//;
	s/elsegundoca.ncr.com//;
	# remove trailing ., @
	s/\.$//; s/\@$//;

	# here will end up with:
	#	userid
	#	first.last
	#	anything@localhost
	#	anything@non-local.domain

	# skip anything non-local
	return $addr if /\./ && /\@/;

	# skip all @localhost
	return $addr if /\@/;

	local(@trace);
	@trace = `/usr/local/bin/rolo -u1 -L name $_ 2>&1` if /\./;
	@trace = `/bin/ypmatch -k $_ aliases 2>&1` if /^[_\w]+$/;
	return ($? ? "" : "$_");
}

sub mail_diff {
	local($new_file,$old_file) = @_;
	local(@newies,@oldies);
	local($desc);
	local(%MER);

	grep(($MER{$_}++   ) && 0, &get_aliases_from_file($old_file));
	grep(($MER{$_} += 2) && 0, &get_aliases_from_file($new_file));

	foreach $element (keys %MER) {
		#
		# case of $MER{$element}:
		#      1 OLD
		#      2 NEW
		#      3 No change...
		push(@oldies,$element) if $MER{$element} == 1;
		push(@newies,$element) if $MER{$element} == 2;
	}
	$desc .= join(' ',"\nDELETED or MODIFIED:\n", sort @oldies)
		if @oldies;
	$desc .= join(' ',"\nADDED or MODIFIED:\n", sort @newies)
		if @newies;
	$desc = "No changes recorded." unless $desc;

	print STDERR "$main'program_name: alias changes:\n--\n$desc\n--\n" 
		if ($opt_v);
	&set_mailer($bounce_mailer ? $bounce_mailer : $mailer);
	&set_mail_sender($whoami_owner);
	&set_mail_from($whoami_owner);
	&sendmail(MAIL, $opt_o, "Mail aliases changed by $main'program_name");
	print MAIL <<"EOF";

The following changes were made to Majordomo's alias file:

------------------------------------------------------------------------------
$desc
------------------------------------------------------------------------------

A copy of the file was saved as '$old_file'. 

EOF
	close(MAIL);
}

sub get_aliases_from_file {
	local($filename) = @_;
	local(@tmp_ref);
	if (-e $filename) {
		printf STDERR "$main'program_name: parsing $filename\n" 
			if ($opt_v);
		open(ALIASFILE, "$filename");
		@tmp_ref = <ALIASFILE>;
		@tmp_ref = grep(/:/, @tmp_ref);
		@tmp_ref = grep(!/^\#/, @tmp_ref);
		close(ALIASFILE);
	}
	return @tmp_ref;
}

#=============================================================================
# Do NOT remove these lines. They set mode in Emacs.
# perl-mode is OK for font locking.
#
#@ Local Variables: ***
#@ mode: perl ***
#@ End: ***
#=============================================================================



syntax highlighted by Code2HTML, v. 0.9.1