# Expansion of the local-part of a locally delivered address is done here.
#
# We check (in order):
#
#	":include:" or "|" or "/" prefix
#	aliases file entry (mapping to a list of addresses)
#	mboxmap file entry (mapping to a host!homedir!user value)
#	$HOME/.forward file entry (mapping to a list of addresses)
#	$MAILVAR/lists directory entry (mapping to a list of addresses)
#	-request or -owner suffix (mapping to owner of mailing list file)
#	$PUNTHOST (mapping to user@$PUNTHOST)
#
# If we get all the way through, we give up.  Note that if PUNTHOST is
# defined, then the mboxmap mechanism should be used to ensure local delivery
# for local users.

provide aliases

# Define the Sendmail aliases database

if [ -f $MAILVAR/db/aliases ]; then
	[ -f $MAILVAR/db/aliases.idx ] || $MAILBIN/zmailer newaliases
	relation -limt ordered,$MAILVAR/db/aliases.dat \
		-f $MAILVAR/db/aliases.idx aliases
else
	aliases () { return 1 }
fi

# If the 'm' option was NOT specified on the aliases relation,
# presumably whatever creates new aliases will poke us (using SIGURG).

trap "db flush aliases ; log flushed aliases" 16

# If an mboxmap file exists, only users specified in the mboxmap are
# recognized, and we want to deliver to them on the host, home directory
# filesystem, and filename, specified as the value in the mboxmap DB.
# The key->value mapping looks like this:
#	<name> <$hostname:$MAILBOX:$name>
# The mboxmap file is in MAILSHARE because it is identical for all machines
# in the cluster.

if [ -f $MAILSHARE/db/mboxmap ]; then
	relation -lmt ordered -f $MAILSHARE/db/mboxmap mboxmap
	POBOX=PObox	# becomes $HOME/../PObox/$USER
else
	mboxmap () { return 1 }
	POBOX=''
fi


# Usage: routeuser (channel host user attributes)
#
# This is where local alias expansion is controlled.

routeuser (quad) {
	local user lcuser mxh al a key attr pobox didexpand


	attr="$(attributes $quad)"
	case "$(get $attr type)" in
	sender)	return (($quad)) ;;
	esac

	# get rid of the attribute tag for alias loop prevention
	a="$quad"
	mxh=$(setf $(last $a) ())
	key="$(elements $a)"
	didexpand=$(expansions "$key")

	al=()

	case "$(channel $quad)" in
	#smtp)	mxh=$(mxhosts $(host $quad).)
	#	user=$(user $quad)
	#	attr=$(attributes $quad)
	#	case $#mxh in
	#	[1-9]*)	# make the XOR list
	#		for host in $(elements $mxh)
	#		do
	#			case $#al in
	#			0)	al=$(rrouter $address $attr) ;;
	#			*)	set $(cdr $(last $al)) \
	#				    (((smtp $host "$user" $attr))) ;;
	#			esac
	#		done
	#		return $al
	#		#a=((x) (smtp '$x' "$(user $quad)" $attr))
	#		#mapcar $a $mxhosts
	#		;;
	#	esac
	#	;;
	local)	;;	# this is the normal case, below
	*)	return (($quad))
		;;
	esac

	user="$(user $quad)"

	[ $didexpand ] ||
	sift "$user" in
	:include:(.*)
		db add expansions "$key" file
		priv="$(get $attr privilege)"
		[ -f "\1" ] &&
		priv=$(getpriv $priv "\1" include) &&
		defer=''
		return $(listaddresses -e root -c 'file expansion' < "\1" |
			 maprrouter $(newattribute $attr privilege $priv) "\1")
		# this is an error...
		;;
	[|/].*	return (($quad)) ;;
	tfis

#	alias expansion
	[ $didexpand ] ||
	     a=$(aliases "$user") &&
		db add expansions "$key" alias &&
		priv=$(filepriv $MAILVAR/db/aliases $(db owner aliases)) &&
		return $(echo $a | listaddresses -e root -c 'alias expansion' |
		      maprrouter $(newattribute $attr privilege $priv) "$user")


	pobox="$(mboxmap "$user")" &&
	sift "$pobox" in
	([^:]+):([^:]+):(.+)
			if [ "x\1" != "x$hostname" ]; then
				db add expansions "$key" mboxmap
				return (((smtp "\1" "$user@\1" $attr)))
			fi
			user="\3"	# this is also good for .forward stuff
			pobox="\2/$POBOX/\3"
			break
			;;
	.+		return (((error database "$user" $attr)))
			;;
	tfis

	priv="$(get $attr privilege)"
