#!/bin/ksh -p
#
# ident "$Id$ SMI"
#
# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

# Sanitize environment
PATH=/usr/sbin:/usr/bin:/bin
export PATH

# Set up program name
#PROGNAME=`basename $0`
# This should (always?) be invoked through the kioskuseradm wrapper
# TODO: add an option to pass in the name to use 
#       (spoofing $0 does not seem to work)
PROGNAME="kioskuseradm"

## Load utilities and setup basic Kiosk Environment
#
theUtilsFile=/opt/SUNWkio/lib/utils.sh
if [ ! -r $theUtilsFile ] ; then
    print -u2 "Error: can't read Kiosk utils file '$theUtilsFile'"
    exit 1
else
    . $theUtilsFile
    if [ $? -ne 0 ] ; then
	print -u2 "Error: failed to load Kiosk utils file '$theUtilsFile'"
	exit 1
    fi 
fi

# Some defaults for user management settings that are not set in utils.sh
KIOSK_USER_DEFAULT_COMMENT=KioskSessionServiceUser
KIOSK_USER_DEFAULT_HOME=$KIOSK_VAR_DIR/home
KIOSK_USER_DEFAULT_SHELL=/bin/sh

# Global locations and settings used in this file
kiosk_userconfigdir=${KIOSK_VAR_DIR:?}/users
kiosk_userconfig=${kiosk_userconfigdir}/kioskuser.conf
kiosk_usergroup=${kiosk_userconfigdir}/kioskuser.grp
kiosk_userlist=${kiosk_userconfigdir}/kioskuser.list
kiosk_homedirskel=${kiosk_userconfigdir}/skel

kiosk_userdyndir=${KIOSK_DYN_DIR:?}/users

kiosk_usercomment="${KIOSK_USER_COMMENT:-$KIOSK_USER_DEFAULT_HOME}"
kiosk_userhome=${KIOSK_USER_HOME:-$KIOSK_USER_DEFAULT_HOME}
kiosk_usershell=${KIOSK_USER_SHELL:-$KIOSK_USER_DEFAULT_SHELL}

# init some variables
prefix=""
new_prefix=
usergroup=""
new_group=
stored_gid=""
new_gid=
start=""
new_start=
count=""
new_count=
selection=""  
force_delete=false
terse_output=false
verbose_output=false
debug=""  

if [ `uname` = "Linux" ] ; then
        NAWK=gawk
	RMTREE="rmdir -p --ignore-fail-on-non-empty" 
else
        NAWK=nawk
	RMTREE="rmdir -ps"  
fi

function short_usage {
	cat >&2 << !    
Usage: $PROGNAME <subcommand> <options>
       $PROGNAME -h
!
	exit 1
}

function show_usage {
	cat >&2 << !    
Usage: $PROGNAME show [-p]
       $PROGNAME create [-q]   -l <prefix>   -g <group> [-i <gid>|auto] 
                               -u <first>    -c <count>
       $PROGNAME modify [-fq] [-l <prefix>] [-g <group> [-i <gid>|auto] ] 
                              [-u <first>]  [-c <count>]
       $PROGNAME extend [-q]   -c <count>
       $PROGNAME delete [-fq]
       $PROGNAME leakcheck [-p]
       $PROGNAME cleanup [-q]
       $PROGNAME status [-pv]
       $PROGNAME help
       $PROGNAME -h
!
}

function usage {
	typeset theExitCode=${1:-1}
	show_usage
	exit $theExitCode
}

function help {       
	show_usage
	cat >&2 << !    

Subcommands:
       show       print the current configuration.
       create     create <count> user accounts for kiosk use. 
                  The accounts are be created starting at uid <first> 
                  as members of group <group> , with user names of 
                  the form <prefix><number> where 0 <= number < count.
                  The kiosk accounts are identified by a 'gecos' field 
                  containing "$kiosk_usercomment".
       modify     move the range of uids - same as 'delete' followed by 'create'
       extend     extend the current range of uids by <count> new accounts
       delete     delete the currently configured accounts.
       leakcheck  search for any "$kiosk_usercomment" accounts left over 
                  from a prior configuration.
       cleanup    force removal of accounts listed by 'leakcheck'.
       status     print number of configured and currently used user accounts.
       help       print this help message.

Options:
       -f         force deleting existing user accouts, even if there are 
                  active sessions.
       -p         print parseable output, not human readable text
       -q         quiet: no output for regular operation
       -v         verbose: show extra output
       -h         show short help.

Operands:
       The permitable ranges for the arguments are:
       <prefix>   up to four letters
       <first>    any unused uid > 100
       <count>    a number >= 1
!
}

#
# Search /etc/passwd for leaked accounts
# (Returns all accounts when $prefix is not set
#
function find_lost_accounts {
	if [ -n "$debug" ]; then set -x; fi 


	# Extract all users from /etc/passwd which are not in
	# the configured range of users <prefix>n 0<=n<count
	# but do use the KioskSessionServiceUser comment field.
	# All users which match $KIOSKUSERCOMMENT, but do not belong
	# to the current range of configured users appear to be left over
	# from earlier Kiosk configurations.
	if [ "$prefix" != "" ]; then
		$NAWK -F':' -v "C=$kiosk_usercomment" -v P=$prefix -v R=$count '{
                	if ( C == $5 ) {
				if ( match( $1, P ) == 1 ) {
                                	len=length(P);
                                	num=substr($1,len+1);
                                	inum=int(num);
                                	if ( match(num,/[0-9]+/) != 1 ||
                                     	inum < 0 || inum >= int(R) )
                                        	print $1 
                       		} else {
					print $1
				}
			}
		}' /etc/passwd	
	else
		$NAWK -F':' -v "C=$kiosk_usercomment" '{ 
			if ( C == $5 ) { 
				print $1
			}
		}' /etc/passwd
	fi
}

