#!/bin/sh

alterator_api_version=1

default_groups="cdwriter cdrom audio video proc radio camera floppy xgrp scanner uucp users"
default_groups_file=/usr/share/install3/default-groups
default_groups_dir=/usr/share/install3/default-groups.d
default_faces_dir=/usr/share/design/current/faces
alterator_images_dir=/usr/share/alterator/design/images
alterator_faces_dir=$alterator_images_dir/faces
accountsservice_icons_dir=/var/lib/AccountsService/icons
userface_name="userface.icon"
default_userface=".face.icon"
cachedir=

BLANK_AVATAR="theme:null"

if [ -n "$TMPDIR" ]; then
    cachedir="$TMPDIR/alterator-users"
else
    cachedir="/var/cache/alterator/alterator-users"
fi

##
# $ALTERATOR_DESTDIR - exported by installer.
#
# XXX: works only for: "add new user"
#

DESTDIR="/"
CHROOT_EXE=""
KIOSK_PROFILES_DIR="/etc/kiosk"

if [ -d "${ALTERATOR_DESTDIR:-}" ]; then
    DESTDIR="$ALTERATOR_DESTDIR"
    CHROOT_EXE="chroot $DESTDIR"
fi

#turn off auto expansion
set -f

. alterator-sh-functions
. shell-quote
. autologin-sh-functions
. shell-ini-config

UID_MIN=$(grep '^UID_MIN' /etc/login.defs 2>/dev/null|sed -r 's,UID_MIN[[:space:]]+,,')
[ -z "$UID_MIN" ] && UID_MIN=500

# create a file to access the directory with avatars
[ -d "$default_faces_dir" -a ! -e "$alterator_faces_dir" ] && \
    ln -s $default_faces_dir $alterator_faces_dir ||:

## lowlevel user helpers

local_getent()
{
    local re="${2:-.*}"
    grep "^$re:" "/etc/$1"
}

is_defined()
{
	set |grep -qs "^$1="
}

user_args()
{
	local args=
	[ -n "$in_home" ] && args="$args -m -d \"$(quote_shell "$in_home")\""
	[ -n "$in_shell" ] && args="$args -s \"$in_shell\""
	(test_bool "$in_iscrypted" && [ -n "$in_passwd_1" ]) && args="$args -p \"$(quote_shell "$in_passwd_1")\""
	echo "$args"
}

user_write_error()
{
    local msg="$(printf "$@")"
    write_error "$msg"
    return 1
}

user_write_retcode()
{
	case "$1" in
		1) write_error "`_ "can't update password file"`" ;;  #'
		2) write_error "`_ "invalid command syntax"`" ;; 
		3) write_error "`_ "invalid argument to option"`" ;; 
		4) write_error "`_ "uid already in use"`" ;; 
		6) write_error "`_ "specified user doesn't exist"`" ;; #'
		8) write_error "`_ "user currently logged in"`" ;;
		9) write_error "`_ "username already in use"`" ;;
		10) write_error "`_  "can't update group file"`" ;; #'
		12) write_error "`_ "can't create or remove home directory"`" ;; #'
		13) write_error "`_ "can't create mail spool"`" ;; #'
		*) write_error "retcode=$1" ;;
	esac
	return "$1"
}

user_chpasswd()
{
    echo "$1:$2"| $CHROOT_EXE /usr/sbin/chpasswd ||
	user_write_error "`_ "cannot change password"`"
}

user_new()
{
    $CHROOT_EXE /usr/sbin/useradd "$@" ||
	user_write_retcode "$?"
}

user_write()
{
    /usr/sbin/usermod "$@" ||
	user_write_retcode "$?"
}

name_write()
{
    $CHROOT_EXE /usr/bin/chfn -f "$@"
}

