# Expansion of the local-part of a locally delivered address is done here.
#
# We check (in order):
#
#	$FORCEPUNT -- if defined, route to: user@$FORCEPUNT
#	":include:" or "|" or "/" prefix
#	aliases file entry (mapping to a list of addresses)
#	mboxmap file entry (mapping to a host!homedir!user value)
#	$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)
#	$forward  file entry (mapping to a list of addresses)
#
# 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.

#
# FIXME: The  $(zapDSNnotify $varnamevar)  calls are returning 1 in case
#        there is DSN data with NOTIFY=SUCCESS value.  In such cases we
#        *really* should produce a Notification Message with 'list expand'
#        or 'alias expand' as the delivery status.   NOT any of the final
#	 destinations possibly thru lots of further diversions.
#
#	 Right now all DSN data is just scrubbed away, although probably
#	 some semi-strange hybride of redirected ORCPT data would be in
#	 order -- at least.  (With redirected sender address.)
#
#	/* Now depending are we handling a mailing-list expansion, or
#	   an alias expansion (RFC 1891, 6.2.7.*) we either report
#	   "delivered" to the list input address, OR "expanded"
#	   for aliases and .forwards (single recipient ones should
#	   *not* report anything but rewrite the recipient address,
#	   but we slip at that..) */
#
# (and same for  fqdnalias.cf  file)
#

provide aliases

#
# Define the 'Sendmail-like' aliases database
# 

if [ -f $MAILVAR/db/aliases.zmsh ]; then

    # If that file exists, it contains scripts defining our databases
    # at load-in time (relation entries), and also runtime callable
    # entrypoint  aliases(uname) { ... }  which "return 1" in case no
    # lookup succeeds.  It yields the first lookup result that succeeded.
    # Also, successfull lookup must yield file privileges of the database
    # file where the lookup was done.  The return is done to a "priv"
    # variable which has local scope in the caller's symbol stack.

    . $MAILVAR/db/aliases.zmsh

else
    if [ -f $MAILVAR/db/aliases ]; then
	# Need to be updated at boot ?
	if [ ! -f $MAILVAR/db/aliases$DBEXTtest -o \
	     $MAILVAR/db/aliases -nt $MAILVAR/db/aliases$DBEXTtest ]; then
		# Yes, so update!
		$MAILBIN/newdb -l -a $MAILVAR/db/aliases
	fi
# ---- raw relation entry
	relation -lmt $DBTYPE -f $MAILVAR/db/aliases$DBEXT aliasesdb
# ---- interface function
	aliases(key) {
		local a
		if a="$(aliasesdb "$key")"; then
		  priv="$(filepriv -M 644 $MAILVAR/db/aliases$DBEXTtest \
					$(db owner aliasesdb))" &&
		  returns "$a"
		fi
		return 1
	}
    else
	aliases () { return 1 }
    fi
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 aliasesdb ; log flushed aliases" 16

#
# If FULLNAMEMAP definition can be found..
#
if [ -f $MAILVAR/db/fullnamemap.zmsh ]; then
	. $MAILVAR/db/fullnamemap.zmsh
fi

#
# Safeguard if fullnamemapdb() is not yet created (as part of aliases()?)
#
case "$(type fullnamemapdb)" in
*"not found")


    # optional: Fullname database: <Full_Name>    <login>

    if [ -f $MAILVAR/db/fullnames$DBEXTtest  ]; then
	relation -lmt $DBTYPE -f $MAILVAR/db/fullnames$DBEXT fullnamemapdb
	fullnamemap (key) {
	    local a;
	    if a="$(fullnamemapdb "$key")" ; then
		# "priv" analysis ???
		returns "$a"
	    fi
	    return 1
	}
    else
	fullnamemap () { return 1 }
    fi

    ;;

*)  # Can be found!
    case "$(type fullnamemap)" in
    *"not found")
	# The DB is there, but not this function, probably as a part of
	# aliases() database call entry...
	fullnamemap() { return 1 }
	;;
    esac
    ;;
