[Raw Msg Headers][Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

a different aliases.cf for zmailer



"aliases.cf" in zmailer 2.2 (and earlier) is flawed.

Consider this scenario.
* user "pat" is in two mailing lists "red" and "green".
* the mailing list "colors" includes both "red" and "green".
* pat has a .forward file that says "pat@elsewhere".

If you send mail to "red" or "green", pat's copy will be sent to
"elsewhere", as intended.

However, if you send mail to "colors", pat will get two copies: one
local and one sent to "elsewhere".  This is because the first time
zmailer encounters pat in the list, it looks at the .forward file; but
the second time it ignores the .forward file because of the way
aliases.cf uses the "expansions" relation.

There are other, more complex scenarios that do not work.  There's a
fundamental flaw the way "expansions" is used for duplicate address
removal.  (aliases.cf confuses two separate issues: removing duplicate
addresses, and preventing recursion when expanding lists.)

Due to this, and other problems, I've rewritten aliases.cf; included
below.  It should be a drop-in replacement for aliases.cf in zmailer
2.2 (and earlier).

This is just a prototype, but it's stable and routes things
successfully.  It still isn't exactly the right approach, but it's a
step in the right direction, and I don't think I'm going to be doing
anything major to it anytime soon.

Other notes:

* The mboxmap code isn't well tested.  We don't use mboxmap here.  I
think this code has behavior identical to stock zmailer, but I may
have made a mistake somewhere.

* Before trying to read a .forward file, it checks if the user's home
directory exists, and tosses it into the hold/SCRIPT:home queue if
not.  This is one strategy for coping with automounter/NFS outage.

* There's a "homemap" relation that routes mail based on the name of
the user's home directory.  This duplicates part of the functionality
of "mboxmap", but it's somewhat simpler.

* You can specify more than one mailing list directory.  I'm not sure
of the general utility of this (it was added to solve a special case
problem), but it was easy to do.  Adding multiple alias files would
probably be more useful, but it's a little trickier to do.

* PUNTHOST is handled more gracefully.  If you accidentally define
PUNTHOST to be the local host, then the message just falls through to
local delivery.

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  aliases.cf
# Wrapped by flee@dictionopolis on Tue Sep 29 22:50:43 1992
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'aliases.cf' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'aliases.cf'\"
else
echo shar: Extracting \"'aliases.cf'\" \(12143 characters\)
sed "s/^X//" >'aliases.cf' <<'END_OF_FILE'
X# Expansion of the local-part of a locally delivered address is done here.
X# [1992-Sep-29 flee@cs.psu.edu]
X
X# XXX "expand" is really a bad name; it's actually a combination of
X# XXX several things: name resolution, routing, etc.
X
X# XXX and why is this a separate level?  the main router does similar
X# XXX things.  can this be unified?
X
Xprovide aliases
X
X# This is the switching station for local name expansion.
X
Xrouteuser (quad) {
X	local user attr a
X	user=$(user $quad)
X	attr=$(attributes $quad)
X
X	# Leave it alone if it's not a local recipient.
X	if [ "$(get $attr type)" != recipient ]
X	|| [ "$(channel $quad)" != local ]; then
X		return (($quad))
X	fi
X
X	# Try various methods until one succeeds.
X
X	# An ":include:$file" spec.
X	a=$(expand $quad include) && return $a
X
X	# The aliases database.
X	a=$(expand $quad alias) && return $a
X
X	# Host routing, via mboxmap.
X	a=$(expand $quad mboxmap_host) && return $a
X
X	# Host routing, via homemap.
X	# Doing this is a bad idea if users want to do host routing
X	# with their .forward files.  XXX This is a deep flaw.
X	# XXX but note cluster cut.
X#	a=$(expand $quad homemap) && return $a
X
X	# User's .forward file, xor host routing via homemap.
X	# Note, listing them together makes them exclusive.
X	a=$(expand $quad forward homemap) && return $a
X
X	# File location, via mboxmap.
X	a=$(expand $quad mboxmap_file) && return $a
X
X	# Mailing lists.
X	a=$(expand $quad list) && return $a
X	a=$(expand $quad list_owner) && return $a
X
X	# Trap "uid#999" messages.
X	a=$(expand $quad uid_error_trap) && return $a
X
X	# PUNTHOST, if defined.
X	a=$(expand $quad punthost) && return $a
X
X	# If mboxmap, trap users without entries.
X	a=$(expand $quad mboxmap_trap) && return $a
X
X	# A real live local delivery.
X	a=$(expand $quad local) && return $a
X
X	# Or fall through to error.
X	return (((error err.nosuchuser "$user" $attr)))
X}
X
X
X# This is the main engine for expansion.  This will call
X# $(expand_$method $user $attr) for each method it's given, with a
X# guard against recursion, until it gets an answer.
X#
X# Note the difference between
X#	$(expand $quad method1)
X#	$(expand $quad method2)
X# and
X#	$(expand $quad method1 method2)
X# In the first case, $(method2 foo) can get called recursively while
X# expanding $(method1 foo).  In the second case, $(method2 foo) will
X# not get called recursively while expanding $(method1 foo).
X# XXX Perhaps this distinction is a little too confusing; maybe
X# XXX there's a better way of doing it.
X
Xexpand (quad) {	# method ...
X	local user attr expansion success result
X
X	user=$(user $quad)
X	attr=$(attributes $quad)
X
X	# This is the key used as a guard.
X	expansion="$@ * $user"
X
X	# A second instance of the same name disappears.
X	# XXX This is not quite the right place to do this.
X	# XXX And expansions should get split into two tables.
X	expansions "finished $expansion" && return ()
X
X	# A recursive instance of the same name gets rejected.
X	expansions "pending $expansion" && return 99
X
X	db add expansions "pending $expansion" 1
X	success=''
X	for method in "$@"; do
X		if result=$(expand_$method "$user" $attr); then
X			success=yes
X			break
X		fi
X	done
X	db remove expansions "pending $expansion"
X
X	[ $success ] || return 99
X
X	db add expansions "finished $expansion" 1
X	return $result
X}
X
X
X##### An ":include:$file" spec.
X
Xexpand_include (user, attr) {
X	local priv a
X
X	sift "$user" in
X	:include:(.*)
X		file="\1"
X		if [ ! -f "$file" ]; then
X			return (((error err.nosuchuser "$user" $attr)))
X		fi
X
X		priv=$(get $attr privilege)
X		priv=$(getpriv $priv "$file" include) || return 99
X		attr=$(newattribute $attr privilege $priv)
X
X		return $(listaddresses <"$file" -e root -c "include $user" |
X			 maprrouter $attr "$user")
X		;;
X	tfis
X	return 99
X}
X
X
X##### The traditional aliases database.
X
XALIASES=$MAILVAR/db/aliases
X
Xif [ -f $ALIASES ]; then
X	[ -f $ALIASES.dat ] || $MAILBIN/zmailer newaliases
X	relation -limt ordered,$ALIASES.dat -f $ALIASES.idx aliases
Xelse
X	aliases () { return 99 }
Xfi
X
X# If the 'm' option was NOT specified on the aliases relation,
X# presumably whatever creates new aliases will poke us (using SIGURG).
X
Xtrap "db flush aliases ; log flushed aliases" 16
X
Xexpand_alias (user, attr) {
X	local priv a
X
X	a=$(aliases "$user") || return 99
X
X	priv=$(filepriv $ALIASES $(db owner aliases))
X	attr=$(newattribute $attr privilege $priv)
X
X	return $(echo "$a" |
X		 listaddresses -e root -c "alias $user" |
X		 maprrouter $attr "$a")
X}
X
X
X##### The homemap database.
X
X# This database looks like:
X#	/home/directory/prefix	$mailserver
X
X# This method removes the last component from the user's home
X# directory and looks it up in the homemap.  (Eg, if a user's home is
X# /nfs/red/sam, then /nfs/red is the key.)  If the lookup succeeds,
X# the mail gets routed to user@$mailserver.
X
XHOMEMAP=$MAILVAR/db/homemap
X
Xif [ -f $HOMEMAP ]; then
X	relation -m -t ordered -f $HOMEMAP homemap
X	use_homemap='yes'
Xelse
X	homemap () { return 99 }
X	use_homemap=''
Xfi
X
Xexpand_homemap (user, attr) {
X	local home a
X
X	[ $use_homemap ] || return 99
X
X	home=$(homedirectory "$user") || return 99
X
X	a=$(/bin/expr "$home" : "\(/.*\)//*[^/]*/*") || return 99
X	a=$(homemap "$a") || return 99
X
X	return $(rrouter "$user@$a" $attr)
X}
X
X
X
X##### .forward files.
X
X# If the user's home directory doesn't exist (perhaps due to
X# automounter/NFS outage), then we defer routing, tossing the message
X# in the hold/SCRIPT:home queue.  This is so we can reliably read
X# .forward files.  If the home directory never exists, then the
X# message will eventually get bounced by the scheduler.
X
X# XXX (cluster cut)  If you deliver mail to a user's home directory (or
X# XXX the same file system), then a forward file that names a
X# XXX different machine in the same cluster should not get forwarded,
X# XXX and a mechanism like homemap should take over.  This looks like
X# XXX it will be a little tricky to do right.
X
Xexpand_forward (user, attr) {
X	local home priv a
X
X	home=$(homedirectory "$user") || return 99
X
X	# Hold the message?
X	if [ ! -d "$home" ]; then
X		return (((hold "SCRIPT:home/$user" "$user" $attr)))
X	fi
X
X	a="$home/.forward"
X	[ -f "$a" ] || return 99
X
X	priv=$(get $attr privilege)
X	priv=$(getpriv $priv "$a" .forward) || return 99
X	attr=$(newattribute $attr privilege $priv)
X
X	return $(listaddresses <"$a" -e "$user" -c "forward $user" |
X		 maprrouter $attr "$a")
X}
X
X
X##### Mailing lists.
X
X# A mailing list X is a file named X within the $MAILLISTS directory
X# (default: $MAILVAR/lists) that contains a list of addresses.
X# $MAILLISTS can be a space-separated list of directories, in which
X# case a mailing list is the union of all files named X in all the
X# directories.  The owner of the files are considered the owners of
X# the mailing list.
X
XMAILLISTS=${MAILLISTS:-$MAILVAR/lists}
X
Xexpand_list (user, attr) {
X	local addrs file priv a zzz
X
X	attr=$(newattribute $attr sender "$user"-owner)
X	addrs=()
X	for dir in $MAILLISTS; do
X		file=$dir/$(recase -l "$user")
X		[ -f "$file" ] || continue
X
X		priv=$(getpriv $priv "$file" maillist) || continue
X		attr=$(newattribute $attr privilege $priv)
X
X		a=$(listaddresses <"$file" -e "$user"-owner -c "list $user" |
X			maprrouter $attr "$file")
X
X		# append $a to $addrs
X		case $#addrs in
X		0)	addrs=$a;;
X		*)	zzz=$(setf $(cdr $(last $addrs)) $a);;
X		esac
X	done
X
X	case $#addrs in
X	0)	return 99;;
X	*)	return $addrs;;
X	esac
X}
X
X# This redirects things like "*-request" to the mailing list owners.
X
Xexpand_list_owner (user, attr) {
X	local addrs list file a zzz
X
X	case "$user" in
X	*-request)	list=$(basename "$user" -request);;
X	*-owner)	list=$(basename "$user" -owner);;
X	owner-*)	list=$(/bin/expr "$user" : "owner-\(.*\)");;
X	*)		return 99;;
X	esac
X
X	addrs=()
X	for dir in $MAILLISTS; do
X		file=$dir/$(recase -l "$list")
X		[ -f "$file" ] || continue
X
X		a=$(rrouter $(uid2login $(filepriv "$file")) $attr)
X
X		# append $a to $addrs
X		case $#addrs in
X		0)	addrs=$a;;
X		*)	zzz=$(setf $(cdr $(last $addrs)) $a);;
X		esac
X	done
X
X	case $#addrs in
X	0)	return 99;;
X	*)	return $addrs;;
X	esac
X}
X
X
X##### The mboxmap database.
X
X# This database looks like:
X#	$user	$host:$prefix:$suffix
X
X# In the end, a user's mail gets delivered to $host and dropped in the
X# file $prefix/PObox/$suffix.  This involves three different methods:
X
X# 0. mboxmap_host reroutes $user to $user@$host.
X# 1. mboxmap_file delivers mail to the file $prefix/PObox/$suffix.
X# 2. mboxmap_trap rejects the mail if $user isn't in the database.
X
X# The idea is that each user's mailbox is on the same fileserver as
X# their home directory, and writing the mailbox is the responsibility
X# of that fileserver, thus avoiding a number of awkward NFS issues.
X
X# XXX However, mboxmap is an awkward concept.  There's a better way of
X# XXX doing this.
X
XMBOXMAP=$MAILSHARE/db/mboxmap
X
Xif [ -f $MBOXMAP ]; then
X	POBOX=PObox
X	relation -lmt ordered -f $MBOXMAP mboxmap
Xelse
X	POBOX=''
X	mboxmap () { return 99 }
Xfi
X
Xexpand_mboxmap_host (user, attr) {
X	local pobox
X
X	pobox=$(mboxmap "$user") || return 99
X
X	sift "$pobox" in
X	([^:]+):([^:]+):(.+)
X		return $(rrouter "$user@\1" $attr);;
X	.+
X		return (((error database "$user" $attr)));;
X	tfis
X}
X
Xexpand_mboxmap_file (user, attr) {
X	local pobox priv
X
X	pobox=$(mboxmap "$user") || return 99
X
X	sift "$pobox" in
X	([^:]+):([^:]+):(.+)
X		priv=$(login2uid "$user")
X		attr=$(newattribute $attr privilege $priv)
X		return (((local - "\2/$POBOX/\3" $attr)));;
X	.+
X		return (((error database "$user" $attr)));;
X	tfis
X}
X
Xexpand_mboxmap_trap (user, attr) {
X	case "$POBOX" in
X	'')	return 99;;
X	*)	return (((error err.nosuchuser "$user" $attr)));;
X	esac
X}
X
X
X##### Redirect "uid#*" to postmaster.
X
Xexpand_uid_error_trap (user, attr) {
X	case "$user" in
X	uid#*)	return $(rrouter postmaster $attr);;
X	esac
X	return 99
X}
X
X
X##### Real live local users get punted to $PUNTHOST.
X
Xexpand_punthost (user, attr) {
X	if [ "$PUNTHOST" ]; then
X		return $(rrouter "$user"@$PUNTHOST $attr)
X	fi
X	return 99
X}
X
X
X##### A really real live local user.
X
Xexpand_local (user, attr) {
X	return (((local - "$user" $attr)))
X}
X
X
X##### Code below copied verbatim from aliases.cf in zmailer 2.2 .
X
X# Usage: getpriv <present privilege> <filename> <filename type>
X#
X# Determine the privileges associated with addresses coming from the filename.
X# The type value is one of .forward, maillist, or include.  Setting
X# private='' ensures that noone can access (modulo a small window) information
X# through the mailer (e.g., by sending mail to addresses taken from a
X# protected file and waiting excitedly for the informative bounce messages)
X# that they couldn't access otherwise.  If private='.forward maillist' then
X# people stop complainig about the former behaviour...
X
Xgetpriv (priv, file, type) {
X	for ptype in $private
X	do
X		if [ $type = $ptype ]; then
X			filepriv "$file"
X			return $?
X		fi
X	done
X	runas $priv filepriv "$file"
X}
X
X# Usage: newattribute <oldattribute> <key1> <value1> [ <key2> <value2> ] ...
X#
X# Returns a new attribute list symbol with the <keyN> <valueN>
X# attributes added to the contents of the <oldattribute> list.
X
Xnewattribute (oldattribute) {
X	local a null value
X	a=$(gensym)
X	eval $a=\$$oldattribute
X	while [ "$#" != 0 ]
X	do
X		value=$(get $a "$1")
X		if [ x"$value" != x"$2" ]; then
X			null=$(setf $(get $a "$1") "$2")
X		fi
X		shift ; shift
X	done
X	echo $a
X}
X
X# Usage: maprrouter <newattributes> <what-is-being-expanded>
X#
X# This function applies the rrouter function to each address read from
X# stdin, passing the <newattributes> parameter.  In case of error, the
X# localpart parameter is used as appropriate descriptive text.  The
X# return value is the concatenation of the return values of the rrouter
X# invocations.
X
Xmaprrouter (attribute, localpart) {
X	local shh al
X
X	al=()
X	while read address
X	do
X		case "$address" in
X		'')	case $#al in
X			0)	al=(((error expansion "$localpart"))) ;;
X			*)	shh=(((error expansion "$localpart")))
X				shh=$(setf $(cdr $(last $al)) $shh)
X				;;
X			esac
X			continue
X			;;
X		esac
X
X		defer=''
X		case $#al in
X		0)	al=$(rrouter "$address" $attribute)
X			[ "$defer" ] &&
X			    shh=(((hold "$defer" "$address" $attribute)))
X			;;
X		*)	shh=$(rrouter "$address" $attribute)
X			[ "$defer" ] &&
X			    shh=(((hold "$defer" "$address" $attribute)))
X			shh=$(setf $(cdr $(last $al)) $shh)
X			;;
X		esac
X	done
X	return $al
X}
END_OF_FILE
if test 12143 -ne `wc -c <'aliases.cf'`; then
    echo shar: \"'aliases.cf'\" unpacked with wrong size!
fi
# end of 'aliases.cf'
fi
echo shar: End of shell archive.
exit 0