#!/bin/ksh -p
#
# Copyright  05/30/00, Sun Microsystems, Inc. All Rights Reserved
# @(#)utpolicy.sh	1.21 00/05/30
#
PATH=/bin:/usr/bin
export PATH

BASE=`pkginfo -r SUNWuto 2>/dev/null`
SUNWUT=$BASE/SUNWut
SUNWUTLIB=$SUNWUT/lib
SUNWUTSBIN=$SUNWUT/sbin
SUNWUTETC=/etc/opt/SUNWut
SUNWUTLOG=/var/opt/SUNWut/log
SUNWUTTOKENS=/var/opt/SUNWut/tokens
SUNWUTXLOCK=/etc/dt/config/config.lock

# XXX to do: "-a -t del:foo.bar" doesn't work

set -u

# trap "???do what" 0 1 2 15

#PS4='${LINENO}	|'

ETCDIR=$(pkginfo -r SUNWutr 2>/dev/null)/etc/opt/SUNWut
case "$ETCDIR" in
(//*)	ETCDIR="${ETCDIR#/}"
	;;
esac
POLICYDIR=$ETCDIR/policy
POLICYFILE=$POLICYDIR/utpolicy
AUTHPROPS=$ETCDIR/auth.props
USEADMINDB=default
POLICY=""
TIDCONFIG=$ETCDIR/terminals
TIDINIT=false
UPDATETIDLIST=false;
# TIDLIST=""
DOWNCASEENA=true
SRPASSWORD=true

#
# Set default values
#
TEST=false
UNREASONABLE=false
APPLY=false

#
# Functions
#

#
# Command syntax
#
function usage {
	typeset ecode=${1:-99}
	typeset message=${2:-}
	if [[ -n "$message" ]]
	then
		print -u2 -- Error: $message
	fi
	cat <<!
Usage: ${0##*/} [-a] [-r type] [-s type] [-z type] \\
    [-t list] [-t clear] [-t add:tid] [-t del:tid] \\
    [-i clear|soft] \\
    [-f file] [-l] [-p] [-P policy] [-Q] [-u] [-x|+x]
	-a			# Apply the policy as the active system policy.
	-r [card|pseudo|both]	# Policy will lookup and use token DB entry.
	-s [card|pseudo|both]	# Policy will allow self registration of tokens.
	-z [card|pseudo|both]	# Policy grant access to tokens w/o DB entry.
	-t list			# Print the list of card reader mode DTUs.
	-t clear		# Clear the list of card reader mode DTUs.
	-t add:terminalId	# Add terminal to card reader list.
	-t del:terminalId	# Remove terminal from card reader list.
	-p			# Do not require Solaris name and password for
				# self registration.
	-i [clear|soft]		# Restart/init SunRay services.
				# "clear" clears out all existing sessions.
				# "soft" leaves sessions intact. Some sessions	
				# may be unreachable after restart.
	-?			# Print this message.
	# The following options are not supported:
	-f file			# Input policy from file
	-l			# Do not force Ethernet address to lower case.
	-P policy		# Set policy descriptor
	-Q			# run translation test loop
	-u			# allow writing of \"unreasonable\" policies
	+x			# Enable the administrative database
	-x			# Disable the administrative database

    With no arguments, ${0##*/} will print out the policy in effect.
!
	exit $ecode
}


function Error {
	typeset ecode=${1:-99}
	typeset message=${2:-}
	if [[ -n "$message" ]]
	then
		print -u2 -- Error: $message
	fi
	exit $ecode
}


function showcurrentpolicy {
	typeset pd=""
	typeset policyfile=""

	policyfile=$POLICYDIR/$(sed -n '
		s/^[ 	]*//
		s/[ 	]*$//
		s/#.*$//
		s/[ 	][ 	]*=[ 	][ 	]*/=/
		s/^policy=\(.*\)$/\1/p
		' $AUTHPROPS)
	print -- "# Reading policy file: $policyfile"
	if [[ ! -r $policyfile || ! -f $policyfile ]]
	then
		usage 8 "Policyfile is not readable"
	fi
	pd=$(policy2pd $policyfile)
	print -- "# Policy:"
	print -n -- "$0"
	print -- "$pd" | while read line
	do
		case $line in
		(*=db)
			what=${line%=*}
			print -n -- " -r $what"
			;;
		(*=register)
			what=${line%=*}
			print -n -- " -s $what"
			;;
		(*=default)
			what=${line%=*}
			print -n -- " -z $what"
			;;
		(terminals=*)
			terminalsFile=${line#terminals=}
			print -n -- " -t clear"
			cat $terminalsFile | while read line
			do
				case $line in
				(terminalId=*)
					terminal=${line#terminalId=}
					print -n -- " -t add:$terminal"
					;;
				(*)
					;;
				esac
			done
			;;
		(requirepw=*)
			if ! ${line#*=}
			then
				print -n " -p"
			fi
			;;
		(pseudo=insertcard)
			;;
		(*)
			print -u2 -- "\nInvalid policy parameter: $line"
			;;
		esac
	done
	print
}


function restartinstructions {
	CHANGE=${1:-minor}
	if [[ -r "$POLICYFILE" && -r "$POLICYFILE.bu" ]]
	then
		if ! diff $POLICYFILE $POLICYFILE.bu > /dev/null
		then
			CHANGE=major
		fi
	fi
	if [[ "$CHANGE" == "major" ]]
	then
cat <<!

THE MOST RECENT POLICY CHANGE WAS SIGNIFICANT. 

(If you cannot afford to terminate existing sessions, then you can restart
the authentication manager without clearing existing sessions.  Note that
some sessions that were granted access under the old policy may persist. 
Use the following command to restart the authentication manager without
clearing existing sessions: "$SUNWUTSBIN/utpolicy -i soft")

The authentication manager must be restarted for changes to take
effect.  Note that all existing sessions will be terminated. Please run
the following command:

	$SUNWUTSBIN/utpolicy -i clear
!
	else
cat <<!

The authentication manager must be restarted for changes to take effect. 
If a significant policy change has been made then the following command
should be run, note that all existing sessions will be terminated:

	$SUNWUTSBIN/utpolicy -i clear

If a minor policy change was made, such as adding a dedicated card reader
terminal, then it is not necessary to terminate existing sessions and the
following command is preferred:

	$SUNWUTSBIN/utpolicy -i soft
!
	fi
}


#
# tidinit - initialize the list of terminals from /etc/opt/SUNWut/terminals
#
function tidinit {
	typeset tidfile=${1:-${TIDCONFIG}}
	typeset tid_allow=""
	typeset tid_annotateTokens=""
	typeset tid_claim=""
	typeset tid_session=""
	typeset tid_terminalId=""
	typeset tid_block=false

	if $TIDINIT
	then
		return
	fi
	TIDINIT=true
	unset TIDLIST
	if [ ! -f $TIDCONFIG ]
	then
		return
	fi
	# read current configuration while deleting comment lines
	sed '
	    s/^[ 	][ 	]*//
	    s/[ 	][ 	]*$//
	    s/[ 	][ 	]*=[ 	][ 	]*/=/
	    /^#/d
	    ' $tidfile |
	while read line
	do
		case $line in
		(begin)
			tid_block=true
			;;
		(allow=*)
			tid_allow="${line#*=}";
			;;
		(annotateTokens=*)
			tid_annotateTokens="${line#*=}";
			;;
		(claim=*)
			tid_claim="${line#*=}";
			;;
		(session=*)
			tid_session="${line#*=}";
			;;
		(terminalId=*)
			tid_terminalId="${line#*=}";
			if ! $tid_block
			then
				tidadd $tid_terminalId
				tid_terminalId=""
				tid_allow=""
				tid_annotateTokens=""
				tid_claim=""
				tid_session=""
				tid_block=false
			fi
			;;
		(end)
			tidadd $tid_terminalId $tid_allow $tid_annotateTokens $tid_claim $tid_session
			tid_terminalId=""
			tid_allow=""
			tid_annotateTokens=""
			tid_claim=""
			tid_session=""
			tid_block=false
			;;
		esac
	done
}


# tidlist - print the current list of terminal ids
function tidlist {
	tidinit
	typeset -i i=0
	typeset -i limit=${#TIDLIST[@]}
	typeset saveIFS="$IFS"

	while [[ $i -lt $limit ]]
	do
		if [[ -n "${TIDLIST[$i]}" ]]
		then
			IFS=:
			set ${TIDLIST[$i]}
			IFS="${saveIFS}"
			print $1
		fi
		i=i+1	# no let statement needed since "typeset -i" is used
	done

}


# tidclear - clear the current list of terminal ids
function tidclear {
	unset TIDLIST
	TIDINIT=true
}


# tidadd tid [allow claim session annotate]
# Default to "pseudo" if model label is missing from the terminalId
# All terminalIDs formed via gui will have a model prefix,
# however the  model label is not mandatory since the  auth manager
# will be matching against the $ena (currently mac addr) part of the TerminalID
function tidadd {
	tidinit
	typeset tid="$1"
	typeset allow="${2:-}"		# optional
	typeset claim="${3:-}"		# optional
	typeset session="${4:-}"	# optional
	typeset annotate="${5:-}"	# optional

	case $tid in
	\\*)
		# literal, just strip off the leading backslash
		tid=${tid#\\}
		DOWNCASEENA=false;
		;;
	*.*)
		# A fully qualified tid is fine
		;;
	*)
		tid="pseudo.$tid"
		;;
	esac
	if $DOWNCASEENA
	then
		case $tid in
		*.*)
			typeset model=${tid%.*}		# XXX
			typeset -l ena=${tid#*.}	# XXX
			tid=$model.$ena			# XXX
			;;
		*)
			;;
		esac
	fi

	typeset tokenspec="$tid:$allow:$claim:$session:$annotate"

	#
	# Check for duplicates
	#
	typeset -i limit=${#TIDLIST[@]}
	typeset -i i=0
	while [[ $i -lt $limit ]]
	do
		if [[ "${TIDLIST[$i]}" == "$tokenspec" ]]
		then
			print -u2 "Warning: duplicate terminal id \"$tid\" ignored"
			return 0
		fi
		((i+=1))
	done

	# Append the new entry to the indexed array
	TIDLIST[${#TIDLIST[@]}]="$tid:$allow:$claim:$session:$annotate"
	return 0
}


# tiddel - remove a terminal id from the list of terminal ids
function tiddel {
	tidinit
	typeset tid=$1
	typeset -i i=0
	typeset -i limit=${#TIDLIST[@]}
	typeset saveIFS="$IFS"
	while [[ $i -lt $limit ]]
	do
		IFS=:
		set ${TIDLIST[$i]}
		IFS="${saveIFS}"
		if [[ "$1" == "$tid" || "$1" == "${ena:-NOENA}" ]]
		then
			# delete the entry by copying the last entry over it
			TIDLIST[$i]=${TIDLIST[$((${#TIDLIST[@]} - 1))]}
			# and then unsetting the last entry
			unset TIDLIST[$((${#TIDLIST[@]} - 1))]
			return 0
		fi
		i=i+1	# no let statement needed since "typeset -i" is used
	done
	return 1
}


# tidsave - write the configuration for the TerminalId authentication module
function tidsave {
	typeset -i i=0
	typeset -i limit=${#TIDLIST[@]}
	typeset tid
	typeset allow
	typeset claim
	typeset session
	typeset annotate
	typeset saveIFS="$IFS"

	if [[ ${#TIDLIST} -eq 0 ]]
	then
		return 0
	fi
	while [[ $i -lt $limit ]]
	do
		IFS=:
		set ${TIDLIST[$i]}
		IFS="${saveIFS}"
		tid=$1
		allow=${2:-}
		claim=${3:-}
		session=${4:-}
		annotate=${5:-}
		if [[ -n "${allow}" || -n "${claim}" || -n "${session}" ||
		    -n "${annotate}" ]]
		then
			print begin
			print terminalId=$tid
			[[ -n "${allow}" ]] &&
				print allow=${allow}
			[[ -n "${claim}" ]] &&
				print claim=${claim}
			[[ -n "${session}" ]] &&
				print session=${session}
			[[ -n "${annotate}" ]] &&
				print annotateTokens=${annotate}
			print end
		else
			print terminalId=$tid
		fi

		i=$i+1
	done
}


#
# Translate from a single line policy description to the multi-line
# format required by the Corona authentication manager
#
# [[card|pseudo|both]=[db|sessionType]][,...]
function pd2policy {
	typeset filter=""
	typeset action=""
	typeset token=""
	typeset flags=""
	typeset module=""
	typeset instance=0
	typeset CAUGHTCARD=false
	typeset CAUGHTPSEUDO=false
	typeset USEDPSEUDO=false;
	typeset USEDCARD=false;

	if [[ -z "${1:-}" ]]
	then
		# XXX should set an error string and use it in caller
		print -u2 Empty Policy
		return 1
	fi
	print ZeroAdmin.m0 -a T0unknown,SyncUnknown -s carderror
	# Set positional parameters: key=value [key=value [key=value]]
	set $(print -- $1 | sed -e 's/[ 	][ 	]*//g' -e 's/,/ /g')
	for filter
	do
		((instance+=1))
		flags=""
		token=${filter%=*}
		action=${filter#*=}
		case $action in
		(db)
			module=Registered
			;;
		(default)
			module=ZeroAdmin
			;;
		(*)	# Session type
			module=ZeroAdmin
			flags="-s $action"
			if [[ "$action" == "register" ]]
			then
				if ! $SRPASSWORD
				then
					flags="$flags -p"
				fi
			fi
			;;
		esac
		case $token in
		(card)
			flags="-r pseudo $flags"
			if [[ $module != Registered ]]
			then
				CAUGHTCARD=true
			fi
			USEDCARD=true;
			;;
		(pseudo)
			flags="-a pseudo $flags"
			if [[ $module != Registered ]]
			then
				CAUGHTPSEUDO=true
			fi
			USEDPSEUDO=true;
			;;
		(both)
			flags="-a default $flags"
			if [[ $module != Registered ]]
			then
				CAUGHTCARD=true
				CAUGHTPSEUDO=true
			fi
			USEDPSEUDO=true;
			USEDCARD=true;
			;;
		esac
		print -- $module.m$instance $flags
	done

	case $CAUGHTPSEUDO.$CAUGHTCARD in
	(true.true)
		# Everything is always caught, no additional policy is needed
		;;
	(false.false)
		# 
		if $USEDPSEUDO
		then
			((instance+=1))
			# Only registered terminals get in, this one denied
			# but if cards also used, ask for card
			if $USEDCARD
			then
				print ZeroAdmin.m$instance -a pseudo -s insertcard
			else
				print ZeroAdmin.m$instance -a default -s noentry
			fi
		elif $USEDCARD
		then
			# pseudo is not used, so this is central registered
			# ask for card
			((instance+=1))
			print ZeroAdmin.m$instance -a pseudo -s insertcard
			((instance+=1))
			print ZeroAdmin.m$instance -r pseudo -s noentry
		else
			# Unless an explicit TerminalId was used, there is
			# no way to get in.
			((instance+=1))
			print ZeroAdmin.m$instance -r pseudo -s noentry
		fi
		;;
	(false.true)
		# Cards are always caught but pseudo can only get
		# in if it is registered or pseudo can never get in.
		((instance+=1))
		print ZeroAdmin.m$instance -a pseudo -s insertcard
		;;
	(true.false)
		# Centrally registered but card is denied access
		# or all Cards are prohibited all the time
		((instance+=1))
		print ZeroAdmin.m$instance -r pseudo -s noentry
		;;
	esac
	return 0
}

#
# This function helps to translate between the auth manager's policy
# files and a single line policy description.
#
function parseFilterOption {
	typeset line=$1
	typeset token=""
	typeset action=""

	# Catch default session type sepeification
	case $line in
		(*-s*)
			# extract the "action" which should just be "register"
			action=$(print -- $line |
			    sed -n -e 's/.*-s[ 	]*\([^ 	]*\).*$/\1/p')
			if [[ $action == "register" ]]
			then
				case $line in
				(*-p*)
					print requirepw=false
					;;
				(*)
					print requirepw=true
					;;
				esac
			fi
			;;
		(*)
			action=default
			;;
	esac

	case $(print -- $line |
	    sed -n -e 's/.*-\([ar]\)[ 	]*\([^ 	]*\).*$/\1,\2/p') in
	(a,pseudo)
		token=pseudo
		;;
	(a,default)
		token=both
		;;
	(r,pseudo)
		token=card
		;;
	(*)
		print -u2 -- Invalid policy file
		exit 6
		;;
	esac
	print -- $token=$action
}

#
# This function translates between the auth manager's policy
# files and a single line policy description.
#
function policy2pd {
	typeset pfile=$1
	typeset line=""
	typeset terminalsFile=""
	sed '
	    s/[ 	][ 	]*/ /g
	    s/^ //
	    s/#.*$//
	    s/ $//
	    /^$/d
	    ' $pfile |
	while read line
	do
		spec=""
		case "$line" in
		(*T0unknown,SyncUnknown*)	# card read errors
			;;
		("ZeroAdmin"*"-s noentry")	# uncaught tokens
			;;
		(TerminalId.*-c*)	# uncaught tokens
			terminalsFile=${line#*-c }
			tidinit $terminalsFile
			;;
		(ZeroAdmin*)
			line=${line#ZeroAdmin.}
			instance=${line%% *}
			line=${line#${instance} }
			spec=$(parseFilterOption "${line}")
			;;
		(Registered*)
			line=${line#Registered.}
			instance=${line%% *}
			line=${line#${instance} }
			spec=$(parseFilterOption "${line}")
			token=${spec%%=*}
			spec="$token=db"
			;;
		(*)
			print -- "BAD LINE: $line"
			print -- "<${line#Registered}>"
			print -- "<${line#ZeroAdmin}>"
			exit 7
			;;
		esac
		if [[ -n "$spec" ]]
		then
			print -- "$spec"
		fi
	done
	if [[ -n "$terminalsFile" ]]
	then
		print -- terminals=$terminalsFile
	fi
}

# Loop though all the policies that this program has a hope to parse
# and translate them to utauthd policy files and back. Check for
# "unreasonable" policies.
function testprog {
	typeset POLICY
	typeset P1
	typeset P2
	typeset reg
	typeset self
	typeset za
	for reg in card pseudo both ""
	do
		if [[ -n "$reg" ]]
		then
			P1="$reg=db"
		else
			P1=""
		fi
		for self in card pseudo both ""
		do
			if [[ -n "$self" ]]
			then
				P2="$self=register"
			else
				P2=""
			fi
			for za in card pseudo both ""
			do
				if [[ -n "$za" ]]
				then
					P3="$za=default"
				else
					P3=""
				fi
				POLICY=$(print "$P1,$P2,$P3" | sed \
					-e 's/^,,*//' \
					-e 's/,,*$//' \
					-e 's/,,/,/'
					)
				if isReasonable "$P1" "$P2" "$P3"
				then
					print "\nPD=$POLICY"
					pd2policy $POLICY > /tmp/.$$
					ANDBACK=$(policy2pd /tmp/.$$ | tr ' \012' ',,')
					ANDBACK=${ANDBACK#,}
					ANDBACK=${ANDBACK%,}
					print "   $ANDBACK"
					cat -n /tmp/.$$
					rm -f /tmp/.$$
				else
					print XXX $POLICY
				fi
			done
		done
	done
}

#
# Return true if the given policy has no redundant entries or
# dead ends.
#
function isReasonable {
	typeset db=""
	typeset self=""
	typeset za=""
	typeset ok=true
	ERR=""

	for arg
	do
		case $arg in
		(*=db)	db=${arg%=*};;
		(*=register)	self=${arg%=*};;
		(*=default)	za=${arg%%=*};;
		("")	;;
		(*)	return 1;;
		esac
	done

	[[ "$db" == "card" && "$self" == both ]]	&& ok=false &&
		ERR="DB entries for pseudo are not used.\n"
	[[ "$db" == "card" && "$self" == pseudo ]]	&& ok=false &&
		ERR="DB entries for pseudo are not used.\n$ERR"
	[[ "$db" == "pseudo" && "$self" == both ]]	&& ok=false &&
		ERR="DB entries for card are not used.\n$ERR"
	[[ "$db" == "pseudo" && "$self" == card ]]	&& ok=false &&
		ERR="DB entries for card are not used.\n$ERR"
	[[ "$self" == "both" && -n "$za" ]]		&& ok=false &&
		ERR="both get stuck in self register, -z flag is not used.\n$ERR"
	[[ -n "$self" && "$self" == "$za" ]]		&& ok=false &&
		ERR="$self gets stuck in self register, -z flag is not used.\n$ERR"
	[[ -n "$self" && "$za" == "both" ]]		&& ok=false &&
		ERR="$self gets stuck in self register, change -z flag.\n$ERR"
	[[ -z "$db" && -n "$self" ]]			&& ok=false &&
		ERR="Database entries are not used. Use -r flag also.\n$ERR"
	[[ -z "$db" && -z "$za" ]]			&& ok=false &&
		ERR="Normal sessions are not accessible with this policy.\n$ERR"

	if ! $USEADMINDB
	then
		[[ -n "$db" || -n "$self" ]]		&& ok=false &&
			ERR="Administrative database would be disabled. Use -z flag only or remove -x flag.\n$ERR"
	fi

	if $ok
	then
		return 0
	else
		ERR="${ERR}Use the -u flag to override this message and to use the policy anyway."
		return 1
	fi
}


function editAuthProps {
	typeset kv=${1}
	typeset key=${kv%=*}

	if [[ ! -w $(dirname $AUTHPROPS) ]]
	then
		print -u2 Cannot update $AUTHPROPS
		return 1
	fi
	rm -f $AUTHPROPS.bu
	cp -p $AUTHPROPS $AUTHPROPS.bu
	chmod u+w $AUTHPROPS
	# the "-" after ed tells ed that this is not interactive
	ed - $AUTHPROPS <<-! 2>/dev/null 1>&2
	g/^$key/d
	i
	$kv
	.
	w
	q
	!
	chmod u-w $AUTHPROPS
	return $?
}


# enableAdminDB - turn the DB on or off as far as utauthd is concerned
# XXX this function might be too fragile
function enableAdminDB {
	typeset on=${1:-false}

	if $on
	then
		if egrep -s '^#[ 	]*adminConfigFile' $AUTHPROPS
		then
			ed - $AUTHPROPS <<-! 2>/dev/null 1>&2
			g/^#[ 	]*adminConfigFile/s/^#[ 	]*//
			w
			q
			!
			return 0
		fi
	else
		if egrep -s '^[ 	]*adminConfigFile' $AUTHPROPS
		then
			ed - $AUTHPROPS <<-! 2>/dev/null 1>&2
			g/^[ 	]*adminConfigFile/s/^/#/
			w
			q
			!
			return 0
		fi
	fi
	return 1
}


function justRestart {
	if [[ "$(/usr/xpg4/bin/id -u)" != "0" ]]
	then
		Error 15 "Only root can restart the SunRay services"
	fi

	print "Any command line option other than -i was ignored."

	${SUNWUTLIB}/utauthd -e
	sleep 5
	(
		cd ${SUNWUTLOG}
		trap "" HUP
		${SUNWUTLIB}/utagelog auth_log 9
		/bin/priocntl -c TS -p 30 -m 30 -e \
			${SUNWUTLIB}/utauthd -b > auth_log 2>&1 & 
	) >/dev/null 1>&2
	exit 0
}


function purgeAndRestart {
	if [[ "$(/usr/xpg4/bin/id -u)" != "0" ]]
	then
		Error 15 "Only root can restart the SunRay services"
	fi

	print "Any command line option other than -i was ignored."

	/etc/init.d/utsvc stop

	# Get the parent dtlogin pid
	typeset dtlparent=$(ps -eo pid,ppid,comm |
	    nawk '$2 == 1  && $3 == "/usr/dt/bin/dtlogin" {print $1}' -)

	#
	# Clean out the SunRay sessions configuration
	# (utacleanup with no arg does this unconditionally)
	#
	/etc/init.d/utacleanup

	kill -HUP $dtlparent	# tell dtlogin parent to read config again

	#
	# Wait for all SunRay X-sessions to exit
	#
	print -n -- \
	    "Waiting 60+ seconds to insure that all SunRay sessions exit..."
	sleep 65
	print "\nRestarting SunRay services"
	(/etc/init.d/utsvc start &) >/dev/null 1>&2

	exit 0
}


function checkarg {
	typeset arg=${1:-}
	case $arg in
	(card|both|pseudo)
		return 0
		;;
	(*)
		usage 6 "Invalid argument $arg"
		;;
	esac
}


#
# Main program
#


#
# Parse Options
#
while getopts ai:t:r:s:z:P:pf:luxQT:F: c 2>/dev/null
do
	case $c in
	(a)	APPLY=true
		;;
	(Q)	TEST=true
		;;
	(r)	DB=$OPTARG
		checkarg $OPTARG
		[[ -n "$POLICY" ]] && usage 0 "Conflicting policies specified"
		;;
	(s)	SELFREG=$OPTARG
		checkarg $OPTARG
		[[ -n "$POLICY" ]] && usage 1 "Conflicting policies specified"
		;;
	(z)	ZEROADMIN=$OPTARG
		checkarg $OPTARG
		[[ -n "$POLICY" ]] && usage 2 "Conflicting policies specified"
		;;
	(p)	SRPASSWORD=false
		;;
	(P)	POLICY=$OPTARG
		[[ -n "${DB:-}" || -n "${SELFREG:-}" || -n "${ZEROADMIN:-}" ]] ||
		    usage 3 "Conflicting policies specified"
		;;
	(f)	PFILE=$OPTARG
		[[ -r $PFILE ]] || usage 3 "Cannot read $PFILE"
		;;
	(F)	POLICYFILE=$OPTARG
		POLICYDIR=$(dirname $OPTARG)
		[[ -n "$POLICYDIR" ]] || POLICYDIR=$PWD
		POLICYFILE=$POLICYDIR/$(basename $POLICYFILE)
		[[ -w $POLICYFILE || -w $POLICYDIR ]] ||
		    usage 11 "Cannot create policy file"
		;;
	(t)	UPDATETIDLIST=true;
		case $OPTARG in
		(del:*)	tiddel ${OPTARG#del:}
			;;
		(add:*)	tidadd ${OPTARG#add:}
			;;
		(clear)	tidclear
			;;
		(list)	tidlist
			exit 0
			;;
		esac
		;;
	(T)	TIDCONFIG=$OPTARG
		;;
	(l)	DOWNCASEENA=false
		;;
	(u)	UNREASONABLE=true
		;;
	(x)	USEADMINDB=false
		;;
	(+x)	USEADMINDB=true
		;;
	(i)	case $OPTARG in
		(clear)	purgeAndRestart;;
		(soft)	justRestart;;
		(*)	usage 4 "Invalid argument to -i option";;
		esac
		exit 0
		;;
	(\?)
		shift $(( $OPTIND - 2 ))
		if [[ "$1" != "-?" ]]
		then
			usage 4 "Invalid option: $1";
		else
			usage 4
		fi
		;;
	esac
done
shift $(( $OPTIND - 1 ))

if [[ $# != 0 ]]
then
	usage 7 "Extraneous arguments on command line: $@"
fi

#
# The test flag overrides everything else
#
if $TEST
then
	if [[ $USEADMINDB == default ]]
	then
		USEADMINDB=true
	fi
	testprog
	exit 0
fi


#
# See if policy is being constructed
#
if [[ -n "${DB:-}" || -n "${SELFREG:-}" || -n "${ZEROADMIN:-}" ]]
then
	if [[ -n "$POLICY" ]]
	then
		usage 5 "P flag cannot be used with -r, -s, or -z flags"
	fi
	if [[ -n "${DB:-}" ]]
	then
		POLICY="$POLICY,$DB=db"
	fi
	if [[ -n "${SELFREG:-}" ]]
	then
		POLICY="$POLICY,$SELFREG=register"
	fi
	if [[ -n "${ZEROADMIN:-}" ]]
	then
		POLICY="$POLICY,$ZEROADMIN=default"
	fi
fi

if [[ -n "$POLICY" ]]
then
	#
	# Policy was specified on command line. Cannot also take it from file
	#
	if [[ -n "${PFILE:-}" ]]
	then
		usage 10 "Conflicting policies specified"
	fi

	#
	# If we are creating a policy and the user didn't direct us
	# to ignore the admin database, then use the admin database.
	#
	if [[ $USEADMINDB == default ]]
	then
		USEADMINDB=true
	fi

	#
	# Translate the policy specification into "key=value key=value..."
	#
	POLICY=${POLICY#,}
	set $(print $POLICY | tr ',' ' ')

	#
	# See if the policy makes sense. Complain if it doesn't.
	#
	if ! $UNREASONABLE && ! isReasonable "${1:-}" "${2:-}" "${3:-}"
	then
		print -u2 -- "Error: unreasonable policy: '$POLICY'"
		print -u2 "$ERR"
		exit 8
	fi
else
	#
	# Arrive here if -r, -s, and -z flags were not used
	#
	if [[ -z "${PFILE:-}" ]]
	then
		#
		# No policy specified at all.
		# Things that can be done without -r, -s, -z, -f, and -P:
		#
		# 1) Turn DB on and off
		# 2) -t option
		# 3) print out current policy
		if [[ "$UPDATETIDLIST" == true && "$APPLY" == true ]]
		then
			#
			# XXX This is common code that should go into a
			# XXX function.
			#
			tidsave > $TIDCONFIG
			ed - $POLICYFILE <<-! 2>/dev/null 1>&2
			g/TerminalId.m0/d
			0a
			TerminalId.m0 -c $TIDCONFIG
			.
			w
			q
			!
			editAuthProps policy=$(basename $POLICYFILE)
			case $USEADMINDB in
			(*)	;&
			(true)	enableAdminDB true;;
			(false)	enableAdminDB false;;
			esac
			if [[ $? -eq 0 ]]
			then
				restartinstructions major
			else
				restartinstructions minor
			fi
			exit 0
		elif [[ $USEADMINDB == default ]]
		then
			# print XXX No policy specified, so show old one
			showcurrentpolicy
			exit 0
		else
			# command was run solely to turn DB on or off
			if enableAdminDB $USEADMINDB
			then
				restartinstructions major
			else
				restartinstructions major
			fi
			exit 0
		fi
	else
		if [[ $USEADMINDB == default ]]
		then
			USEADMINDB=true
		fi
		POLICY=$(policy2pd $PFILE | tr ' \012' ',,')
		if [[ $? != 0 ]]
		then
			print -u2 -- "Invalid policy file: $PFILE"
			exit 9
		else
			# clean up if needed
			POLICY=${pd%,}
		fi
	fi
fi

if ! $UNREASONABLE && ! isReasonable "${1:-}" "${2:-}" "${3:-}"
then
	print -u2 -- "Error: unreasonable policy: '$POLICY'"
	print -u2 -- "$ERR"
	exit 12
fi

if $APPLY
then
	cp -p $POLICYFILE $POLICYFILE.bu
	if [[ ${#TIDLIST} -ne 0 ]]
	then
		tidsave > $TIDCONFIG
		print TerminalId.m0 -c $TIDCONFIG > $POLICYFILE
		pd2policy $POLICY >> $POLICYFILE
	else
		pd2policy $POLICY > $POLICYFILE
	fi
	editAuthProps policy=$(basename $POLICYFILE)
	if $USEADMINDB
	then
		enableAdminDB true
	else
		enableAdminDB false
	fi
	if [[ $? -eq 0 ]]
	then
		restartinstructions major
	else
		restartinstructions minor
	fi
else
	if [[ ${#TIDLIST} -ne 0 ]]
	then
		tidsave
		print -- -------
		print TerminalId.m0 -c $TIDCONFIG
	fi
	pd2policy $POLICY
fi

exit 0