esac

#
# Hooks to define USERDB mapping
#

if [ -f $MAILVAR/db/userdb.zmsh ]; then
   . $MAILVAR/db/userdb.zmsh
else
    if [ -f $MAILVAR/db/userdb$DBEXTtest ]; then
	relation -lmt $DBTYPE -f $MAILVAR/db/userdb$DBEXT userdb
    else
	userdb () { return 1 }
    fi
fi

#
# Safeguard if userdb() is not yet created (as part of aliases()?)
#
case "$(type userdb)" in
*"not found")
	userdb () { return 1 }
    ;;
esac



# 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 $MAILVAR/db/mboxmap.zmsh ]; then
	. $MAILVAR/db/mboxmap.zmsh
	POBOX=PObox	# becomes $HOME/../PObox/$USER
else
    if [ -f $MAILSHARE/db/mboxmap$DBEXTtest ]; then
	relation -lmt $DBTYPE -f $MAILSHARE/db/mboxmap$DBEXT mboxmap
	POBOX=PObox	# becomes $HOME/../PObox/$USER
    else
	mboxmap () { return 1 }
	POBOX=''
    fi
fi

#
# Map containing accounts that have expired, we return
# a special error report on them.
#
if [ -f $MAILVAR/db/expired.zmsh ]; then
	. $MAILVAR/db/expired.zmsh
else
    if [ -f $MAILVAR/db/expiredaccts$DBEXTtest ]; then
       relation -lmt $DBTYPE -f $MAILVAR/db/expiredaccts$DBEXT expired
    else
       expired () { return 1 }
    fi
fi

#
# Usage: $(didexpand "subkey")
# Uses caller varspace variable "key"
# Returns:
#	0 (true)  when no expansions detected
#	1 (false) and reoccurring expansion call is seen
#

didexpand (subkey) {
	local a
	if a="$(expansions "$key.$subkey")"; then
		return 1
	else
		return 0
	fi
}


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

routeuser (quad, plustail, domain) {
	local chan user host lcuser mxh al a l
	local key attr pobox nattr homedir sender
	local plustail2 forward priv hashomedir type
	local sysuser

	plustail2=""

	attr=$(attributes $quad)
	chan=$(channel $quad)
	host=$(host $quad)
	user=$(user $quad)

	if [ "$DOMAIN_AWARE_GETPWNAM" = "1" ] ; then
		sysuser="$host"		# for local chan this is user@domain
	else
		sysuser="$user"
	fi

	# For the expansions control tag we can use only
	# the 'channel',  and the 'username' strings, we CAN'T
	# use 'attributes' element, because .forward recursion
	# will have subsequently DIFFERENT values for it.
	# Use of 'host' field means we can't use 

	key="$user"

	type="$(get $attr type)"
	sender="$(get $attr sender)"

	case $type in
	sender)
		a=$(userdb "$user:mailname") \
		  || returns ((("$chan" "$host" "$user$plustail$domain" $attr)))
		returns ((("$chan" "$a" "$user$plustail$domain" $attr)))
		;;
	esac

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

	al=()

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

	user=$(condquote -- $user)
	host=$(condquote -- $host)