user_passwd()
{
    local password=
    if test_bool "$in_auto" && [ -n "$in_passwd_auto" ]; then
	password="$in_passwd_auto"
	elif test_bool "$in_iscrypted"; then
		return 0
	elif [ -n "$in_passwd_1" -o -n "$in_passwd_2" ] ; then
	if [ "$in_passwd_1" != "$in_passwd_2" ]; then
	    write_error "`_ "Passwords mismatch"`"
	    return 1
	else
	    password="$in_passwd_1"
	fi
    fi
    echo "$password"
    return 0
}

## lowlevel group helpers

group_write_retcode()
{
	case "$1" in
		2) write_error "`_ "invalid command syntax"`" ;;
		3) write_error "`_ "invalid group name"`" ;;
		4) write_error "`_ "gid not unique"`" ;; 
		6) write_error "`_ "specified group doesn't exist"`" ;;
		8) write_error "`_ "can't remove user's primary group"`" ;; #'
		9) write_error "`_ "group name not unique"`" ;;
		10) write_error "`_ "can't update group file"`" ;; #'
		*) write_error "retcode=$1" ;;
	esac
	return "$1"
}

group_new()
{
    $CHROOT_EXE /usr/sbin/groupadd -f -r "$1" 2>/dev/null ||
	group_write_retcode "$?"
}

group_include()
{
    group_new "$1" || return 1

    $CHROOT_EXE /usr/bin/gpasswd -a "$2" "$1" >/dev/null ||
	user_write_error "`_ "unable to add user %s to group %s"`" "$2" "$1"
}

merge_groups()
{
    printf "%s\n%s\n" "$1" "$2" | sed -r -e '/^[[:blank:]]*$/d' -e 's|[[:blank:]]+|\n|g' | sort -u
}

group_include_default()
{
    local i= installer_def_groups= installer_add_groups=

    if [ -s "$DESTDIR/$default_groups_file" ]; then
        installer_def_groups="$(cat "$DESTDIR/$default_groups_file")"
    fi

    if [ -d "$DESTDIR/$default_groups_dir" ]; then
        local f=
        for f in $(find "$DESTDIR/$default_groups_dir" -maxdepth 1 -type f 2>/dev/null); do
            installer_add_groups="$installer_add_groups${installer_add_groups:+ }$(cat "$f")"
        done
    fi

    if [ "$($CHROOT_EXE control libnss-role status 2>/dev/null)" = enabled ]; then
        if [ -n "$installer_def_groups" ] || [ -n "$installer_add_groups" ]; then
            # FIlter out groups which in the users role already
            local users_role="$($CHROOT_EXE rolelst -S users | sed -e 's/^users://' -e 's/,/\n/g' | sort)"
            local l="$(merge_groups "$installer_def_groups" "$installer_add_groups")"

            set +o posix
            default_groups="users $(comm -23 <(echo "$l") <(echo "$users_role") | grep -v '^users$')"
            set -o posix
        else
            default_groups="users"
        fi
    else
        if [ -n "$installer_def_groups" ]; then
            default_groups="$installer_def_groups"
        fi

        if [ -n "$installer_add_groups" ]; then
            default_groups="$(merge_groups "$default_groups" "$installer_add_groups")"
        fi
    fi

    for i in $default_groups; do
        group_include "$i" "$1" || return 1
    done
}

group_exclude()
{
    $CHROOT_EXE /usr/bin/gpasswd -d "$2" "$1" >/dev/null
}

sys_role_exclude()
{
    strings=$(rolelst -S)

    while IFS= read -r str
    do
        str=${str%:*}
        #remove spaces at the beginning and end of a line
        str="$(echo -e "${str}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
        group_exclude "$str" "$1"
    done <<< $strings
}

### high level procedures

list_shell()
{
    while read sh; do
	[ -x "$sh" ] || continue
	write_enum_item "$sh"
    done </etc/shells
    write_enum_item "/sbin/nologin"
}

list_account_()
{
    local_getent passwd|
    while IFS=':' read name password uid gid gecos home shell; do
        [ "$uid" -ge "$UID_MIN" ] || continue
	[ "$shell" == "/sbin/nologin" ] || grep -qs "^$shell$" /etc/shells || continue
	[ -x "$shell" ] || continue
    echo "$name"
    done 2>/dev/null
}

