#!/bin/ksh -p
#
# ident "@(#)utpolicy.sh	1.128 04/08/24 SMI"
#
# Copyright 1999-2004 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

# utpolicy has been redesigned to only store the policy options in
# SRDS and not generate the policy stack file.  That will be done
# by another script, utgenpolicy, which is called by utauthd.

# Explicitly set umask so that files get created with correct permissions
umask 022

#exec > /tmp/utpolicy.$$ 2>&1
PROG=$0
#set -x

PATH=/bin:/usr/bin
export PATH

BASEDIR=/etc/opt/SUNWut/basedir
SUNWUT=`${BASEDIR}/lib/utprodinfo -r SUNWuto 2>/dev/null`/SUNWut
SUNWUTSBIN=$SUNWUT/sbin
SUNWUTLIB=$SUNWUT/lib
SUNWUTETC=/etc/opt/SUNWut
SUNWUTPROCDIR=/tmp/SUNWut/session_proc
UTGLPOLICY=$SUNWUTLIB/utglpolicy
UTREADER=$SUNWUTSBIN/utreader

## This file is created by the command /opt/SUNWbb/bin/bbmkuser
USER_CONF=/var/opt/SUNWbb/users.conf

UTADMIN_CONF=$SUNWUTETC/utadmin.conf

set -u

# Directory where policy files reside
POLICYDIR=$SUNWUTETC/policy

# Default policy file after policy is set
# Before utconfig is run, policy file is set to ZeroAdmin
POLICYFILE=$POLICYDIR/utpolicy
# This property file has an entry
# policy = XXXXX, where XXXX is the policy file to be used
# The default is "utpolicy" and therefore is not present in the file
# But, if policy is ZeroAdmin or RegisterDistributed, you'll see a line
# policy = ZeroAdmin or policy = RegisterDistributed
# in the file 
AUTHPROPS=$SUNWUTETC/auth.props
#
# If we are creating a policy and the user didn't direct us
# to ignore the admin database, then use the admin database.
#
POLICY=""

SRPASSWORD=true

# Somehow in 1.3 we have wound up in the unfortunate situation of
# using the term "logical token" to mean two things - it used to only
# mean the registered form of a raw token which is stored in our DB,
# but now it also means tokens which have been translated by policy
# (including the old "logical tokens").	 In utpolicy I will use the
# term "translated tokens" for translated tokens to differentiate them
# from the pre-existing "logical tokens" in the DB.
#
# We only need this list until we properly introduce the "card" type,
# so we don't have to reject everything else when we really wanted
# only to accept cards, so this code should go away. XXXRAD
REJECTTRANSLATEDTOKENS="-r user -r mobile -r asc -r auth -r escape"

#
# Set default values
#
UNREASONABLE=false
APPLY=false
SERVERSELECT=false
TERMINALGROUP=false
POLICYSET=false
MOBILE=false
ASC=false
ASCCARDS=""
ACCEPTASCCARDS=""
REJECTASCCARDS=""
EXITENABLE=""
#
# Functions
#

