#!/bin/bash
##
## Author: Lorenzo Canovi
## create/modify users
#
CMD=$(basename $0)
CMDVER="1.15"
CMDSTR="$CMD v$CMDVER (2023-07-27)"

EXECUTE=true



# (FUNCTIONS)

# compara i dati passati con quelli contenuti in /etc/passwd
# (tutti tranne la password), ritorna ok se non ci sono differenze,
# non ok ed elenco differenze su stdout se ce ne sono
#
compare_user()
{
	local name=$1
	local uid=$2
	local gid=$3
	local gecos=$4
	local home=$5
	local shell=$6

	pwent=$(grep "^$name:" /etc/passwd)
	[ "$pwent" = "" ] && {
		echo "new"
		return 1
	}
	local puid=$(	echo "$pwent" | cut -d: -f3)
	local pgid=$(	echo "$pwent" | cut -d: -f4)
	local pgecos=$(	echo "$pwent" | cut -d: -f5)
	local phome=$(	echo "$pwent" | cut -d: -f6)
	local pshell=$(	echo "$pwent" | cut -d: -f7)

	groupname=$(grep ":$pgid:" /etc/group | cut -d: -f1)

	[ "$groupname" != "" ] && pgid=$groupname

	changes=""
	[ "$puid" != "$uid" ]		&& changes="$changes uid=$puid"
	[ "$pgid" != "$gid" ]		&& changes="$changes gid=$pgid"
	[ "$pgecos" != "$gecos" ]	&& changes="$changes gecos"
	[ "$phome" != "$home" ]		&& changes="$changes home"
	[ "$pshell" != "$shell" ]	&& changes="$changes shell"

	[ "$changes" != "" ] && {
		echo $changes
		return 1
	}
	return 0
}


usage()
{
	echo "$@
usage: $CMD [-c classconfig_file] [-r num_retry]
		class uid:logname[:home:shell:group:gecos] ...

. fields home, shell, group and gecos are optional but positional
  (use :: for an empty field, eg for shell use uid:logname::shell)
. empty fields will be filled using the classes definitions
. no spaces allowed! __ will be replaced by a single space (eg: for gecos)
. many -c options can be specified (order take precedence)
. classconfig_file is now: '$USERTYPES_CONFIG'
"
	for i in $USERTYPES_CONFIG
	do
		[ -f$i ] && {
			classes=$(sed -e 's/:.*//' -e 's/#.*//' $i | \
				sort -u)
			echo ". classes from $i: " $classes >&2
		}
	done
	exit 1
}


expandmacros()
{
	sed -e "s/@SELF@/$NAME/g" \
		-e "s/@GROUP@/$GROUP/g" \
		-e "s#@DATADIR@#$KUSA_PATH_DATADIR#g" \
		-e "s#@ARCDIR@#$KUSA_PATH_ARCDIR#g" \
		-e "s#@DBDIR@#$KUSA_PATH_DBDIR#g" \
		-e "s#@GITDIR@#$KUSA_PATH_GITDIR#g" \
		-e 's/__/ /g'
}


local_fs()
{
	local done_dir=/tmp/done_dir.$$.tmp
	local done_dev=/tmp/done_dev.$$.tmp

	cp /dev/null $done_dir
	cp /dev/null $done_dev

	cat /proc/mounts | while read dev dir type parms
	do
		if [ $dir != "/" ]
		then
			case $type in
				ext*|reiserfs|xfs) ;; # OK
				*) continue ;;
			esac
		fi
		grep -q "^$dir$" $done_dir && continue
		grep -q "^$dev$" $done_dev && continue
		[ -d $dir ] && echo $dir	# may not exists, if in chroot jail
		echo $dir >>$done_dir
		echo $dev >>$done_dev
	done

	rm -f $done_dir $done_dev
}





# (MAIN)

users=
class=
RETRY=0

while [ $# != 0 ]
do
	case $1 in
		-c)	shift
			[ $# = 0 ] && usage
			[ -f "$1" ] || usage "not a file: '$1'"
			USERTYPES_CONFIG="$USERTYPES_CONFIG $1"
			;;
		-r)	shift
			[ $# = 0 ] && usage
			RETRY="$1"
			;;
		-n)	EXECUTE=false
			;;
		*:*)	users="$users $1"
			;;
		*)	[ "$class" != "" ] && usage
			class=$1
			;;
	esac
	shift
done

[ "$USERTYPES_CONFIG" = "" ] && usage "errore: -c user_classes_file required"

[ "$class" = "" ] && usage
[ "$users" = "" ] && usage

classdef=

