#!/bin/ash
# Fatdog64 User Manager
# Copyright (C) James Budiono 2012
#
# License: GNU GPL Version 3 or later
#
# Note: the bits in Global Inits, which is part of GTK-server bash init code
#       is (C) Peter van Eerten. Everything else is (C) James Budiono
#
# Note: Group management has not been done yet.
#
#131106 L18L internationalization

# std localisation stanza
export TEXTDOMAIN=fatdog
. gettext.sh
# performance tweak - use "C" if there is no localisation
! [ -e $TEXTDOMAINDIR/${LANG%.*}/LC_MESSAGES/$TEXTDOMAIN.mo ] &&
! [ -e $TEXTDOMAINDIR/${LANG%_*}/LC_MESSAGES/$TEXTDOMAIN.mo ] && LANG=C


### configuration
APPTITLE="$(gettext 'Fatdog64 User Manager')"
SYSTEM_MANAGED_USERS="root spot"	# system-managed users cannot be deleted.
DEFAULT_PASSWORD=woofwoof		# default password for new user
AUTOLOGIN_CONFIG=/etc/autologin	# control file for autologin

[ $(id -u) -ne 0 ] && exec gtksu "$APPTITLE" "$0"

### runtime variables
GDK_TYPE_PIXBUF=
MAIN_WINDOW=

# userstore tree model
UT_USERID=0

# groupstore tree model
# not implemented yet

#---------------------------------------------------------- Global Inits
### global variables for proper GTK-server
GTK= NULL="NULL"
PIPE=/tmp/gtk.bash.$$
trap 'stop-gtk-server -fifo $PIPE; exit' HUP TERM INT # stop GTK-server in case of an error

### start GTK-server
gtk-server -fifo=$PIPE &
while [ ! -p $PIPE ]; do sleep 1; continue; done

### GTK-server helper functions
# Assignment function
define() { $2 $3 $4 $5 $6 $7 $8 $9; eval $1=\'"$GTK"\'; }

# Communicate with GTK-server
gtk()
{
/bin/echo "$@" > $PIPE
read GTK < $PIPE
}

#---------------------------------------------------------- GUI realization

# add GTK+ API not defined in gtk-server.cfg
add_missing_gtk_api() {
	gtk gtk_server_define gdk_pixbuf_get_type NONE INT 0
	gtk gtk_server_define gtk_label_set_markup NONE NONE 2 WIDGET STRING
	
	# add GTK+ API not defined in gtk-server.cfg
	gtk gtk_server_define gtk_builder_new NONE WIDGET 0
	gtk gtk_server_define gtk_builder_add_from_file NONE INT 3 WIDGET STRING NULL
	gtk gtk_server_define gtk_builder_add_from_string NONE INT 4 WIDGET STRING INT NULL
	gtk gtk_server_define gtk_builder_get_object NONE WIDGET 2 WIDGET STRING
	gtk gtk_server_define gtk_builder_connect_signals NONE NONE 2 WIDGET NULL	
	
	gtk gtk_server_define gtk_tree_view_get_cursor NONE NONE 3 WIDGET PTR_WIDGET NULL
	
	# hack for getting unlimited string length and leak-free operation with gtk_tree_get_model
	# see below
	gtk gtk_server_define strcat NONE STRING 2 POINTER STRING
	gtk gtk_server_define memcpy NONE NONE 3 PTR_LONG POINTER INT			
}

### helper - leak-free, unlimited string length from gtk_tree_model_get with unpatched gtk-server
# $1-tree $2-iter $3-index $4-result name
# static variable to speed up stuff
STATIC_BUF=
STATIC_ADDRESS=
tree_model_get_string() {
	[ -z "$STATIC_BUF" ] && define STATIC_BUF gtk malloc 32 # gtk-server MAX_DIG is 32 bytes
	
	gtk gtk_server_define gtk_tree_model_get NONE NONE 5 WIDGET WIDGET INT POINTER INT # ensure correct definition
	gtk gtk_tree_model_get $1 $2 $3 $STATIC_BUF -1
	
	gtk memcpy NULL $STATIC_BUF 32	# print address stored in $BUF --> *buf
	STATIC_ADDRESS=$GTK
	if [ $STATIC_ADDRESS -ne 0 ]; then
		gtk strcat $STATIC_ADDRESS \"\" # get string point by address --> **buf	
		eval $4=\'"$GTK"\'				# this will be our return value	
		gtk free $STATIC_ADDRESS		# this was dynamically alloc-ed by gtk_tree_model_get, now free it
	else
		eval $4=\"\"
	fi
}

### helper - refresh the content of userview by loading it from /etc/group
refresh_userview() {
	local ITER
	define ITER gtk gtk_server_opaque
	
	gtk gtk_list_store_clear $USERSTORE
	gtk gtk_list_store_append $USERSTORE $ITER NULL
	gtk gtk_list_store_set $USERSTORE $ITER $UT_USERID root -1 NULL -1
	for p in $(awk -F: '$1 == "users" {gsub(/,/,"\n",$4); print $4}' /etc/group); do
		gtk gtk_list_store_append $USERSTORE $ITER NULL
		gtk gtk_list_store_set $USERSTORE $ITER $UT_USERID $p -1 NULL -1
	done
	
	gtk free $ITER
}

### helper - get the currently selected userid
# returns SELECTED_USERID
get_selected_user() {
	local ITER SELECTED_PATH
	define ITER gtk gtk_server_opaque
	
	define SELECTED_PATH gtk gtk_tree_view_get_cursor $USERVIEW NULL
	if [ $SELECTED_PATH -ne 0 ]; then
		gtk gtk_tree_model_get_iter $USERSTORE $ITER $SELECTED_PATH	
		gtk gtk_tree_path_free $SELECTED_PATH

		# $1-tree $2-iter $3-index $4-result name
		tree_model_get_string $USERSTORE $ITER $UT_USERID SELECTED_USERID
	fi
	
	gtk free $ITER
}

### helper - returns true if the user is managed by system
# $1-user
is_system_managed() {
	for p in $SYSTEM_MANAGED_USERS; do
		case $p in $1) 
			return 0
		esac
	done
	return 1
}