#
# end all processes of this user
# $1 user
# $2 [optional] signal to use
#
function kill_all_procs {
	if [ -n "$debug" ]; then set -x; fi 
		
	sigopt=${2:-}

	# For now we only send SIGTERM
	# (do we need more?)    
	pkill $sigopt -U $1 
}

#
# end kiosk session of this user
# $1 user
# $2 timeout (in seconds) to wait for the session to finish
#    (optional, default=30s)
#
function kill_session {
	if [ -n "$debug" ]; then set -x; fi 

	typeset user=$1
		
	$terse_output || print -n "Killing session for user $user "
	if launchCriticalAppForUser "$PROGNAME" "$user" /bin/true 2>/dev/null 
	then    
	    	# Wait for session to end
	    	retries=${2:-30}

		# Allow session to release its user reservation
		disable_config_lock
            	while has_active_session "$user" && [ $retries -gt 0 ] 
		do	    
			$terse_output || print -n -- "-"	
	    		sleep 1	
			(( retries-=1 ))	
		done
		restore_config_lock

		if [ $retries -ne 0 ] ; then
			sleep 1 # extra time for the display manager to complete logout
			$terse_output || print -- "- done"	
		else
			$terse_output || print -- "- incomplete"	
		fi		
	else
		$terse_output || print -- "- failed"	
        fi		
	if has_active_session "$user" ; then
	    $terse_output || print -u2 "Notice: could not kill session for user $user."
	fi    
}

# 
# Check if a range of user accounts is available to be useradd'ed 
# ignoring a certain other range, which may be overlapping, intersection 
# or empty
# $1 name prefix (or "")
# $2 first account number of range
# $3 number of accounts
# $4 first account number of ignored range
# $5 number of ignored accounts
# $6 type of identifier ("account" or "id")
# return code: 
#    0  all the accounts are available
#    1  some of the accounts are not available
#    2  an error occurred
#
function check_accounts_available_except {

	if [ -n "$debug" ]; then set -x; fi 

	typeset prefx="$1"
	typeset start=$2
	typeset numaccts=$3
	typeset ign_start=$4
	typeset ign_numaccts=$5
	typeset accttype=$6
	
	typeset end=`expr $numaccts + $start`
	typeset ign_end=`expr $ign_numaccts + $ign_start`
	
	typeset cnt=$start
	typeset accts=
	typeset all_accts=
	
	# Catch case where there is nothing to check
	if [ $ign_start -le $start ] && [ $ign_end -ge $end ] ; then 
		return 0
	fi
	if [ $end -le $start ] ; then
		print -u2 "Warning: trying to check empty range."
		return 0
	fi	

	$terse_output || print "Validating new user ${accttype}s."
	all_accts=` while [ $cnt -lt $end ]; do
			if ! [ $ign_start -le $cnt -a $cnt -lt $ign_end ] ; then
				echo "$prefx$cnt"		
			fi
			(( cnt+=1 ))
		done`
        if [ -z "$all_accts" ] ; then
	        # This should have been caught above, so it probably indicates
	        # an error in the while loop
		print -u2 "Error: Could not determine accounts to check."
		return 2
	fi

	accts=`getent passwd $all_accts`

	typeset res=$?	
	
	if [ -n "$accts" ] ; then
		print -u2 "Some requested user ${accttype}s are in use."	
		if ! $terse_output ; then
			echo "$accts" | 
			awk -F: '{ print "- " $1 " (uid=" $3 ") - " $5 }' >&2
		fi
		return 1
	elif [ $res -ne 2 -a $res -ne 0 ] ; then
		print -u2 "Error: Validating user ${accttype}s failed."
		return 2
	else
		return 0
	fi	
}

# 
# Check if a range of user accounts is available to be useradd'ed 
# $1 name prefix (or "")
# $2 first account number of range
# $3 number of accounts
# $4 type of identifier ("account" or "id")
# return code: 
#    0  all the accounts are available
#    1  some of the accounts are not available
#    2  an error occurred
#
function check_accounts_available {
	check_accounts_available_except "$1" $2 $3 0 0 $4
}

# 
# Check if a range of user accounts is available to be useradd'ed 
# $1 user name prefix
# $2 first account number (0...) of range
# $3 first uid of range
# $4 number of accounts
# return code: 
#    0  all the accounts are available
#    1  some of the accounts are not available
#    2  an error occurred
#
function user_accounts_available {

	if [ -n "$debug" ]; then set -x; fi 

	typeset prefx=$1
	typeset start=$2
	typeset startuid=$3
	typeset numaccts=$4
	
# Check uid availability
	check_accounts_available "" $startuid $numaccts id

	typeset res1=$?

# Check user name availability
	check_accounts_available "$prefx" $start $numaccts account

	typeset res2=$?

	if [ $res1 -eq 0 -a $res2 -eq 0 ] ; then
		return 0
	elif [ $res1 -eq 1 -o $res2 -eq 1 ] ; then
		return 1
	else
		return 2
	fi		
}