#	only allow .forward file reading if privs allow it

	case "$(get $attr type)" in
	expandsender) a=$(homedirectory "$user")/.forward && [ -f "$a" ] &&
			return (($quad)) ;;
	esac

	[ $didexpand ] ||
	a=$(homedirectory "$user")/.forward && [ -f "$a" ] &&
		db add expansions "$key" user &&
		priv="$(getpriv $priv "$a" .forward)" &&
		return $(listaddresses -e "$user" -c "$a file expansion" < $a |
			 maprrouter $(newattribute $attr privilege $priv) "$a")

	# local user with no alias and no .forward file

	if [ "$pobox" ]; then
		db add expansions "$key" pobox
		priv=$(login2uid $user)
		return (((local - "$pobox" $(newattribute $attr privilege $priv))))
	fi

	# check in the mailing list directory
	lcuser="$(recase -l "$user")"
	a="$MAILVAR/lists/$lcuser"
	[ $didexpand ] || [ -f "$a" ] &&
	    db add expansions "$key" list &&
	      priv=$(getpriv $priv "$a" maillist) &&
	        return $(listaddresses -E "$user-owner" -e "$lcuser"-owner -c "$a file expansion" < "$a" |
		         maprrouter $(newattribute $attr privilege $priv sender "$lcuser"-owner) "$a")


	# turn *-owner and *-request into the owner of a mailing list file
	# turn *-group into members listed in /etc/groups
	case "$lcuser" in
	*-owner)
		a="$MAILVAR/lists/$(basename "$lcuser" -owner)" &&
			[ -f "$a" ] &&
			return $(rrouter $(uid2login $(filepriv "$a")) $attr)
		;;
	*-request)
		a="$MAILVAR/lists/$(basename "$lcuser" -request)" &&
			[ -f "$a" ] &&
			return $(rrouter $(uid2login $(filepriv "$a")) $attr)
		;;
	*-group)
		[ $didexpand ] ||
		  a=$(groupmembers $(basename "$lcuser" -group)) &&
		  db add expansions "$key" list &&
		  return $(echo "$a" |
			   listaddresses -e postmaster -c "$lcuser expansion" |
			   maprrouter $(newattribute $attr sender postmaster) "$lcuser")
		;;
	esac


	# oh well... give up
	if [ "x$PUNTHOST" != "x" ]; then
		return $(rrouter "$user@$PUNTHOST" $attr)
	elif [ $didexpand ]; then
		case "$(expansions "$key")" in
		user|alias)
			db add expansions "$key" other ;;
		*)	# the address just disappears... we've got it already
			#log expansion "$(expansions "$key"):"
			return () ;;
		esac
	fi
	case "$user" in
	uid#*)	return $(rrouter postmaster $attr)
		;;
	esac

	case "x$POBOX" in
	x)	db add expansions "$key" local
		return (($quad)) ;;
	esac

	return (((error nosuchuser "$user" $attr)))
}

# Usage: getpriv <present privilege> <filename> <filename type>
#
# Determine the privileges associated with addresses coming from the filename.
# The type value is one of .forward, maillist, or include.  Setting
# private='' ensures that noone can access (modulo a small window) information
# through the mailer (e.g., by sending mail to addresses taken from a
# protected file and waiting excitedly for the informative bounce messages)
# that they couldn't access otherwise.  If private='.forward maillist' then
# people stop complainig about the former behaviour...

getpriv (priv, file, type) {
	for ptype in $private
	do
		if [ "$type" = "$ptype" ]; then
			filepriv "$file"
			return $?
		fi
	done
	runas $priv filepriv "$file"
}

# Usage: newattribute <oldattribute> <key1> <value1> [ <key2> <value2> ] ...
#
# Returns a new attribute list symbol with the <keyN> <valueN>
# attributes added to the contents of the <oldattribute> list.

newattribute (oldattribute) {
	local a null value
	a="$(gensym)"
	eval "$a"="\$$oldattribute"
	while [ "$#" != 0 ];
	do
		value="$(get "$a" "$1")"
		if [ "x$value" != "x$2" ]; then
			null="$(setf "$(get "$a" "$1")" "$2")"
		fi
		shift ; shift
	done
	echo "$a"
}

# Usage: maprrouter <newattributes> <what-is-being-expanded>
#
# This function applies the rrouter function to each address read from
# stdin, passing the <newattributes> parameter.  In case of error, the
# localpart parameter is used as appropriate descriptive text.  The
# return value is the concatenation of the return values of the rrouter
# invocations.

maprrouter (attribute, localpart) {
	local shh al

	al=()
	while read address
	do
		case "$address" in
		'')	case $#al in
			0)	al=(((error expansion "$localpart"))) ;;
			*)	shh=(((error expansion "$localpart")))
				shh=$(setf $(cdr $(last $al)) $shh)
				;;
			esac
			continue
			;;
		esac

		defer=''
		case $#al in
		0)	al=$(rrouter "$address" $attribute)
			[ "$defer" ] &&
			    shh=(((hold "$defer" "$address" $attribute)))
			defer=''
			;;
		*)	shh=$(rrouter "$address" $attribute)
			[ "$defer" ] &&
			    shh=(((hold "$defer" "$address" $attribute)))
			defer=''
			shh=$(setf $(cdr $(last $al)) $shh)
			;;
		esac
	done
	return $al
}