# echo "routeuser: host=$host, user=$user" >> /dev/tty

	[ -n "$FORCEPUNT" ] &&
		returns $(rrouter "$user@$FORCEPUNT" "$host" $attr "" "")

	a="$(dequote -- "$user")"

	ssift "$a" in
	\\(.*)	user="\1" # Back-quoted username -- most likely
		#didexpand=local
		;;
	':include:[\ \t]*(.+)[\ \t]*'
		# Weird format to do explicite white-space stripping...
		priv=$(get $attr privilege)
		if $(didexpand ':include:') ; then
		  if [ -f "\1" ] &&
		     priv=$(getpriv "644" $priv "\1" include) ; then
		    # sendmail-like :include: expansion
		    db add expansions "$key.:include:" 1
		    nattr=$(newattribute $attr privilege $priv)
		    $(zapDSNnotify $nattr expanded "$sender" "$user$domain")
		    defer=''
		    if a=$(runas $priv cat "\1" | \
			   listexpand -E "postmaster" -e root -p $priv	\
					-c ":include:\1 file expansion"	\
					"$nattr" "\1" "$host" "" "$domain" )
		    then
		      postzapDSNnotify a
		      returns $a
		    fi
		  fi
		else
		  returns (((bitbucket "duplicate-$key-:include:" "$user" $attr)))
		fi
		;;

#	# This following requires ZM 2.99.46+ shell language!
#	# The single/double quotes are paired, and ignored while
#	# scanning the selector pattern.  If a quote is needed,
#	# try following mapping:
#	# left is input, right is result:  '"pat'"'" -> "pat'
#	# That is, to produce a double quote, code: '"',
#	# and for single quote, code: "'"
#	# Here we have SPACE and TAB in this pattern!
#	'[^"]*[ 	][^"]*'
#		# If the $user had no quotes on it, we add them here!
#		if [ "z$(dequote -- "$user")" = "z$user" ]; then
#			user="\"$user\""
#		fi
#		;;
	[|/].*	returns (($quad))
		;;
	tfiss

#	alias expansion

	priv=$nobodypriv
	if $(didexpand aliases) ; then
	  if a="$(aliases "$user")" ; then
		db add expansions "$key.aliases" 1
		nattr=$(newattribute $attr privilege $priv)
		$(zapDSNnotify $nattr expanded "$sender" "$user$domain")

		if a=$(echo -- "$a" |					\
		       listexpand	-E "root" -e "root" -N -	\
					-c "$user file expansion"	\
					$nattr "$user" "$host"		\
					"$plustail" "$domain")
		then
		  postzapDSNnotify a
		  returns $a
		fi
	  fi
	else
	  returns (((bitbucket "duplicate-$key-aliases" "$user" $attr)))
	fi

# ---- from rrouter.cf:

	priv=$nobodypriv
	if $(didexpand fullnamemap) ; then
	  if a="$(fullnamemap "$user")" ; then
		db add expansions "$key.fullnamemap" 1
		nattr=$(newattribute $attr privilege $priv)
		$(zapDSNnotify $nattr expanded "$sender" "$user$domain")

		a=$(rrouter "$a" "$user" $nattr "$plustail" "$domain")
		postzapDSNnotify a
		returns $a
	  fi
	else
	  returns (((bitbucket "duplicate-$key-fullnamemap" "$user" $attr)))
	fi
	if [ "$do_newsgroup" ] && [ -n "$(newsgroup "$user")" ]; then
		# Retain possible DSN attributes
		returns (((usenet - "$user" $attr)))
	fi

# ---- end of "from rrouter.cf":

	# Listed in 'expired accounts' database ?
	if [ -z "$PUNTHOST" ] && a=$(expired "$user") ; then
		returns (((error acctexpired "$user" $attr)))
	fi

	if $(didexpand mboxmap) ; then
	  if pobox="$(mboxmap "$user")"; then
		tsift "$pobox" in
		([^:]+):([^:]+):(.+)
			if [ "z\1" != "z$hostname" ]; then
				returns (((smtp "\1" "$user@\1" $attr)))
			fi
			user="\3"	# this is also good for .forward stuff
			pobox="\2/$POBOX/\3"
			break
			;;
		.+
			returns (((error database "$user" $attr)))
			;;
		tfist
	  fi
	else
	  returns (((bitbucket "duplicate-$key-mboxmap" "$user" $attr)))
	fi

	priv=$(get $attr privilege)

	# check in the mailing list directory
	lcuser="$(recase -l -- "$user")"

	l="$preowner$lcuser$postowner$domain"

	a="$MAILVAR/lists/$lcuser"
	nattr=()
	#l="$preowner$lcuser$postowner$domain"
	if $(didexpand lists) ; then
	  if [ -f "$a" ] ; then
	    db add expansions "$key.lists" 1
	    if priv=$(getpriv "664" $priv "$a" maillist) &&
	       nattr=$(newattribute $attr privilege $priv sender "$l") ; then
		$(zapDSNnotify $nattr delivered "$sender" "$lcuser$domain")
		if a=$(runas $priv cat "$a" | \
		       listexpand	-E "$l" -e "$l" -p $priv	\
					-c "$a file expansion"		\
					$nattr "$a" "$host" "" "$domain" )
		then
		  postzapDSNnotify a
		  returns $a
		fi
	    fi
	  fi
	else
	  returns (((bitbucket "duplicate-$key-lists" "$user" $attr)))
	fi

	a="$MAILVAR/modlists/$lcuser"