# 
# Check if a range of user accounts is available to be useradd'ed 
# Don't consider another range, as it may be an exception
#
# $1 user name prefix
# $2 first account number (0...) of range
# $3 first uid of range
# $4 number of accounts
# $5 existing user name prefix to be ignored
# $6 first existing account number to be ignored
# $7 first uid of existing range to be ignored
# $8 number of existing accounts to be ignored
# return code: 0  all the accounts are available
#
function user_accounts_available_except {

	if [ -n "$debug" ]; then set -x; fi 

	typeset prefx=$1
	typeset start=$2
	typeset startuid=$3
	typeset numaccts=$4
	typeset ign_prefx=$5
	typeset ign_start=$6
	typeset ign_startuid=$7
	typeset ign_numaccts=$8
	
# Check uid availability
	check_accounts_available_except "" $startuid $numaccts \
					   $ign_startuid $ign_numaccts id

	typeset res1=$?

# Check user name availability
	# Here collisions can only happen if the prefixes are the same
	# fixme: we don't handle the case where the new prefix
	# has the form <oldprefix><digits> or vice versa
	if [ "$prefx" = "$ign_prefx" ] ; then
		check_accounts_available_except "$prefx" $start $numaccts \
						   $ign_start $ign_numaccts account
	else
		check_accounts_available "$prefx" $start $numaccts account
	fi	

	typeset res2=$?

	if [ $res1 -eq 0 -a $res2 -eq 0 ] ; then
		return 0
	elif [ $res1 -eq 1 -o $res2 -eq 1 ] ; then
		return 1
	else
		return 2
	fi		
}

#
## Create/check the user group for kiosk users
# $1 group
# $2 group-id
#
function create_group
{
	if [ -n "$debug" ]; then set -x; fi 

	typeset grp=$1
	typeset grpid=${2:-existing}

	rm -f "$kiosk_usergroup"
	if ! getent group $grp > /dev/null 2>&1
	then	
		typeset ga_opts=""
		case "$grpid" in
	            auto) 	
			;;
	            legacy) 
			grpid=auto 
			;;
	            existing) 
			print -u2 "Error: Group $grp does not exist."
			return 1
			;;
		    *)	
			if getent group $grpid > /dev/null 2>&1 
			then 	
				print -u2 "Error: group-id '$grpid' is already in use."
				return 1
			fi
			ga_opts="-g $grpid"
			;;
		esac

		$terse_output || 
			print "Creating kiosk group $grp"	
		groupadd $ga_opts "$grp" &&
			print -- "$grp $grpid" > "$kiosk_usergroup"
	else
		if [ "$grpid" != "existing" -a "$grpid" != "legacy" ]
		then
			print -u2 "Error: Group $grp does not exist."
			return 1
		fi
	fi		
}

#
## Delete the user group if appropriate
# $1 group
# $2 group-id 
#
function delete_group
{
	if [ -n "$debug" ]; then set -x; fi 

	typeset grp=$1
	typeset grpid=${2:-keep}

	if [ "$grpid" != "keep" -a "$grpid" != "existing" ] ; then
		$terse_output || 
			print "Deleting kiosk group $grp"	
		groupdel "$grp"
	fi		
}

#
## Check, if we can change the group as desired
# $1 new group
# $2 new group id
# $3 old group
# $4 old group id
#
function can_change_group
{
	if [ -n "$debug" ]; then set -x; fi 

	typeset grp=$1
	typeset grpid=$2
	typeset oldgrp=$3
	typeset oldgrpid=$4

	$terse_output || print "Validating group-id"
	if [ "$oldgrp" != "$grp" ] ; then
		# A real change is requested
		typeset grp_exists=false
		if getent group $grp > /dev/null 2>&1 
		then
			grp_exists=true
		fi	

		case "$grpid" in
		    auto) 
# Require that grp does not exist	    
			if $grp_exists ; then
				print -u2 "Error: Group $grp already exists."
				return 1
			fi		
			;;
		    legacy) 
# old-style: use auto, if needed    
			;;
		    existing) 
			if ! $grp_exists ; then
				print -u2 "Error: Group $grp does not exist."
				return 1
			fi	
			;;
		    keep)
			print -u2 "Internal Error: invalid automatic group-id"
			print -u2 "Error: Cannot change kiosk group"
			return 1
			;;	
		    *)	
			if [ "$oldgrpid" = "$grpid" ] ; then	    
				print -u2 "Error: Cannot reuse current kiosk group-id for new kiosk group."
				return 1
			fi
			if getent group $grpid > /dev/null 2>&1 
			then 	
				print -u2 "Error: group-id '$grpid' is already in use."
				return 1
			fi
			;;
		esac
	# Our parameter parsing should have ensured that we don't see 
	# $grpid='existing' here
	elif [ "$oldgrpid" != "$grpid" ] &&
		[ "${grpid:-keep}" != "keep" ] && 
		[ "$grpid" != "legacy" ]
	then	
		# New group-id requested for same group?
		typeset ogid="group-id '$oldgrpid'"
		if [ "$oldgrpid" = "keep" ] ; then
			ogid="preexisting"
		fi		
		print -u2 "Cannot alter kiosk group $grp ($ogid) to group-id '$grpid'"  
		return 1
	fi		
	# All checks passed
	return 0
}

