[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