# /bin/ls -lL "$a"
	if $(didexpand modlists) ; then
	  if [ -f "$a" ] ; then
	    db add expansions "$key.modlists" 1
	    priv=$(getpriv "664" $priv "$a" maillist)
	    case "$lcuser" in
		*-mod)
			l="$preowner$(basename "$lcuser" -mod)$postowner"
			nattr=$(newattribute $attr privilege $priv sender "$l")
			$(zapDSNnotify $nattr delivered "$sender" "$lcuser$domain")

			# echo -- "$l exists!" >> /dev/tty
			if  a=$(runas $priv cat "$a" | \
				listexpand -E "$l$domain" -e "$l"	\
					-c "$a file expansion"		\
				     $nattr "$a" "$host" "$plustail" "$domain")
			then
			  postzapDSNnotify a
			  returns $a
			fi
			;;
	    esac
	  fi
	else
	  returns (((bitbucket "duplicate-$key-modlists" "$user" $attr)))
	fi

	# turn *-owner and *-request into the owner of a mailing list file
	# turn *-group into members listed in /etc/groups

	ssift "$lcuser" in
	(.+)-owner
		a="$MAILVAR/lists/\1"
		if  $(didexpand listowner) ; then
		  if [ -f "$a" ] ; then
		    db add expansions "$key.listowner" 1
		    priv=$(filepriv -M 664 "$a")
		    nattr=$(newattribute $attr privilege $priv)
		    $(zapDSNnotify $nattr expanded "$sender" "$lcuser$domain")
		    a=$(rrouter "$(uid2login $priv)" 			\
				"$host" $nattr "$plustail" "$domain")
		    postzapDSNnotify a
		    returns $a
		  fi
		else
		  returns (((bitbucket "duplicate-$key-lists-owner" "$user" $attr)))
		fi
		break
		;;
	owner-(.+)
		a="$MAILVAR/lists/\1"
		if  [ -f "$a" ] && $(didexpand ownerlist) ; then
		    db add expansions "$key.ownerlist" 1
		    priv=$(filepriv -M 664 "$a")
		    nattr=$(newattribute $attr privilege $priv)
		    $(zapDSNnotify $nattr expanded "$sender" "$lcuser$domain")
		    a=$(rrouter "$(uid2login $priv)" \
				 "$host" $nattr "$plustail" "$domain")
		    postzapDSNnotify a
		    returns $a
		fi
		break
		;;
	(.+)-request
		a="$MAILVAR/lists/\1"
		if [ -f "$a" ] && $(didexpand listrequest) ; then
		    db add expansions "$key.listrequest" 1
		    priv=$(filepriv -M 664 "$a")
		    nattr=$(newattribute $attr privilege $priv)
		    $(zapDSNnotify $nattr expanded "$sender" "$lcuser$domain")
		    a=$(rrouter "$(uid2login $priv)" \
				 "$host" $nattr "$plustail" "$domain")
		    postzapDSNnotify a
		    returns $a
		fi
		break
		;;
	(.*)-group
		if $(didexpand unixgroup) && a="$(groupmembers "\1")" ; then
		    db add expansions "$key.unixgroup" 1
		    nattr=$(newattribute $attr privilege $nobodypriv )
		    $(zapDSNnotify $nattr expanded "$sender" "$lcuser$domain")
		    if a=$(echo -- "$a" |
			   listexpand -e postmaster -N -		\
				-c "$lcuser expansion" $nattr $lcuser	\
				"$host" "$plustail" "$domain")
		    then
		      postzapDSNnotify a
		      returns $a
		    fi
		fi
		;;
	tfiss