list_account()
{
    local usernames=`list_account_`
    for name in $usernames; do
        write_enum_item "$name"
    done
}

list_kiosk_profile()
{
    lang="${in_language/_*/}"
    write_enum_item "" "`_  "Default desktop"`"
    for file in $(find "$KIOSK_PROFILES_DIR" -name '*.desktop')
    do
        # Test hidden flag
        hidden="$(ini_config_get "$file" 'Desktop Entry' Hidden)"
        [ "$hidden" = "true" ] && continue

        # Ignore missing TryExec
        try_exec="$(ini_config_get "$file" 'Desktop Entry' TryExec)"
        [ -n "$try_exec" -a ! -n "`which "$try_exec" 2>/dev/null`" ] && continue

        # Detect application name (begin from localized GenericName, end with Name)
        name="$(ini_config_get "$file" 'Desktop Entry' "GenericName[$lang]")"
        if [ -z "$name" ]; then
            name="$(ini_config_get "$file" 'Desktop Entry' "Name[$lang]")"
            if [ -z "$name" ]; then
                name="$(ini_config_get "$file" 'Desktop Entry' "GenericName")"
                if [ -z "$name" ]; then
                    name="$(ini_config_get "$file" 'Desktop Entry' "Name")"
                fi
            fi
        fi
        [ -z "$name" ] && continue

        # Add to list
        profile="$(basename $file)"
        write_enum_item "$profile" "$name ($profile)"
    done
}

list_system_roles()
{
    strings=$(rolelst -S)

    [ -z "$strings" ] || {
        while IFS= read -r str
        do
            str=${str%:*}
            #remove spaces at the beginning and end of a line
            str="$(echo -e "${str}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
            write_enum_item "$str" "$str"
        done <<< $strings
    }
}