### signal handler - update and display user details info
#$1-userid
update_user_details() {
	local THIS_USER
	THIS_USER="$1"
	local msg=$(awk -v user=$1 -F: '
	BEGIN { ORS="" }
	$1 == user {
		sub(/,.*/,"",$5)
		print $5 "\\n\\n"
		print "uid: " $3 "\\n"
		print "gid: " $4 "\\n"
		print "shell: " $7 "\\n"
		print "home: " $6 "\\n"		
	}
' /etc/passwd
)
	if ps -o user -C freemem.sh | grep -qm 1 $1; then
		if [ $USER = $1 ]; then
			msg="$msg\n$(gettext 'Owner of this desktop.')\n"
		else
			msg="$msg\n$(eval_gettext '${THIS_USER} has a desktop running in the background.')\n"
		fi
	fi
	gtk gtk_label_set_markup $USER_DETAILS_FRAME_LABEL \""<b>$1</b>"\"	
	gtk gtk_label_set_markup $USER_DETAILS_LABEL \""$msg"\"
	gtk gtk_widget_show $USER_DETAILS_FRAME	
}

### signal handler - create new user
add_new_user() {
	local newid
	if newid=$(Xdialog --title "$APPTITLE" --stdout --inputbox "$(gettext 'Please enter the userid. It must not be the same as existing ones.')" 0 0); then
		newid=$(echo $newid | sed 's/[^A-Za-z0-9._-]*//g')	# remove all funny characters
		check=$(awk -F: -v user="$newid" '$1 == user {print $1}' /etc/passwd)
		if [ "$check" ]; then
			Xdialog --title "$(gettext 'Error!')" --infobox "$(eval_gettext '$newid already exist. Please choose another userid.')" 0 0 10000
		else
			mkuser.sh $newid
			echo -e "$DEFAULT_PASSWORD\n$DEFAULT_PASSWORD\n" | passwd -a md5 $newid
			refresh_userview
			Xdialog --title "$(gettext 'Success!')" --infobox "$(eval_gettext 'User $newid is created. Password is set to $DEFAULT_PASSWORD.')" 0 0 10000
		fi
	fi
}