# Define something like following in your  zmailer.conf  file to enable this !
#	LISTSERV="/v/net/listserv.funet.fi"

if [ -n "$LISTSERV" ] ; then 

# listserv automatic list alias mapping support, the listserv home
# probably should go to zmailer.conf or elsewhere at some time
# but let's keep it simple during testing <hks@nic.funet.fi> 1998
# We check first zmailer lists that can override listserv in an
# emergency, for example.
#

	a="$LISTSERV/home/$lcuser.list"
  
	if [ -f "$a" ] ; then
		nattr=$(newattribute $attr privilege "0")
		$(zapDSNnotify $nattr delivered "$sender" "$lcuser$domain")
		a=(((local pipe.$lcuser "|$LISTSERV/bin/lsv_amin $LISTSERV/lsvspool $lcuser" $nattr)))
		postzapDSNnotify a +
		returns $a
	fi

	ssift "$lcuser" in

	owner-(.+)
		listbase="\1"
		a="$LISTSERV/home/$listbase.list"
		if [ -f "$a" ]; then
		   nattr=$(newattribute $attr privilege "0")
		   $(zapDSNnotify $nattr)
		   a=(((local pipe.owner-$listbase "|$LISTSERV/bin/lsv_amin $LISTSERV/lsvspool owner-$listbase" $nattr)))
		   postzapDSNnotify a
		   returns $a
		fi
		;;

	(.+)-owner
		listbase="\1"
		a="$LISTSERV/home/$listbase.list"
		if [ -f "$a" ]; then
		   nattr=$(newattribute $attr privilege "0")
		   $(zapDSNnotify $nattr)
		   a=(((local pipe.$listbase-owner "|$LISTSERV/bin/lsv_amin $LISTSERV/lsvspool owner-$listbase" $nattr)))
		   postzapDSNnotify a
		   returns $a
		fi
		;;

	(.+)-search-request
		listbase="\1"
#listsserv search facility support <hks>
		a="$LISTSERV/home/$listbase.list"
		if [ -f "$a" ]; then
		   nattr=$(newattribute $attr privilege "0")
		   $(zapDSNnotify $nattr)
		   a=(((local pipe.$listbase-search-request "|$LISTSERV/bin/lsv_amin $LISTSERV/lsvspool $listbase-search-request" $nattr)))
		   postzapDSNnotify a
		   returns $a
		fi
		;;

	(.+)-request
		listbase="\1"
		a="$LISTSERV/home/$listbase.list"
		if [ -f "$a" ] ; then
		   nattr=$(newattribute $attr privilege "0")
		   $(zapDSNnotify $nattr)
		   a=(((local pipe.$listbase-request "|$LISTSERV/bin/lsv_amin $LISTSERV/lsvspool $listbase-request" $nattr)))
		   postzapDSNnotify a
		   returns $a
		fi
		;;

	(.+)-server
		listbase="\1"