list_groups()
{
    [ -z "$in_name" ] || {
        #Groups the user belongs to
        groups=$(groups "$in_name")

        IFS=' ' read -r -a array <<< ${groups#*:}
        IFS=$'\n' sorted=($(sort <<<"${array[*]}"));unset IFS
        for index in ${!sorted[@]}
        do
            write_enum_item "${sorted[$index]}" "${sorted[$index]}"
        done
    }
}

get_kiosk_profile()
{
    file="/etc/X11/xsession.user.d/$in_name"
    sed -n 's/^PROFILE=//p' "$file" 2>/dev/null
}

set_kiosk_profile()
{
    local profile="$1"
    if [ -n "$profile" ]; then
        echo '#!/bin/sh' > "/etc/X11/xsession.user.d/$in_name"
        echo "PROFILE=$profile" >> "/etc/X11/xsession.user.d/$in_name"
        echo 'e="$(sed -n 's/^Exec[[:space:]]*=[[:space:]]*//p' "/etc/kiosk/$PROFILE")"' >> "/etc/X11/xsession.user.d/$in_name"
        echo 'test -n "$e" && `$e`' >> "/etc/X11/xsession.user.d/$in_name"
        chmod +x "/etc/X11/xsession.user.d/$in_name"
    else
        [ -e "/etc/X11/xsession.user.d/$in_name" ] && /bin/rm -f "/etc/X11/xsession.user.d/$in_name"
    fi
}

create_account()
{
    [ -n "$in_new_name" ] || return

    # prepare password
    local password
    password="$(user_passwd)" || return

    # main settings
    local args="$(user_args)"
    eval user_new $args "$in_new_name" || return

    # real name settings
    if [ -n "$in_real_name" ];then
	name_write "$in_real_name" "$in_new_name" || return
    fi

    # wheel
    if test_bool "$in_allow_su";then
	group_include wheel "$in_new_name" || return
    fi

    # default groups
    group_include_default "$in_new_name" || return

    if test_bool "$in_autologin"; then
	al_disable
	al_enable "$in_new_name" || return
    fi

    # change password
    [ -z "$password" ] ||
	user_chpasswd "$in_new_name" "$password" ||
	return
}

destroy_account()
{
    al_disable "$in_new_name"
    [ -z "$in_name" ] ||
	/usr/sbin/userdel "$in_name" ||
	user_write_retcode "$?"
}

read_account()
{
    [ -z "$in_name" ] || {
	local_getent passwd "$in_name"|
	    (IFS=':' read name password uid gid gecos home shell;
		real_name=$(echo "$gecos" | cut -d, -f1)
		write_string_param real_name "$real_name"
		write_string_param home "$home"
		write_string_param shell "$shell"

		! local_getent group wheel|cut -d: -f4|fgrep -qws "$name"
		write_bool_param allow_su "$?")
	! al_check "$in_name"; write_bool_param autologin "$?"
        write_string_param kiosk_profile "$(get_kiosk_profile)"

        #list of system roles
        role_lst=$(rolelst -S)
        roles=""
        while IFS= read -r str
        do
            str=${str%:*}
            #remove spaces at the beginning and end of a line
            str="$(echo -e "${str}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
            ! local_getent group "$str"|cut -d: -f4|fgrep -qws "$in_name"

            sys_assigned=$?
            if [ "$sys_assigned" -eq 1 ]
            then
                if [ "$roles" ]
                then
                    roles="$roles;$str"
                else
                    roles="$str"
                fi
            fi
        done <<< $role_lst

        write_string_param system_roles "$roles"
    }

    [ -n "$in_name" ] && set_face $in_name ||:
}

write_account()
{
    [ -n "$in_name" ] || return

    # real name settings
    if [ -n "$in_real_name" ];then
	name_write "$in_real_name" "$in_name" || return
    fi

    # main settings
    local args="$(user_args)"
    if [ -n "$args" ];then
	eval user_write $args "$in_name" || return
    fi

    # prepare password
    local password
    password="$(user_passwd)" || return

    # change password
    [ -z "$password" ] ||
	user_chpasswd "$in_name" "$password" ||
	return

    # wheel
    if test_bool "$in_allow_su"; then
	group_include wheel "$in_name" || return
    else
	group_exclude wheel "$in_name"
    fi

    # system role
    sys_role_exclude "$in_name"
    IFS=';' read -r -a array <<< $in_system_roles

	for i in ${!array[@]}
	do
        group_include "${array[$i]}" "$in_name" || return
	done

    if test_bool "$in_autologin"; then
	if ! al_check "$in_name"; then
	    al_disable	# possibly someone else
	    al_enable "$in_name" || return
        fi
    else
	al_check "$in_name" && al_disable "$in_name" ||:
    fi

    set_kiosk_profile "$in_kiosk_profile"

    [ -n "$in_user_face" ] && write_to_cache "face" $in_user_face ||:
}

generate_password()
{
    write_string_param passwd_auto "$(pwqgen)"
}

allow_autologin()
{
    local ret=false
    al_possible && ret=true
    write_bool_param allow_autologin "$ret"
}

check_symlink()
{
    [ $# -ne 0 ] || return

    local target_file="$1"
    local cnt=0
    while :; do
        [ -h "$target_file" -a "$cnt" -lt 8 ] || break
        target_file=`/bin/readlink -n $target_file`
        cnt=$((cnt+1))
    done
    [ "$cnt" -lt 8 -a -f "$target_file" ] && return 0 || return 1
}

read_face()
{
    local file=`ls $alterator_faces_dir`
    for f in $file; do
        local cur_face=$alterator_faces_dir/$f
        if [ -f "$cur_face" -o -h "$cur_face" ]; then
            if [ -h "$cur_face" ]; then
                check_symlink $cur_face
                [ $? -eq 0 ] || continue
            fi

            [[ "`basename $cur_face`" =~ ^.*\.(png|jpg)$ ]] || continue

            write_table_item name $f pixmap "faces/$f"
        fi
    done
}

. shell-config

write_to_cache()
{
    mkdir -p "$cachedir" && \
    shell_config_set "$cachedir/data" "$1" "$2"
}

write_face()
{
    [ -n "$in_face" ] || return

    if [ -n "$in_name" -a -n "$in_face" ]; then
        local face=`basename $in_face`

        # check face is a file or symlink
        local path_to_face=`find $default_faces_dir -name "$face"`
        [ -n "$path_to_face" ] && [ -f "$path_to_face" -o -h "$path_to_face" ] || return
        check_symlink $path_to_face
        [ $? -eq 0 ] || return

        local tmp_folder=`mktemp -d alterator-users.XXXXXX`
        local user_home_dir=`getent passwd $in_name | cut -d\: -f6`
        facename=`find $default_faces_dir -type f -name "$face" 2>/dev/null`

        # Write to the user directory
        [ -f $user_home_dir/$default_userface ] ||\
            install -m 644 /dev/null "$user_home_dir/$default_userface"

        chmod 0640 $tmp_folder
        cp $facename $tmp_folder
        face=`basename $facename`
        /bin/chmod 0644 $tmp_folder/$face
        /bin/chown $in_name:$in_name $tmp_folder/$face

        local uid=`getent passwd $in_name | cut -d: -f3`
        /usr/bin/alterator_users_helper -f $tmp_folder/$face -t $user_home_dir/$default_userface -u $uid

        # Write to the AccountsService directory
        mkdir -p $accountsservice_icons_dir
        [ -f $accountsservice_icons_dir/$in_name ] ||\
            install -m 644 /dev/null "$accountsservice_icons_dir/$in_name"

        cp $facename $tmp_folder
        /bin/chmod 0644 $tmp_folder/$face
        /bin/chown root:root $tmp_folder/$face

        /usr/bin/alterator_users_helper -f $tmp_folder/$face -t $accountsservice_icons_dir/$in_name -u $UID

        # Set paths to file.
        local accountservice_user_dir=/var/lib/AccountsService/users
        local accountservice_user_file=$accountservice_user_dir/$in_name
        mkdir -p $accountservice_user_dir
        if [ -f "$accountservice_user_file" ]; then
            # Update existing file: Set Icon as XFCE sets it.
            if grep -q '^Icon=' "$accountservice_user_file"; then
                sed -i "s|^Icon=.*|Icon=$accountsservice_icons_dir/$in_name|" "$accountservice_user_file"
            else
                # If no icon line, set it manually.
                sed -i '/^\[User\]/a Icon='"$accountsservice_icons_dir/$in_name" "$accountservice_user_file"
            fi
        else
            # If no file, create it.
            printf '[User]\nIcon=%s\n' "$accountsservice_icons_dir/$in_name" > "$accountservice_user_file"
        fi

        rm -rf $tmp_folder
        set_face "$in_name"
    elif [ -n "$in_face" ]; then
        write_to_cache "face" $in_face
        write_to_cache "displayed_face" $in_face
    fi
}

set_face()
{
    [ "$#" -lt 1 ] || in_name=$1

    local is_save=false
    [ ! -n "in_save" ] || local is_save=$in_save

    local face
    local user_home_dir=`getent passwd $in_name | cut -d\: -f6`
    if [ -n "$in_name" ] && [ -f "$accountsservice_icons_dir/$in_name" -o -f "$user_home_dir/$default_userface" ]; then
        face=$userface_name

        [ -f "$accountsservice_icons_dir/$in_name" ] &&\
            ln -sf $accountsservice_icons_dir/$in_name $alterator_images_dir/$face ||\
            ln -sf $user_home_dir/$default_userface $alterator_images_dir/$face

        write_to_cache "displayed_face" $face
    elif [ -n "$in_name" ]; then
        face=$BLANK_AVATAR
        [ ! -h "$alterator_images_dir/$userface_name" ] ||\
            write_to_cache "displayed_face" $face
        unlink $alterator_images_dir/$userface_name
    elif [ -f "$cachedir/data" ] && [ "$is_save" = true ]; then
        face=`shell_config_get "$cachedir/data" "face"`
    else
        face=`shell_config_get "$cachedir/data" "displayed_face"`
    fi
    write_string_param face $face
}

view_face()
{
    if [ -n "$in_face" ]; then
        write_to_cache "displayed_face" $in_face
    else
        local shw_face=`shell_config_get "$cachedir/data" "displayed_face"`
        write_string_param face $shw_face
    fi
}

get_current_face()
{
    local usernames=`list_account_`
    [ -n "$usernames" ] || return
    local name=`echo $usernames|cut -d' ' -f1`

    set_face $name
    local face=$BLANK_AVATAR
    [ ! -h "$alterator_images_dir/$userface_name" ] ||\
        face="$userface_name"
    write_to_cache "face" $face
    write_to_cache "displayed_face" $face
    write_string_param face $face
}

remove_avatar()
{
    [ -n "$in_name" ] || return

    local user_home_dir=`getent passwd $in_name | cut -d\: -f6`
    [ ! -f "$user_home_dir/$default_userface" ] ||\
        rm -f $user_home_dir/$default_userface

    [ ! -f "$accountsservice_icons_dir/$in_name" ] ||\
        rm -f $accountsservice_icons_dir/$in_name

    [ ! -h "$alterator_images_dir/$userface_name" ] ||\
        unlink $alterator_images_dir/$userface_name

    # Remove Icon line at icon delete.
    local accountservice_user_dir=/var/lib/AccountsService/users
    local accountservice_user_file=$accountservice_user_dir/$in_name
    if [ -f "$accountservice_user_file" ]; then
        if grep -q '^Icon=' "$accountservice_user_file"; then
            sed -i '/^Icon=/d' "$accountservice_user_file"
        fi
    fi

    local face=$BLANK_AVATAR

    write_to_cache "face" $face
    write_to_cache "displayed_face" $face
}

get_ssh_key_fingerprint()
{
    local key="$1"
    local res="$(echo "$key" | ssh-keygen -l -f -)" && 
    echo "$res" | cut -f2 -d' '
}

ssh_key_add()
{
    [ -z "$in_key" -o -z "$in_name" ] && return 0
    if ! id -u $in_name >& /dev/null; then
        write_error "`_ "User does not exist"`"
        return 1
    fi

    local fp="$(get_ssh_key_fingerprint "$in_key")"
    if [ -z "$fp" ]; then
        write_error "`_ "Invalid ssh key"`"
        return 1
    fi

    local user_home_dir="$(grep ^"$in_name": /etc/passwd |cut -d ':' -f6)"
    if [ ! -d "$user_home_dir" ]; then
        write_error "`_ "User $in_name has no home directory"`"
        return 1
    fi

    if [ ! -d "$user_home_dir/.ssh" ]; then
        write_error "`_ "User $in_name has no .ssh directory"`"
        return 1
    fi

    local authorized_keys_file="$user_home_dir/.ssh/authorized_keys"
    [ -s "$authorized_keys_file" ] &&
    while IFS=' ' read -r line; do
        local existing_fp="$(get_ssh_key_fingerprint "$line")"
        if [ "$fp" = "$existing_fp" ]; then
            write_error "`_ "SSH key already exists"`"
            return 1
        fi
    done < "$authorized_keys_file"

    echo "$in_key" >> "$authorized_keys_file"
}

alterator_export_var \
    name system-account-name \
    new_name system-account-name

alterator_export_proc list_shell
alterator_export_proc list_account
alterator_export_proc list_kiosk_profile
alterator_export_proc list_system_roles
alterator_export_proc list_groups
alterator_export_proc create_account
alterator_export_proc destroy_account
alterator_export_proc read_account
alterator_export_proc write_account
alterator_export_proc generate_password
alterator_export_proc allow_autologin
alterator_export_proc read_face
alterator_export_proc set_face
alterator_export_proc write_face
alterator_export_proc view_face
alterator_export_proc get_current_face
alterator_export_proc remove_avatar
alterator_export_proc ssh_key_add

message_loop