#
# Command syntax
#
function usage {
	typeset ecode=${1:-99}
	typeset message=${2:-}
	if [[ -n "$message" ]]
	then
		print -u2 -- ERROR: $message
	fi

	utcmd=${PROG##*/}

	if [[ "$OS" == "SunOS" ]]
	then
		cat <<!
Usage: 
${utcmd} 
${utcmd} -a { [-g] [-m] [-M] [-p] [-r type] [-s type] [-z type] [-S smartcard_type] [-d] }
${utcmd} -h

	-a			# Apply the policy as the active system policy.
	-p			# Do not require Solaris name and password for
				  self registration.
	-g			# Turn on session selection within a server
				  group.
	-k card|pseudo|both	# Use Kiosk mode for sessions
	-m			# Enable multihead sessions
	-M			# Policy will allow Non-SmartCard Mobility.
				  Requires a policy that allows pseudo tokens.
	-d			# Disable Exit option for Non-smartCard Mobility GUI
	-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.
	-S smartcard_type	# Send this card type to utsclogin

	-h			# Print this message.

	# The following options are RESERVED:
	-f -l -u -P -Q -T -F

    With no arguments, ${utcmd} will print out the policy in effect.
  
!
	else
		cat <<!
Usage: 
${utcmd} 
${utcmd} -a { [-g] [-m] [-p] [-r type] [-s type] [-z type] }
${utcmd} -h

	-a			# Apply the policy as the active system policy.
	-p			# Do not require Linux name and password for
				  self registration.
	-g			# Turn on session selection within a server
				  group.
	-m			# Enable multihead sessions
	-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.

	-h			# Print this message.

	# The following options are RESERVED:
	-f -l -u -P -Q -T -F

    With no arguments, ${utcmd} will print out the policy in effect.
  
!
	fi
	exit $ecode
}


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

function checkdsgroup {
        REPLICAOUT=`$SUNWUTSBIN/utreplica -l`
        if [[ -n $(print $REPLICAOUT | grep "No replica") ]]
        then
		print -- "false" 
	else
		print -- "true"
        fi
}

function showcurrentpolicy {
	typeset -i i=0
	POLICYOPTS=`$UTGLPOLICY 2>/dev/null` 
	if [[ $? -ne 0 ]]
	then
		Error 1 "Could not retrieve policy."
	fi
	
	parsereaderopts $POLICYOPTS
}

function parsereaderopts {
	typeset UTP_OPT=""

	while [[ $# -gt 0 ]]
	do
		if [[ "$1" = "-t" ]]; then
			shift 2
		else
			UTP_OPT="$UTP_OPT $1"
			shift 1
		fi
	done
	print -- "# Current Policy:"
        normalize $UTP_OPT
}

function showzeroadminpolicy {
	print -- "# Current Policy:"
	print -- "-a -g -z both"
}

function synchwrite {
	tries=0
	while [ $tries -lt 10 ]
	do
		if [[ $(normalize $@) = $(normalize `$UTGLPOLICY 2>/dev/null`) ]]
		then
			return
		fi
		sleep 1
		let "tries = tries + 1"
	done
	Error 1 "Could not update policy."
}

function normalize {
	print -- $(print -- $@)
}

function applyWarning {
cat <<!

WARNING: Please use -a option to effect a policy change.

!

}

function restartinstructions {
	#
	# If user did not use -a option, no need to print out restart instruction
	#
	if ! $APPLY
	then
	  applyWarning
	  return
	fi

	major_msg
}

#
# Return true if the given policy has no redundant entries or
# dead ends.
#
function isReasonable {
	typeset db=""
	typeset self=""
	typeset za=""
	typeset mobile=""
	typeset asc=""
	typeset ok=true
	typeset cardpolicy=false
	typeset pseudopolicy=false
	typeset exitenable=""
	ERR=""
	#
	# this (require*) case is put because -p flag from PFILE
	# caused it to fail. 
	#
	for arg
	do
		case $arg in
		(*=db)	db=${arg%=*};;
		(*=register)	self=${arg%=*};;
		(*=default)	za=${arg%%=*};;
		(mobile=*) mobile=true;;
		(exitenable=*) exitenable=false ;;
		(asc=*) asc=true; cardpolicy=true ;;
		("")	;;
		(require*) ;;		   
		(*)	return 1;;
		esac
		
		case $arg in
		(pseudo=*) pseudopolicy=true ;;
		(card=*)   cardpolicy=true ;;
		(both=*)   pseudopolicy=true; cardpolicy=true ;;
		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"
	[[ "$db" == "pseudo" && "$self" == both ]]	&& ok=false &&
		ERR="DB entries for card are not used.\n"
	[[ "$db" == "pseudo" && "$self" == card ]]	&& ok=false &&
		ERR="DB entries for card are not used.\n"
	[[ "$self" == "both" && -n "$za" ]]		&& ok=false &&
		ERR="both get stuck in self register, -z flag is not used.\n"
	[[ -n "$db" && "$db" == "$za" ]]		&& ok=false &&
	      	ERR="$za gets stuck in register, -z flag is not used.\n"
	[[ -n "$db" && "$za" == "both" ]]		&& ok=false &&
	      	ERR="$db gets stuck in register, -z flag is not used.\n"
	[[ -n "$za" && "$db" == "both" ]]                && ok=false &&
                ERR="$za gets stuck in register, -z flag is not used.\n"
	[[ -n "$self" && "$self" == "$za" ]]		&& ok=false &&
		ERR="$self gets stuck in self register, -z flag is not used.\n"
	[[ -n "$self" && "$za" == "both" ]]		&& ok=false &&
		ERR="$self gets stuck in self register, change -z flag.\n"
	[[ -z "$db" && -n "$self" ]]			&& ok=false &&
		ERR="Database entries are not used. Use -r flag also.\n"
	[[ -z "$db" && -z "$za" && -z "$mobile" && -z "$asc"  ]] && ok=false &&
		ERR="Normal sessions are not accessible with this policy.\n"
	[[ "$cardpolicy" == false &&
	    ( "$KIOSK" == card || "$KIOSK" == both ) ]] && ok=false &&
		ERR="Kiosk mode specified for cards but no policy.\n"
	[[ "$pseudopolicy" == false &&
	    ( "$KIOSK" == pseudo || "$KIOSK" == both ) ]] && ok=false &&
		ERR="Kiosk mode specified for pseudos but no policy.\n"
	[[ "$pseudopolicy" == false && -n "$mobile" ]]              && ok=false &&
                ERR="Must specify pseudo policy with the -M flag.\n"	
	[[ -z $mobile && -n $exitenable ]]  && ok=false &&
		ERR="Cannot use -d option without specifying -M.\n"

	if $ok
	then
		return 0
	else
		return 1
	fi
}

#
# Modify "policy" field of auth.props to indicate which policy file is being used
# e.g. policy=ZeroAdmin or policy=utpolicy
#
function editAuthProps {

	typeset kv=${1}
	typeset key=${kv%=*}
	if [[ ! -w $(dirname $AUTHPROPS) ]]
	then
		print -u2 Cannot update $AUTHPROPS
		return 1
	fi
	[[ -f "$AUTHPROPS.bu" ]] && rm -f $AUTHPROPS.bu
	[[ -f "$AUTHPROPS" ]] && 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 $?
}

function mustBeRoot {
	case "$(id)" in
	'uid=0('*)
		;;
	*)
		Error 15 "Must be root to make policy changes" 
		;;
	esac
}

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

#
# Check if policy is reasonable.
#
function validatepolicy {
       typeset policy=${1:-}
       set $(print $policy | tr ',' ' ')
       if ! $UNREASONABLE && ! isReasonable "$@"
       then
	 print -u2 -- "ERROR: unreasonable policy: '$policy'"
	 print -u2 -- "$ERR"
	 exit 12

       fi
}

major_msg(){

cat <<!

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

	$SUNWUTSBIN/utrestart -c

If a minor policy change was made then it is not necessary to terminate 
existing sessions and a warm restart is sufficient by executing the 
following command:

	$SUNWUTSBIN/utrestart
!

}

#
# Determin the option to allow multiple -r, -s, or -z options in
# one policy string.
# getarg $newoption $previous_option
# OUTPUT: combined_option
# For example, getarg "card" "pseudo" would output "both"
#
getarg() {
	if [[ $# -eq 1 ]]
	then
		print $1
	elif [[ ( "$1" == "both" ) || ( "$2" == "both" ) ||
		( "$1" == "card" && "$2" == "pseudo" ) ||
		( "$1" == "pseudo" && "$2" == "card" ) ]]
	then
		print "both"
	else
		print $1
	fi
}


#
# Main program
#

#
# No argument on cmd line, simply print out current policy and exit
#
if [ $# -eq 0 ]
then
	if [[ -f $UTADMIN_CONF ]]
	then
		showcurrentpolicy
	else
		showzeroadminpolicy
	fi
	exit 0
else
	mustBeRoot
fi

OS=`uname -s`
case "$OS" in
SunOS)	OPTLIST="adgmk:r:s:z:P:pluhMS:ti"
	;;

Linux)	OPTLIST="agmr:s:z:P:pluh"
	;;
	
*)	# undefined OS
	print -u2 "Unable to determince the OS running on this system."
	exit 1
esac

#
# Check for invalid options before connecting to ldap
#
while getopts ${OPTLIST} c 2>/dev/null
do
	case $c in
	(t)	usage 1 "The -t option is obsolete.  Please use utreader to configure token readers."
		;;
	(i)	usage 1 "The -i option is obsolete.  Please use utrestart to restart services."
		;;
	(\?)	usage 21 "Invalid option on command line: $*"
	esac
done

if [[ `expr $OPTIND - 1` != $# ]]
then
	usage 22 "Invalid or extraneous arguments on command line: $*"
fi

#
# Reset the OPTIND variable before calling getopts again
#
OPTIND=1

#
# Check to see if utconfig has been run first by checking for the
# existence of /etc/opt/SUNWut/utadmin.conf
# 
if [[ ! -f $UTADMIN_CONF ]]
then
	Error 1 "Please run utconfig first before further setting/querying of utpolicy."
fi

# Parse Options
#
DB=""
SELFREG=""
ZEROADMIN=""
KIOSK=""
UT_OPTIONS=$@
IS_POLICY_CHANGE=false
while getopts adgmk:r:s:z:P:phMS: c $UT_OPTIONS 2>/dev/null
do
	case $c in
	(a)	APPLY=true
		;;
	(d)	EXITENABLE=false
		;;
	(k)	if [[ -e $USER_CONF ]]
		then
			checkarg $OPTARG
			KIOSK=$(getarg $OPTARG $KIOSK)
			IS_POLICY_CHANGE=true
		else
			Error 9 "Cannot enable Controlled Access Mode.  Please run utconfig."             
		fi
		;;
	(r)	checkarg $OPTARG
		DB=$(getarg $OPTARG $DB)
		[[ -n "$POLICY" ]] && usage 19 "Conflicting policies specified"
		POLICYSET=true
		IS_POLICY_CHANGE=true
		;;
	(s)	checkarg $OPTARG
		SELFREG=$(getarg $OPTARG $SELFREG)
		[[ -n "$POLICY" ]] && usage 1 "Conflicting policies specified"
		POLICYSET=true	# not a real policy, but let it through for error
				# checking purposes
		IS_POLICY_CHANGE=true
		;;
	(z)	checkarg $OPTARG
		ZEROADMIN=$(getarg $OPTARG $ZEROADMIN)
		[[ -n "$POLICY" ]] && usage 2 "Conflicting policies specified"
		POLICYSET=true
		IS_POLICY_CHANGE=true
		;;
	(g)	SERVERSELECT=true
		;;
	(m)	TERMINALGROUP=true
		IS_POLICY_CHANGE=true
		;;
	(M)	MOBILE=true
		IS_POLICY_CHANGE=true
		;;
	(p)	SRPASSWORD=false
		IS_POLICY_CHANGE=true
		;;
	(P)	POLICY=$OPTARG
		[[ -n "${DB:-}" || -n "${SELFREG:-}" || -n "${ZEROADMIN:-}" ]] ||
		    usage 3 "Conflicting policies specified"
		;;
	(S)	ASCCARDS="$ASCCARDS $OPTARG"
		ASC=true
		IS_POLICY_CHANGE=true
		POLICYSET=true
		;;
	(h)	usage 0
		;;
	(\?)
#		shift $(( $OPTIND - 2 ))
		if [[ "$1" != "-?" ]]
		then
			usage 4 "Invalid option: $1";
		else
			usage 20
		fi
	esac
done


if $ASC
then
    ASCCARDS=$(print $ASCCARDS | sed 's/,/ /'g)
    for card in $ASCCARDS
    do
	ACCEPTASCCARDS="$ACCEPTASCCARDS -a $card"
	REJECTASCCARDS="$REJECTASCCARDS -r $card"
    done
fi

#
# If nothing is to be applied and it's not a test, print a warning message and exit.
#
if  ! $APPLY
then
    applyWarning
    exit 0
fi

#
# if there is only -a but no policy, complain and exit
#
if [[ "${APPLY}" == true ]]
then
	if [[ $POLICYSET == false ]]
	then
		Error 1 "Nothing to apply. Please specify policy."
	fi
fi

ISDSGROUP=$(checkdsgroup)

#
# Check to see if this host is part of a failover configuration.
# If it is then it should print a warning if the -g option was
# not specified on the command line.
# The output of utreplica -l will be the
# guide in deciding whether the server is part of a failover
# group or not.
#
if $IS_POLICY_CHANGE && $ISDSGROUP && ! $SERVERSELECT
then
	print -u2 "WARNING: -g option must be used in a failover configuration."
fi

#
# See if policy is being constructed and set POLICY
# When we talk about POLICY, only stuff set by -r, -s -z are checked.
# Setting Terminal Id using -t flag should be part of the POLICY as well tho'.
#
if [[ -n "${DB:-}" || -n "${SELFREG:-}" || -n "${ZEROADMIN:-}"
    || -n ${ASCCARDS:-} || -n ${MOBILE:-} || -n ${EXITENABLE:-} ]]
then
	if [[ -n "$POLICY" ]]
	then
		usage 5 "P flag cannot be used with -r, -s, or -z flags"
	fi
	if [[ -n "${SELFREG:-}" ]]
	then
		POLICY="$POLICY,$SELFREG=register"
	fi
	if [[ -n "${DB:-}" ]]
	then
		POLICY="$POLICY,$DB=db"
	fi
	if [[ -n "${ZEROADMIN:-}" ]]
	then
		POLICY="$POLICY,$ZEROADMIN=default"
	fi
	if [[ ${ASC} == true ]]
	then
		POLICY="$POLICY,asc=true"
	fi
	if [[ ${MOBILE} == true ]]
	then
		POLICY="$POLICY,mobile=true"
	fi
	if [[ ${EXITENABLE} == false ]]
	then
		POLICY="$POLICY,exitenable=false"
	fi
fi

#
# POLICY could be specified on command line or read from a file when using -f option.
# But they cannot both be used.

# On command line user specifies using -s -r or -z flags
if [[ -n "$POLICY" ]] 
then
	#
	# Translate the policy specification into "key=value, key2=value2, key3=value3 ..."
	# Remove the prefixing "," from POLICY
	#

	POLICY=${POLICY#,}
	#
	# See if the policy makes sense. Complain if it doesn't.
	#
	validatepolicy $POLICY
fi


#
# Now apply the new policy if -a was on the command line
# Config files that are affected: utpolicy, auth.props, terminals (only if -t is used)
#
if $APPLY
then
	READEROPTS=`$UTREADER -p 2>/dev/null`
	if [[ $? -ne 0 ]]
        then
                Error 1 "Could not update policy.  Please refer to \
			/var/opt/SUNWut/log/messages for more details."
        fi
	$UTGLPOLICY $@ 2>/dev/null 1>&2
	if [[ $? -ne 0 ]]
	then
		Error 1 "Could not update policy"
	fi
	if [[ -n $READEROPTS ]]; then
		# Enable utglpolicy to update policy before calling utreader
		synchwrite "$@"
		$UTREADER $READEROPTS 2>/dev/null 1>&2
		if [[ $? -ne 0 ]]
        	then
                	Error 1 "Could not update policy.  Please refer to \
				/var/opt/SUNWut/log/messages for more details."
        	fi
	fi
	#
	# Edit the AUTHPROPS file 

	editAuthProps policy=$(basename $POLICYFILE)

	restartinstructions
	
	# print current policy to /var/opt/SUNWut/log/messages
	logger -p user.info -t UTPOLICY -- `eval showcurrentpolicy`

	# when invoked with -G we want some additional output on stdout
	[[ ! -z ${UT_GLOBALRUN:-} ]] && showcurrentpolicy
fi

# Done. Good bye.
exit 0