#listsserv search facility support <hks>
		a="$LISTSERV/home/$listbase.list"
		if [ -f "$a" ]; then
		   nattr=$(newattribute $attr privilege "0")
		   $(zapDSNnotify $nattr)
		   a=(((local pipe.$listbase-server "|$LISTSERV/bin/lsv_amin $LISTSERV/lsvspool $listbase-server" $nattr)))
		   postzapDSNnotify a
		   returns $a
		fi
		;;
	(.+)
		listbase="\1"
		a="$LISTSERV/home/$listbase.list"
		if [ -f "$a" ] ; then
		   nattr=$(newattribute $attr privilege "0")
		   $(zapDSNnotify $nattr delivered "$sender" "$lcuser$domain")
		   a=(((local pipe.$listbase "|$LISTSERV/bin/lsv_amin $LISTSERV/lsvspool $listbase" $nattr)))
		   postzapDSNnotify a 0
		   returns $a
		fi
		break
		;;
	tfiss
fi

#	only allow .forward file reading if privs allow it

	hashomedir=
	if homedir="$(homedirectory "$sysuser")" ; then
		hashomedir=1
	else
	    [ -n "$defer" ] &&
		    returns (((hold "$defer" "$address" $attr)))

	    ssift "$user" in # Just in case:  user--.*
	    (.+)--(.*)
		user="\1+\2"
		;;
	    tfiss

	    ssift "$user" in # No 'homedir' for this user ? Has a '+' in it ?
	    ([^+]+)\+.+
		# Try expanding 'user+', and then plain 'user'
		key="\1+"
		if $(didexpand 'aliases+') && a="$(aliases "\1+")"; then
		  db add expansions "$key.aliases+" 1
		  nattr=$(newattribute $attr privilege $priv)
		  $(zapDSNnotify $nattr expanded "$sender" "$lcuser$domain")

		  a=$(echo -- "$a" |					\
		      listexpand	-E "root" -e "root" -N -	\
					-c "$user alias expansion"	\
				$nattr "$user" "$host" "$plustail" "$domain")
		  postzapDSNnotify a
		  returns $a

		fi
		# For a "user+" there is no homedirectory, try plusless below
		;;
	    ([^+]+)\+(.*)
		# It was 'user+', try now plain 'user' for
		# aliases, and .forward
		key="\1"
		plustail2="+\2"

		if $(didexpand aliases+-) && a="$(aliases "\1")"; then
		  db add expansions "$key.aliases+-" 1
		  nattr=$(newattribute $attr privilege $priv)
		  $(zapDSNnotify $nattr expanded "$sender" "$lcuser$domain")

		  a=$(echo -- "$a" |				\
		      listexpand -E "root" -e "root" -N -	\
				-c "$user alias expansion"	\
				$nattr "$user" "$host" "$plustail2" "$domain")
		  postzapDSNnotify a
		  returns $a

		fi

		if $(didexpand 'fullnamemap+-') && a="$(fullnamemap "\1")";then
		  db add expansions "$key.fullnamemap+-" 1
		  nattr=$(newattribute $attr privilege $priv)
		  $(zapDSNnotify $nattr expanded "$sender" "$lcuser$domain")

		  a=$(rrouter "$a" "$user" $nattr "$plustail2" "$domain")
		  postzapDSNnotify a
		  returns $a
		fi

		pobox="$(mboxmap "\1")" &&
		  tsift "$pobox" in
		  ([^:]+):([^:]+):(.+)
			if [ "z\1" != "z$hostname" ]; then
				returns (((smtp "\1" "$user@\1" $attr)))
			fi
			plustail="$plustail2"
			user="\3"	# this is also good for .forward stuff
			pobox="\2/$POBOX/\3"
			break
			;;
		  .+	returns (((error database "$user" $attr)))
			;;
		  tfist

		if homedir="$(homedirectory "\1")"; then
			hashomedir=1
		fi
		[ -n "$defer" ] &&
		    returns (((hold "$defer" "$address" $attr)))
		;;
	    tfiss
	fi

##	if [ -z "$ROUTEUSER_IN_ABNORMAL_UNIX" ] && [ "$hashomedir" ]; then
	    forward="$homedir/.forward"