### signal handler - delete user
#$1-userid
del_user() {
	local THIS_USER
	THIS_USER="$1"
	if is_system_managed $1; then
		Xdialog --title "$(gettext 'Error!')" --msgbox "$(eval_gettext "${THIS_USER} is a system-managed user, it cannot be deleted.")" 0 0 10000
		return	
	fi

	if [ $USER = $1 ]; then
		Xdialog --title "$(gettext 'Error!')" --msgbox "$(gettext 'You cannot delete yourself.')" 0 0 10000
		return	
	fi
	
	local homedir
	homedir=$(awk -v user=$1 -F: '$1 == user { print $6 }' /etc/passwd)
	if Xdialog --title "$APPTITLE" --yesno "$(eval_gettext "Do you really want to remove user $THIS_USER? 
All files in ${THIS_USER}'s home directory ($homedir) will be deleted.")" 0 0; then
		rmuser.sh $THIS_USER
		refresh_userview
		gtk gtk_widget_hide $USER_DETAILS_FRAME
	fi
    #' (this line is just to satisfy geany highlighting)
}

### signal handler - set password for user
#$1-userid
set_password() {
	local newpwd
	local THIS_USER
	THIS_USER="$1"
	if newpwd=$(Xdialog --title "$APPTITLE" --stdout --password --inputbox "$(gettext 'Enter new password (blank means no password required)')" 0 0); then
		newpwd=$(echo $newpwd) # cheap way to remove leading/trailing spaces
		if [ "$newpwd" ]; then
			echo -e "$newpwd\n$newpwd\n" | passwd -a md5 $1
			Xdialog --title "$APPTITLE" --infobox "$(eval_gettext "${THIS_USER}' s password is updated successfully.")" 0 0 10000
		else
			Xdialog --title "$APPTITLE" --infobox "$(eval_gettext "${THIS_USER}'s password is removed - everyone can login as ${THIS_USER} without password.")" 0 0 10000
			passwd -d $1
		fi
	fi
}