#
# create/prepare the directories necessary to add a Kiosk user
# $1 group
# $2 group-id 
#
function create_directories {
	if [ -n "$debug" ]; then set -x; fi 

	typeset grp=$1
	typeset grpid=$2

	# first ensure config dir exists	
	if [ ! -d "$kiosk_userconfigdir" ] ; then
		mkdir -p "$kiosk_userconfigdir" || return 1
	fi
	chmod 755 "$kiosk_userconfigdir" || return 1

	# now ensure the kiosk group exists
	create_group "$grp" "$grpid" || return 1

	chgrp $grp $kiosk_userconfigdir  || return 1
	
	if [ ! -d "$kiosk_userhome" ] ; then
		mkdir -p "$kiosk_userhome" || return 1
	fi
	chgrp $grp $kiosk_userhome  || return 1
	chmod 750 "$kiosk_userhome" || return 1

	# Generally this should be a freshly created 
	# (and thus empty) 'skeleton'
	if [ ! -d "$kiosk_homedirskel" ] ; then
		mkdir -p "$kiosk_homedirskel" || return 1
	fi
	chgrp $grp $kiosk_homedirskel  || return 1
	chmod 700 "$kiosk_homedirskel" || return 1

	return 0
}

#
# adjust the directories and group for a Kiosk user
# $1 new group
# $2 new group id
# $3 old group
# $4 old group id
#
function adjust_directories {
	if [ -n "$debug" ]; then set -x; fi 

	typeset grp=$1
	typeset grpid=$2
	typeset oldgrp=$3
	typeset oldgrpid=$4
	typeset delgroup=false

	# first ensure config dirs exists	
	if [ ! -d "$kiosk_userconfigdir" ] ; then
		print -u2 "Missing directory: $kiosk_userconfigdir"  
		return 2
	fi
	if [ ! -d "$kiosk_userhome" ] ; then
		print -u2 "Missing directory: $kiosk_userhome"  
		return 2
	fi
	if [ ! -d "$kiosk_homedirskel" ] ; then
		print -u2 "Missing directory: $kiosk_homedirskel"  
		return 2
	fi

	# now ensure the new kiosk group exists
	if [ "$grp" != "$oldgrp" ] ; then
		create_group "$grp" "$grpid" "$oldgrpid"
		if [ $? -ne 0 ] ; then
			return 1
		fi		
		delgroup=true
	fi		

	# Now adjust the directories
	chgrp $grp $kiosk_userconfigdir  || return 1
	chmod 755 "$kiosk_userconfigdir" || return 1
	
	chgrp $grp $kiosk_userhome  || return 1
	chmod 750 "$kiosk_userhome" || return 1

	chgrp $grp $kiosk_homedirskel  || return 1
	chmod 700 "$kiosk_homedirskel" || return 1

	# remove the old group, if we created it
	if $delgroup ; then
		delete_group "$oldgrp" "$oldgrpid" 
	fi		

	return 0
}