##	else
##	    # Clear the possible 'homedir' flag - don't want it here!
##	    hashomedir=
##	fi

	case "$type" in
	expandsender)	[ -f "$forward" ] && returns (($quad))
			;;
	esac

	priv=$(get $attr privilege)

	if [ -z "$PUNTHOST" ] && $(didexpand forward) && [ "$hashomedir" ] &&
	   [ -f "$forward" ] ; then
	    db add expansions "$key.forward" 1
	    if priv=$(getpriv "644" $priv "$forward" .forward) ; then
		nattr=$(newattribute $attr privilege $priv)
		$(zapDSNnotify $nattr expanded "$sender" "$lcuser$domain")
		a=$(runas $priv cat "$forward" | \
		    listexpand	-e "$user" -N -				\
				-c "$forward .forward expansion"	\
				$nattr "$forward" "$host" "$plustail"	\
				"$domain")
		postzapDSNnotify a
		returns $a
	    fi
	fi

	# local user with no alias and no .forward file

	if [ -n "$pobox" ] ; then
		priv=$(login2uid "$sysuser")
		returns (((local "pob:$user" "$pobox$plustail$domain" $(newattribute $attr privilege $priv))))
	fi

	# oh well... give up, and sent to the PUNTHOST
	if [ -n "$PUNTHOST" ]; then
		returns $(rrouter "$user$plustail"@$PUNTHOST "$host" $attr "" "$domain")
	fi

	case "$user" in
	uid#*)	returns $(rrouter postmaster "$host" $attr "" "$domain")
		;;
	esac

#
#  Problem below is that '$(homedirectory )' function can't quite
#  be overridden in virtual-ISP mode, where "/etc/passwd" isn't
#  the real account database...
#


	if [ -z "$ROUTEUSER_IN_ABNORMAL_UNIX" ] ; then

# Ending case: If not POBOX, nor homedirectory defined, then
#              fall to "error" case below.

		case "${hashomedir}x$POBOX" in
		1x)	db add expansions "$key" local
			if [ -z "$localdoesdomain" ]; then
				domain=""
			fi
			quad=($chan "$host" "$user$plustail$domain" $attr)
			returns (($quad))
			;;
		esac

	else

# Ending case: If not POBOX, then fall to "error" case below.

		case "x$POBOX" in
		x)	db add expansions "$key" local
			if [ -z "$localdoesdomain" ]; then
				domain=""
			fi
			quad=($chan "$host" "$user$plustail$domain" $attr)
			returns (($quad))
			;;
		esac
	fi

	returns (((error nosuchuser "$user$plustail$domain" $attr)))
}

# Usage: getpriv <maxperm> <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 (maxperm, priv, file, type) {
#echo -- "getpriv($maxperm, $priv, $file, $type)" > /dev/tty
	for ptype in $(elements $private)
	do
		if [ "z$type" = "z$ptype" ]; then
			filepriv -M $maxperm "$file"
			return $?
		fi
	done
	runas $priv filepriv -M $maxperm "$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
#echo -- "newattribute(old=$oldattribute,args=$*)" > /dev/tty
	a=$(gensym)
	eval $a=\$$oldattribute
	while [ "$#" != 0 ];
	do
		lreplace $a "$1" "$2"
		shift ; shift
	done
	echo -- "$a"
}

# Usage: maprrouter <newattributes> <what-is-being-expanded> <what-it-was>
#
# 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, origaddr, plustail, domain) {
	local shh al

	al=()
	while read address
	do
		case "$address" in
		'')	shh=(((error expansion "$localpart")))
			lappend al $shh
			continue
			;;
		esac

		defer=''
		shh=$(rrouter "$address" "$origaddr" $attribute \
			"$plustail" "$domain")
		[ -n "$defer" ] &&
		    shh=(((hold "$defer" "$address" $attribute)))
		defer=''
		lappend al $shh
	done
	returns $al
}