for classfile in $USERTYPES_CONFIG
do
	classdef=$(grep "^$class:" $classfile) && break
done

[ "$classdef" = "" ] && {
	echo "errore: classe '$class' non trovata in $USERTYPES_CONFIG" >&2
	rm -f $classfile
	exit 1
}

export NAME GROUP
export KUSA_PATH_ARCDIR KUSA_PATH_DATADIR KUSA_PATH_GITDIR KUSA_PATH_DBDIR

KUSA_PATH_DATADIR=${KUSA_PATH_DATADIR:-$(kusa-conf path.datadir)}	|| exit $?
KUSA_PATH_ARCDIR=${KUSA_PATH_ARCDIR:-$(kusa-conf path.arcdir)}		|| exit $?
KUSA_PATH_DBDIR=${KUSA_PATH_DBDIR:-$(kusa-conf path.dbdir)}		|| exit $?
KUSA_PATH_GITDIR=${KUSA_PATH_GITDIR:-$(kusa-conf path.gitdir)}		|| exit $?

# no login user class?
#
lock_password=false
echo "$class" | grep -q "nologin" && lock_password=true

pass=0
status=0

while :
do
    [ $pass -le $RETRY ] || break

    pass=$(expr $pass + 1)

    [ $pass -gt 1 -a $status = 0 ] && break
    [ $pass -gt 1 -a $RETRY != 0 ] && echo -e "\n* RETRY $pass\n"

    status=0

    for user in $users
    do
	[ $status != 0 -a $RETRY = 0 -a $EXECUTE = true ] && exit 1

	uid=$(echo "$user" | cut -d: -f1 | expandmacros)
	NAME=$(echo "$user" | cut -d: -f2 | expandmacros)
	home=$(echo "$user" | cut -d: -f3 | expandmacros)
	shell=$(echo "$user" | cut -d: -f4 | expandmacros)
	GROUP=$(echo "$user" | cut -d: -f5 | expandmacros)
	gecos=$(echo "$user" | cut -d: -f6 | expandmacros)

	home=${home:-$(echo "$classdef" | cut -d: -f3 | expandmacros)}
	shell=${shell:-$(echo "$classdef" | cut -d: -f4 | expandmacros)}
	GROUP=${GROUP:-$(echo "$classdef" | cut -d: -f2 | expandmacros)}
	gecos=${gecos:-$(echo "$classdef" | cut -d: -f5 | expandmacros)}

	groups=$(echo "$classdef" | cut -d: -f6 | expandmacros)

	[ "$groups" != "" ]		&& groups="-G $groups"
	[ "$GROUP" == "@SELF@" ]	&& GROUP=$NAME

	echo "$uid$NAME$home$shell$GROUP$gecos" | grep -q '@[A-Z][A-Z]*@' && {
		echo "error, unresolved variable @...@ in line: $user" >&2
		exit 1
	}

	# controlla se l'utente esiste, e se si se deve modificare qualcosa
	#
	result=$(compare_user $NAME $uid $GROUP "$gecos" $home $shell) && continue


	if [ "$result" = new ]
	then
		printf " usr %-12s %-30s " $NAME "NEW"
		command="useradd"
	else
		printf " usr %-12s %-30s " $NAME "mod $result"
		command="usermod"
	fi

	grep -q "^$GROUP:" /etc/group || {
		echo "ERR group not found: $GROUP" >&2
		status=1
		continue
	}

	# se cambia uid, controllo che non sia gia` presente
	# in /etc/passwd
	#
	echo $result | grep -q "uid=" && {
		cut -d: -f3 /etc/passwd | grep -q "^$uid$" && {
			echo "ERR requested uid $uid already in /etc/passwd" >&2
			status=1
			continue
		}
	}

	$EXECUTE || command="echo $command"

	$command -c "$gecos" -d $home -g $GROUP -u $uid \
			-s $shell $groups $NAME || {
		status=1
		continue
	}
	$EXECUTE && {
		echo ok
		$lock_password && {
			usermod --lock $NAME || {
				status=1
				continue
			}
		}
	}

	# fix: se cambiato uid deve riassegnare ownership ai files
	#
	echo $result | grep -q "uid=" && {
		puid=$(echo $result | sed -e 's/.*uid=//' -e 's/ .*//')
		files=xx
		fs=$(local_fs)
		echo -n "		  fixing uid from $puid to $uid on " $fs " ... "
		$EXECUTE && {
			files=$(find $fs -xdev -user $puid -print -exec verbatim_chown $uid {} \; | wc -l)
		}
		echo "$files files/dirs"
	}

   done
done

exit $status