### signal handler - launch desktop as a specified user
# $1-user
launch_desktop_as_user() {
	local FGCONSOLE="$(fgconsole)"
	local THIS_USER
	THIS_USER="$1"
	local disp pid tty
	[ $USER = $1 ] && return
	
	# check if user is already running a desktop
	if ! pid=$(ps -o pid=,user= -C freemem.sh | grep $1); then 
		for disp in $(seq 1 10); do
			[ ! -e /tmp/.X${disp}-lock ] && break
		done
		Xdialog --title "Attention!" --infobox "$(eval_gettext "About to launch a secondary desktop, running as ${THIS_USER}.") 

$(gettext 'You can switch between desktops using Ctrl-LeftAlt-Fx, where x=4,5,6 etc.') 
$(eval_gettext 'Use Ctrl-LeftAlt-F${FGCONSOLE} to switch back to this desktop.')

$(gettext 'To terminate the secondary desktop, choose "Exit X server" or "Restart X server" from the menu.')" 0 0 10000
		su - $1 -c "/bin/bash -l -c \"xinit -- :${disp}\"" &
	else
		Xdialog --title "$(gettext 'Attention!')" --infobox "$(eval_gettext "Switching to ${THIS_USER}'s desktop.") 
$(eval_gettext 'Use Ctrl-LeftAlt-F${FGCONSOLE} to switch back to this desktop.')" 0 0 10000
		pid=$(echo ${pid% *})
		disp=$(cat /proc/$pid/environ | tr '\000' '\n' | sed '/^DISPLAY/ !d; s/^.*=//')
		tty=$(ps -o tty=,args= -C X,Xorg | sed "/$disp/ !d; s/ .*\$//")
		[ -z $tty ] && 	tty=$(ps -o tty=,args= -C X,Xorg | sed "/:/ d; s/ .*\$//")
		tty=${tty#tty}
		chvt $tty
	fi
    #' (this line is just to satisfy geany highlighting)
}

################### main ###################

### localisation
[ -z $TEXTDOMAINDIR ] && TEXTDOMAINDIR=/usr/share/locale
[ -z $TEXTDOMAIN ] && TEXTDOMAIN=fatdog64
gtk gtk_server_define setlocale NONE STRING 2 INT STRING
gtk gtk_server_define bindtextdomain NONE STRING 2 STRING STRING
gtk gtk_server_define textdomain NONE STRING 1 STRING
gtk setlocale 6 \"\" # 6 corresponds to LC_ALL
gtk bindtextdomain $TEXTDOMAIN $TEXTDOMAINDIR
gtk textdomain $TEXTDOMAIN

### init gui
add_missing_gtk_api
gtk gtk_init
define GDK_TYPE_PIXBUF gtk gdk_pixbuf_get_type

define BUILDER gtk gtk_builder_new
define RESULT gtk gtk_builder_add_from_file $BUILDER "${0}.xml"
gtk gtk_builder_connect_signals $BUILDER

define MAIN_WINDOW gtk gtk_builder_get_object $BUILDER main_window
define CLOSE_BUTTON gtk gtk_builder_get_object $BUILDER close-button

define USER_DETAILS_FRAME gtk gtk_builder_get_object $BUILDER user-details-frame
define USER_DETAILS_LABEL gtk gtk_builder_get_object $BUILDER user-details-label
define USER_DETAILS_FRAME_LABEL gtk gtk_builder_get_object $BUILDER user-details-frame-label

define ADD_USER_BUTTON gtk gtk_builder_get_object $BUILDER add-user-button
define DELETE_USER_BUTTON gtk gtk_builder_get_object $BUILDER delete-user-button
define SET_PASSWORD_BUTTON gtk gtk_builder_get_object $BUILDER set-password-button
define AUTOLOGIN_AS_USER_BUTTON gtk gtk_builder_get_object $BUILDER autologin-as-user-button
define LAUNCH_DESKTOP_AS_USER_BUTTON gtk gtk_builder_get_object $BUILDER launch-desktop-as-user-button

define USERVIEW gtk gtk_builder_get_object $BUILDER userview
define USERSTORE gtk gtk_builder_get_object $BUILDER userstore

# connect signals
gtk gtk_server_connect $MAIN_WINDOW delete-event quit
gtk gtk_server_connect $CLOSE_BUTTON clicked quit
gtk gtk_server_connect $USERVIEW cursor-changed userview-cursor-changed
gtk gtk_server_connect $ADD_USER_BUTTON clicked add-user
gtk gtk_server_connect $DELETE_USER_BUTTON clicked del-user
gtk gtk_server_connect $SET_PASSWORD_BUTTON clicked set-password
gtk gtk_server_connect $AUTOLOGIN_AS_USER_BUTTON clicked autologin-user
gtk gtk_server_connect $LAUNCH_DESKTOP_AS_USER_BUTTON clicked launch-desktop

# preparation stuff
refresh_userview

### gtk main loop
gtk gtk_widget_show_all $MAIN_WINDOW
gtk gtk_widget_hide $USER_DETAILS_FRAME
! grep -q autologin /etc/inittab && gtk gtk_widget_hide $AUTOLOGIN_AS_USER_BUTTON
while true; do 
	define EVENT gtk gtk_server_callback wait
	case $EVENT in
		quit)
			break ;;
			
		userview-cursor-changed) 
			get_selected_user 
			update_user_details $SELECTED_USERID ;;
			
		add-user) 
			add_new_user ;;
			
		del-user)
			get_selected_user
			del_user $SELECTED_USERID ;;
			
		set-password)
			get_selected_user
			set_password $SELECTED_USERID ;;
			
		autologin-user)
			get_selected_user
			echo $SELECTED_USERID > $AUTOLOGIN_CONFIG 
			chmod 0600 $AUTOLOGIN_CONFIG # root only
			Xdialog --title "$APPTITLE" --infobox "$(eval_gettext 'At next reboot the system will auto-login as $SELECTED_USERID')" 0 0 10000
			;;
			
		launch-desktop)
			get_selected_user
			launch_desktop_as_user $SELECTED_USERID
			;;
			
	esac
done
gtk gtk_server_exit 