#
# remove the directories used by kiosk user management
# $1 old kiosk group
# $2 old kiosk group id
#
#
function remove_directories {
	typeset oldgrp="$1"
	typeset oldgrpid="$2"

	$terse_output ||  print "Removing configuration directories."

	#  Remove the group file to allow fdirectory removal
	rm -f "$kiosk_usergroup"

    	rmdir $kiosk_homedirskel ||
		print -u2 "Failed to remove directory $kiosk_homedirskel"
	rmdir $kiosk_userhome ||   
		print -u2 "Failed to remove directory $kiosk_userhome"
	rmdir $kiosk_userconfigdir ||   
		print -u2 "Failed to remove directory $kiosk_userconfigdir"
	rm -rf $kiosk_userhome    

	rm -f $kiosk_userdyndir/* 
	$RMTREE $kiosk_userdyndir
	
	# remove the group, if we created it
	delete_group "$oldgrp" "$oldgrpid" 

	# evaluate success
	[ ! -d $kiosk_userconfigdir ] &&
	[ ! -d $kiosk_userhome ] &&
	[ ! -d $kiosk_userdyndir ]
}

#
# add a user to /etc/passwd, create home dir
# $1 - user to create
# $2 - uid to create
# $3 - group for the user
# return exit code of useradd
#
function do_useradd {
	if [ -n "$debug" ]; then set -x; fi 

	typeset user=$1
	typeset uid=$2
	typeset grp=$3
	typeset home=$kiosk_userhome/$user

	/usr/sbin/useradd -u $uid -g $grp -c "$kiosk_usercomment" \
			  -m -k "$kiosk_homedirskel" \
			  -s "$kiosk_usershell" -d "$home" $user 2>/dev/null
}

#
# delete a user from /etc/passwd, remove home dir
# $1 - user to delete
# return exit code of userdel
#
function do_userdel {

	if [ -n "$debug" ]; then 
		set -x 
		typeset -f -t cleanup
	fi 

	typeset user=$1
	typeset homedir=`getent passwd "$user" | cut -d':' -f 6`

	# Get rid of running processes, tmp directories, etc	 
	cleanup $PROGNAME $user "$homedir" 2>/dev/null

	if [ -d "$homedir" ]; then
		/usr/sbin/userdel -r $user 2>/dev/null
	else
		/usr/sbin/userdel $user 2>/dev/null
	fi

	typeset res=$?
	if [ $res -ne 0 ] ; then
		return $res
	fi

        # Cleanup any leaked session management files 
	usersession="$kiosk_userdyndir/$user.session"
	if [ -f "$usersession" ] ; then
		typeset userdpy=$(cat "$usersession" 2>/dev/null)
		[ -z "$userdpy" ] || rm -f "$kiosk_userdyndir/$userdpy.dpy"
		rm -f "$usersession"
	fi
	return 0	
}


# 
# create a range of users in /etc/passwd 
# $1 user name prefix
# $2 user group name
# $3 first uid of range
# $4 number of users to create
# $5 number of preexisting users (default 0)
# return code: 0  ok
# return code: 11 partially failed
# return code: 12 failed, no user created and no user conf file written
#
function do_create_users {

	if [ -n "$debug" ]; then set -x; fi 

	typeset res=0
	typeset grp=$2
	typeset start=${5:-0}
	typeset uid=`expr $3 + $start`
	typeset cnt=$start
	typeset end=`expr $4 + $start`

	$terse_output || echo "Configuring new kiosk user accounts:"
	while [ $cnt -lt $end ]; do
	
		user=${1}$cnt
		home=$kiosk_userhome/$user

		do_useradd $user $uid $grp
		if [ $? -ne 0 ]; then
			$terse_output || print 
			print -u2 "Creating Kiosk user account $user failed"
			res=11
			break;
		fi
		$terse_output || print -n "."
		(( uid+=1 ))
		(( cnt+=1 ))
	done
	$terse_output || print
	if [ $cnt != $start ]; then
		echo "$1 $2 $3 $cnt" > $kiosk_userconfig    
		chmod 644 $kiosk_userconfig
		$terse_output || echo "$cnt users configured"
	else
		$terse_output || echo "no new users configured"
		res=12
	fi
	return $res
}

# 
# create a range of users in /etc/passwd 
# $1 user name prefix
# $2 user group name
# $3 first uid of range
# $4 number of users to create
# $5 gid for group creation
# return code: 0  ok
# return code: 11 partially failed
# return code: 12 failed, no user created and no user conf file written
# return code: 13 failed, requested range not available
# return code: 14 failed, could not create user config directories
#
function create_new_user_config {

	if [ -n "$debug" ]; then set -x; fi 

	typeset prf=$1   
	typeset grp=$2   
	typeset start=0
	typeset firstuid=$3 
	typeset startuid=$firstuid
        typeset numaccts=$4	
	typeset gid=$5

	if ! user_accounts_available "$prf" $start $startuid $numaccts ; then
	    return 13	
	fi

	if ! create_directories $grp $gid ; then
	    print -u2 "Setting up configuration directories failed"
	    return 14	
	fi
		
	do_create_users "$prf" "$grp" "$firstuid" "$numaccts" "$start"
}

# 
# create a range of users in /etc/passwd 
# $1 user name prefix
# $2 user group name 
# $3 first uid of entire configuration
# $4 number of users to create
# $5 number of preexisting users 
# return code: 0  ok
# return code: 11 partially failed
# return code: 12 failed, no user created and no user conf file written
# return code: 13 failed, requested range not available
# return code: 14 failed, could not create user config directories
#
function extend_user_config {

	if [ -n "$debug" ]; then set -x; fi 

	typeset prf="$1"   
	typeset grp=$2   
	typeset start=$5
	typeset firstuid=$3
	typeset startuid=`expr $firstuid + $start`
        typeset numaccts=$4	

	if ! user_accounts_available "$prf" $start $startuid $numaccts ; then
	    return 13	
	fi

	do_create_users "$prf" "$grp" "$firstuid" "$numaccts" "$start"
}

#
# deletes a range of users from /etc/passwd
# $1 user name prefix
# $2 first uid to use
# $3 number of users to delete
# return code: 0  ok
# return code: 21 removing of users partially failed
# return code: 22 failed, no user deleted, user config left in place 
#
function do_delete_users {

	if [ -n "$debug" ]; then set -x; fi 

	typeset res=0
        typeset cnt=0
	typeset rmv=0
	typeset prefix=$1
	typeset start=$2
	typeset count=$3

	# first disable access by renaming $kiosk_userconfig
	mv $kiosk_userconfig $kiosk_userconfig.do_delete
	if [ $? -ne 0 ]; then
	    	print -u2 "Cannot unconfigure user accounts."
		$terse_output || echo "No accounts deleted"
		return 22
	fi    	
	
	# remove old tmp user list
	# Ignore failure - at most this will result in reporting extra failures
	rm -f $kiosk_userlist.tmp_delete

	$terse_output || echo "Unconfiguring kiosk user accounts:"
	line_open=false
        while [ $cnt -lt $count ]; do

                typeset user=${prefix}$cnt
		typeset comment="`getent passwd $user | cut -d':' -f 5`" 

		if [ "$comment" = "$kiosk_usercomment" ]; then

		    	# Still have a session running"
			if has_active_session "$user" ; then
				$line_open && print
				line_open=false
				kill_session $user
			fi	

                	do_userdel $user
                	if [ $? -ne 0 ]; then
				$line_open && print
				line_open=false
                        	print -u2 "removing passwd entry failed for $user"
				echo $user >> $kiosk_userlist.tmp_delete
				res=21
			else
				(( rmv+=1 ))
				if ! $terse_output ; then	
					print -n "."
					line_open=true	
				fi
			fi
		else
			$line_open && print
			line_open=false
			print -u2 -n "passwd entry for $user does not "
			print -u2    "match \"$kiosk_usercomment\", not deleted!"
		fi
                (( cnt+=1 ))
        done
	$line_open && print
	rm -f $kiosk_userconfig.do_delete

	if [ $res -ne 0 ] ; then
	    if ! $terse_output ; then
		print "Could not delete the following user accounts:"
		cat $kiosk_userlist.tmp_delete
		print "Use the 'cleanup' subcommand to remove the accounts later."
		print
	    fi
	    rm -f $kiosk_userlist.tmp_delete
	fi
 
	$terse_output || echo "$rmv of $count users deleted"
	return $res
}

#
# deletes kiosk user accounts and configuration
# $1 user name prefix
# $2 group name
# $3 first uid 
# $4 number of users 
# $5 gid
# return code: 0  ok
# return code: 21 removing of users partially failed
# return code: 22 failed, no user deleted, user config left in place 
# return code: 23 failed to remove user configuration directories
# return code: 24 both errors 21 and 23 occurred
#
function delete_user_config {

	if [ -n "$debug" ]; then set -x; fi 

	typeset res=0

	if are_sessions_active ; then
	    	if ! $force_delete ; then
			print -u2 "Cannot delete kiosk user configuration: there are active sessions."
			return 22
		fi
		print -u2 "Warning: there are active kiosk sessions."
	fi
			
	do_delete_users "$1" $3 $4
        res=$?

	if [ $res -eq 22 ] ; then return $res ; fi
	
	if ! remove_directories "$2" "$5" ; then
		if [ $res -eq 0 ] ; then 
			res=23
		else
			res=24
		fi
		$terse_output ||  print -u2 "Removing configuration directories or groups failed"
	fi

	return $res	
}

#
# Moves a range of users in /etc/passwd
# $1 current user prefix
# $2 current group name
# $3 current uid start
# $4 current count
# $5 user name prefix
# $6 group name
# $7 first uid to use
# $8 number of users to create
# $9 existing group gid
# $10  new gid
# return code: 0  ok
# return code: 1X 2X return codes from create or delete
#
function modify_user_config { 
	typeset oldprf="${1}"
	typeset oldgrp="${2}"
	typeset olduid="${3}"
	typeset oldcnt="${4}"
	typeset newprf="${5}"
	typeset newgrp="${6}"
	typeset newuid="${7}"
	typeset newcnt="${8}"
	typeset oldgid="${9}"
	typeset newgid="${10}"

	if [ -n "$debug" ]; then set -x; fi 

	if ! can_change_group $newgrp "$newgid" $oldgrp "$oldgid" ; then
	    return 22	
	fi

	if are_sessions_active ; then
	    	if ! $force_delete ; then
			print -u2 "Cannot replace kiosk user configuration: there are active sessions."
			return 22
		fi
		print -u2 "Warning: there are active kiosk sessions."
	fi
			
	if ! user_accounts_available_except "$newprf" 0 $newuid $newcnt \
		                            "$oldprf" 0 $olduid $oldcnt 
	then
	    return 13	# see create_new_user_config
	fi

	typeset res=0
	do_delete_users "$oldprf" $olduid $oldcnt
	res=$?
	if [ $? -eq 0 ]; then
		if ! adjust_directories $newgrp "$newgid" $oldgrp "$oldgid" ; then
		    print -u2 "Setting up configuration directories or groups failed"
		    return 14	
		fi
		do_create_users $newprf $newgrp $newuid $newcnt
		res=$?
	else
		$terse_output || echo "failed to delete current user range"
	fi
	return $res
}

#
# test for users in /etc/passwd left over from earlier configurations
# return code: 0  no stale users found
# return code: 31 stale users found
#
function test_users {
	if [ -n "$debug" ]; then set -x; fi 

	typeset res=0

	typeset lost_accounts=`find_lost_accounts`
	if [ -n "$lost_accounts" ]; then
		$terse_output || print "Stale Kiosk user entries:"
                print -- "$lost_accounts"
		res=31
        else
		$terse_output || print "No lost Kiosk user accounts found"
	fi
	return $res
}

#
# remove users from /etc/passwd, which have been leftover from earlier
# attempts of delete_users.
# return code: 0  removed all stale users
# return code: 41 remove partially failed
#
function remove_users {
	if [ -n "$debug" ]; then set -x; fi 

	typeset res=0

	typeset lost_accounts=`find_lost_accounts`
	if [ -n "$lost_accounts" ]; then
		typeset tmp_remove=/tmp/kiosk.tmp_remove.$$

		rm -f $tmp_remove
		for user in $lost_accounts; do

			kill_all_procs $user -KILL
			do_userdel $user 
                        if [ $? -ne 0 ]; then
                                print -u2 "Removing Kiosk user account $user failed"
                                echo $user >> $tmp_remove
				res=41
			else
				$terse_output || print "Removed account: $user"
                        fi  
		done
		if [ res -ne 0 ]; then
		        if ! $terse_output ; then
			        print
				print "Failed to remove:"
				cat $tmp_remove
			fi
			rm -f $tmp_remove
		fi
	fi
	return $res
}

#
# Test if there are any session currently active for configured users
#
# $1 current user prefix
function are_sessions_active {
	if [ -n "$debug" ]; then set -x; fi 

	typeset sessions="$(ls $kiosk_userdyndir/${1}*.session 2>/dev/null1)"
	test -n "$sessions"
}

#
# Test if there is a session currently active for the given user
#
# $1 user name
function has_active_session {
	if [ -n "$debug" ]; then set -x; fi 

	user=$1	
	test -f "$kiosk_userdyndir/$user.session"
}		

#
# count users currently configured and users currently being used
# $1 current user prefix
# $2 number of users configured
# return code: 0  counting was successful
# return code: 51 something went wrong
#
function count_users {
	if [ -n "$debug" ]; then set -x; fi 


	typeset MAXUSER=$2
	typeset USERPREFIX=$1

	typeset cnt=0
	typeset used=0
	while [ $cnt != $MAXUSER ]; do 
		typeset user=${USERPREFIX}$cnt
		if has_active_session "$user" ; then
		    	if $verbose_output ; then
				dpy="$(cat $kiosk_userdyndir/$user.session)"
			    	if $terse_output ; then
					echo "$user $dpy"
				else
					echo "$user has a session on display ${dpy:-<none>}"
				fi	
			fi		

			((used+=1))
		fi
		((cnt+=1))
	done
	if $terse_output ; then
		# Show totals only, if not verbosely listing sessions
		$verbose_output || print "$MAXUSER $used"
	else	
		$verbose_output && [ $used -ne 0 ] &&
		    print -- "--------------------------------------------"
	    	print "$used of $MAXUSER kiosk accounts are in use."
	fi

	return 0
}

#
## disable_config_lock, restore__config_lock
##  
## Moves away and restore the kiosk lock file, if it exists, to allow
## external kiosk user operations to complete.
## Automatic restoration of the lock is attempted, if possible
#
kiosk_userlock=$kiosk_userdyndir/usercfg.lock
kiosk_userlockdisabled=$kiosk_userdyndir/usercfg.unlocked
kiosk_trapsignals="0 TERM INT QUIT HUP ABRT"
function disable_config_lock
{
	if [ -f "$kiosk_userlockdisabled" ] ; then
		print -u2 -n "Warning: cannot unlock session: "
		print -u2    "\"$kiosk_userlockdisabled \" exists."
		return 1
	fi	
	if ! mv $kiosk_userlock $kiosk_userlockdisabled ; then
		print -u2 "Error: unlocking session failed."
		return 1	
	fi		
	trap restore_config_lock $kiosk_trapsignals
}

function restore_config_lock
{
	mv $kiosk_userlockdisabled $kiosk_userlock 2>/dev/null
	trap - $kiosk_trapsignals
}


function isInteger
{
	[[ "$1" = +([0-9]) ]]
}
#
# check_args, check argument validity
# $1 Are arguments required
#
function check_args {
	if [ -n "$debug" ]; then set -x; fi 

    	typeset argsRequired="${1}"
	shift

	check_no_more_args $*

	if $argsRequired ; then
		if [ -z "$new_prefix" ] || [ -z "$new_group" ] || 
		   [ -z "$new_start" ] || [ -z "$new_count" ] 
		then
			print -u2 "Error: missing mandatory options"
			usage    
		fi
	fi	

# TODO: Improve validity tests for prefix/groupname/...
	if [ -z "${new_prefix:=$prefix}" ] ||
	   [ ${#new_prefix} -gt 4 ]	
	then	   
		print -u2 "Error: Invalid prefix '$new_prefix'"
		usage
	fi

	if [ -z "${new_group:=$usergroup}" ] 
	then	   
		print -u2 "Error: Invalid group '$new_group'"
		usage
	fi

	if ! isInteger "${new_start:=$start}" || [ $new_start -lt 100 ]	
	then	   
		print -u2 "Error: Invalid first user id '$new_start'"
		usage
	fi

	if ! isInteger "${new_count:=$count}" ||
	   [ $new_count -lt 1 -o $new_count -gt 9999 ]	
	then	   
		print -u2 "Error: Invalid number of accounts '$new_count'."
		usage
	fi

	case "${new_gid:-NOT_SET}" in
	auto) 	;;
	legacy)	;;
	NOT_SET) 
		if [ "$new_group" = "$usergroup" ] ; then
			new_gid=${stored_gid:-keep}
		else
			new_gid=existing		
		fi
		;;
	+([0-9]))	
		if [ $new_gid -lt 1 ] ; then
			print -u2 "Error: Invalid group '$new_group'"
			usage
		fi
		;;
	*)	print -u2 "Error: Invalid group '$new_group'"
		usage
		;;
	esac
}

#
# check_arg1, check for acceptable range extension
# $1 possibly legacy count to be added
#
function check_arg1 {
	if [ -n "$debug" ]; then set -x; fi 

	check_no_more_args $*

	if [ -n "${new_prefix}${new_group}${new_gid}${new_start}" ]
	then
		print -u2 "Invalid options for $selection command."	
		usage
	fi
	if [ -z "$new_count" ]; then
		print -u2 "Error: missing mandatory count option."
		usage
	fi	
	if ! isInteger "$new_count" ; then
		print -u2 "Error: invalid number '$new_count'."
		usage 
	fi
	if [ $new_count -lt 1 -o $new_count -gt 9999 ]; then
		print -u2 "Error: extension count out of range."
		usage 
	fi
	
	typeset newtotal=`expr ${count:-0} + $new_count`
	if [ $newtotal -lt 1 -o $newtotal -gt 9999 ]; then
		print -u2 "Error: New number of accounts ($newtotal) out of range."
		usage 
	fi
}

#
# check_arg0, check for acceptable options
# for non-create command
#
function check_arg0 {
	check_no_more_args $*

	if [ -n "${new_prefix}${new_group}${new_gid}${new_start}" ]
	then
		print -u2 "Invalid options for $selection command."	
		usage
	fi
}

#
# check_no_more_args
#
# Verify that there are no extraneous operands on the command line 
#
function check_no_more_args {
	if [ -n "$debug" ]; then set -x; fi 

	if [ $# -ne 0 ]; then
		print -u2 "Error: extra arguments found."
		usage
	fi
}

#
# main starts here
#

# Parse global options
while getopts D:h opt; do
	case $opt in
	D)	# Debug Mode
		echo "$0: entering debug mode" >&2
		debug=$OPTARG
		[ $OPTARG = '-' ] || exec 2> $OPTARG
		set -x
		;;
	h)	usage 0
		;;	
	\?)	short_usage
		;;
	esac
done
shift `expr $OPTIND - 1`

# if we got here, a subcommand is required
if [ $# -eq 0  ] ; then
	short_usage
fi

selection="$1"
shift

# Parse subcommand options
OPTIND=1
while getopts l:g:i:u:c:fpqhv opt; do
	case $opt in
	l)	new_prefix=$OPTARG
		;;
	g)	new_group=$OPTARG
		;;
	i)	new_gid=$OPTARG
		;;
	u)	new_start=$OPTARG
		;;
	c)	new_count=$OPTARG
		;;
	f)	force_delete=true
		;;	
	p|q)	terse_output=true
		;;	
	v)	verbose_output=true
		;;	
	h)	usage 0
		;;	
	\?)	usage 
		;;
	esac
done
shift `expr $OPTIND - 1`

if [ -s $kiosk_userconfig ]; then
	read prefix usergroup start count < $kiosk_userconfig

	if [ -s "$kiosk_usergroup" ] ; then		
		read stored_group stored_gid < "$kiosk_usergroup"
		if [ "$stored_group" != "$usergroup" ]
		then
			print -u2 "Warning: Invalid kiosk group configuration found."
			print -u2 "         Kiosk group will not be deleted."
#rm -f "$kiosk_usergroup"
			stored_group=keep
		fi	
	fi
fi
	

res=2
case $selection in
	show)
		# just print configuration (if any)
		check_arg0 $*

		res=0
		if [ "$prefix" = "" ]; then
			$terse_output || echo "No kiosk user accounts configured..."
			res=2
		elif ! $terse_output ; then	
			echo "Current kiosk user account settings:"
			echo "  user name prefix:   $prefix"
			echo "  first account uid:  $start"
			echo "  number of accounts: $count"
			echo "  kiosk group name:   $usergroup"
			[ -n "$stored_gid" ] && 
			echo "  kiosk group gid:    $stored_gid"
				
		else
			# Format allows invoking "kioskuseradm create <output>"
			if [ -n "$stored_gid" ] ; then
				grp_opt="-g $usergroup -i $stored_gid"
			else	
				grp_opt="-g $usergroup"
			fi
			echo "-l $prefix $grp_opt -u $start -c $count"
		fi
		;;
	create)	
		check_args true $*
		if [ "$prefix" = "" ]; then
			create_new_user_config $new_prefix $new_group \
			    			$new_start $new_count $new_gid
			res=$?
		else
			print -u2 "$PROGNAME: Cannot create new configuration. Kiosk user accounts already configured."
		        $terse_output || print -u2 "Use the modify or extend subcommands to change configuration."
		fi
		;;
		
	modify)	
		if [ "$prefix" != "" ]; then
			check_args false $*
			modify_user_config $prefix $usergroup $start $count \
			    			$new_prefix $new_group \
                                                $new_start $new_count \
						${stored_gid:-keep} \
						$new_gid
			res=$?
		else
			print -u2 "$PROGNAME: Cannot $selection: no accounts configured..." 
		fi
		;;
		
	extend)	
		check_arg1 $*
		if [ "$prefix" != "" ]; then
			extend_user_config $prefix $usergroup $start \
						$new_count $count 
			res=$?
		else
			print -u2 "$PROGNAME: Cannot $selection: no accounts configured..." 
		fi
		;;
		
	delete)	
		check_arg0 $*
		if [ "$prefix" != "" ]; then
			delete_user_config $prefix $usergroup $start $count \
						${stored_gid:-keep} 
		        res=$?
                else
			print -u2 "$PROGNAME: Cannot $selection: no accounts configured..." 
                fi 
		;;
		
	leakcheck)
		check_arg0 $*
		test_users
		res=$?
		;;
		
	status)
		check_arg0 $*
		if [ "$prefix" != "" ]; then
			count_users $prefix $count	
			res=$?
		else
			echo "No accounts configured..."  
			res=1
		fi
		;;
		
	# do_kill_session subcommand: debug facility, not visible in usage	
	# needed to debug session kill with wait
	do_kill_session)
		theUser=$1
		if has_active_session $theUser; then
                        kill_session $theUser ${2:-}
		else	
			print -u2 "There is no session for user '$theUser'."
                fi 
		res=$?
		;;
	
	cleanup)
		check_arg0 $*
		remove_users
		res=$?
		;;
	
	help)
		if [ $# -ne 0 ] || $terse_output; then
                        usage
                fi 
		help
		res=$?
		;;
	
	*)	usage ;;
esac

exit $res
                                    
