#!/bin/sh -e

grubdir=`echo "/boot/grub" | sed 's,//*,/,g'`

PACKAGE_VERSION="2.12-1+1astra5+ci2"
PACKAGE_NAME="GRUB"
self=`basename $0`
bindir="${exec_prefix}/bin"
grub_mkpasswd="${bindir}/grub-mkpasswd-pbkdf2"

USER_NAME_FIELD="GRUB2_USER"
SUPERUSER_UID=1000
SUPERUSER_GROUP="astra-admin"

# tries to get user name from specified file
# on success returns exit code 0 and not empty user name to stdout
# on any unexpected cases leading to some kind of undefined behaviour returns non zero exit code
# in other cases, when user name is simply undefined in file (or file not exists), simply returns empty string and zero
# exit code
get_user_name_from_file() {
    config_path=$1
    if [ ! -f "${config_path}" ]; then
        echo "File with name '${config_path}' not found" >&2
        return 0
    fi
    grep_result=$(grep "^${USER_NAME_FIELD}=" "${config_path}")
    return_code=$?
    if [ ${return_code} -eq 1 ]; then
        echo "Variable '${USER_NAME_FIELD}' was not set in file '${config_path}'" >&2
        return 0
    fi
    if [ ${return_code} -eq 2 ]; then
        echo "There was an error when running grep on file '${config_path}'" >&2
        return 1
    fi
    if [ ${return_code} -ne 0 ]; then
        echo "Grep returned unexpected code ${return_code} on file '${config_path}'" >&2
        return 1
    fi
    lines_count=$(echo "${grep_result}" | wc -l)
    return_code=$?
    if [ ${return_code} -ne 0 ]; then
        echo "'wc -l' returned unexpected code ${return_code} for string '${grep_result}'" >&2
        return 1
    fi
    if [ "${lines_count}" -ne 1 ]; then
        echo "'wc -l' returned unexpected lines count ${lines_count} for string '${grep_result}'. Required only one"\
        "user in config file '${config_path}'" >&2
        return 1
    fi
    user_name=$(echo "${grep_result}" | sed -e "s/^${USER_NAME_FIELD}=//")
    return_code=$?
    if [ ${return_code} -ne 0 ]; then
        echo "Sed returned unexpected code ${return_code} for string '${grep_result}'" >&2
        return 1
    fi
    if [ -z "${user_name}" ]; then
        echo "Field with name '${USER_NAME_FIELD}' has empty value in file '${config_path}'" >&2
        return 0
    fi
    echo "${user_name}"
}

get_user_name_from_superuser_group() {
    getent_result=$(getent group ${SUPERUSER_GROUP})
    return_code=$?
    if [ ${return_code} -eq 2 ]; then
        echo "Group with name '${SUPERUSER_GROUP}' not found" >&2
        return
    fi
    if [ ${return_code} -ne 0 ]; then
        echo "Unexpected return code ${return_code} after running getent" >&2
        return
    fi
    user_names=$(echo "${getent_result}" | cut -d: -f4)
    return_code=$?
    if [ ${return_code} -ne 0 ]; then
        echo "Unexpected return code ${return_code} after running 'cut -d: -f4' on string '${getent_result}'" >&2
        return
    fi
    if [ -z "${user_names}" ]; then
        echo "User names received from getent result '${getent_result}' using 'cut -d: -f4' is empty" >&2
        return
    fi
    user_name=$(echo "${user_names}" | cut -d, -f1)
    return_code=$?
    if [ ${return_code} -ne 0 ]; then
        echo "Unexpected return code ${return_code} after running 'cut -d, -f1' on string '${user_names}'" >&2
        return
    fi
    if [ -z "${user_name}" ]; then
        echo "User name received from '${user_names}' using 'cut -d, -f1' is empty" >&2
        return
    fi
    echo "${user_name}"
}

# Function is marked as unsafe because called subroutines can return non zero codes.
# So we need to wrap it with "set +e" and then restore "set -e". This is done using get_user_name wrapper
get_user_name_unsafe() {
    config_path=$1
    user_name=$(get_user_name_from_file "${config_path}")
    return_code=$?
    if [ ${return_code} -ne 0 ]; then
        echo "Unexpected situation was detected when getting user name from file '${config_path}'. Will not resume"\
        "other methods to prevent possible data loss" >&2
        return
    fi
    if [ -n "${user_name}" ]; then
        echo "${user_name}"
        return
    fi
    echo "Unable to get user name from file '${config_path}', trying other methods" >&2

    # try to get user name by superuser uid
    user_name=$(id -nu ${SUPERUSER_UID})
    return_code=$?
    if [ ${return_code} -eq 0 ]; then
        echo "${user_name}"
        return
    fi
    echo "id returned unexpected code ${return_code}, trying to get user name in other ways" >&2

    user_name=$(get_user_name_from_superuser_group)
    if [ -n "${user_name}" ]; then
        echo "${user_name}"
        return
    fi
    echo "Unable to get user name from superuser group ${SUPERUSER_GROUP}" >&2
}

get_user_name() {
    set +e
    get_user_name_unsafe "$1"
    set -e
}

# Usage: usage
# Print the usage.
usage () {
    cat <<EOF
Usage: $0 [OPTION]
$0 prompts the user to set a password on the grub bootloader. The password
is written to a file named user.cfg which lives in the GRUB directory
located by default at ${grubdir}.

  -h, --help                     print this message and exit
  -v, --version                  print the version information and exit
  -o, --output <DIRECTORY>       put user.cfg in a user-selected directory
EOF
}

argument () {
    opt=$1
    shift

    if test $# -eq 0; then
        gettext_printf "%s: option requires an argument -- \`%s'\n" "$self" "$opt" 1>&2
        exit 1
    fi
    echo $1
}

# Ensure that it's the root user running this script
if [ "${EUID}" -ne 0 ]; then
    echo "The grub bootloader password may only be set by root."
    usage
    exit 2
fi

# Check the arguments.
while test $# -gt 0
do
    option=$1
    shift

    case "$option" in
    -h | --help)
	usage
	exit 0 ;;
    -v | --version)
	echo "$self (${PACKAGE_NAME}) ${PACKAGE_VERSION}"
	exit 0 ;;
    -o | --output)
        OUTPUT_PATH=`argument $option "$@"`; shift ;;
    --output=*)
        OUTPUT_PATH=`echo "$option" | sed 's/--output=//'` ;;
    -o=*)
        OUTPUT_PATH=`echo "$option" | sed 's/-o=//'` ;;
    esac
done

# set user input or default path for user.cfg file
if [ -z "${OUTPUT_PATH}" ]; then
    OUTPUT_PATH="${grubdir}"
fi

if [ ! -d "${OUTPUT_PATH}" ]; then
    echo "${OUTPUT_PATH} does not exist."
    usage
    exit 2;
fi
USER_CFG_FILE="${OUTPUT_PATH}/user.cfg"

USER_NAME=$(get_user_name "${USER_CFG_FILE}")

if [ -z "${USER_NAME}" ]; then
  echo "Unable to get user name"
  exit 3
fi
echo "Selected GRUB superuser is '${USER_NAME}'"

ttyopt=$(stty -g)
fixtty() {
      stty ${ttyopt}
}

trap fixtty EXIT
stty -echo

# prompt & confirm new grub2 root user password
echo -n "Enter password: "
read PASSWORD
echo
echo -n "Confirm password: "
read PASSWORD_CONFIRM
echo
stty ${ttyopt}

getpass() {
    local P0
    local P1
    P0="$1" && shift
    P1="$1" && shift

    ( echo ${P0} ; echo ${P1} ) | \
        LC_ALL=C ${grub_mkpasswd} | \
        grep -v '[eE]nter password:' | \
        sed -e "s/PBKDF2 hash of your password is //"
}

MYPASS="$(getpass "${PASSWORD}" "${PASSWORD_CONFIRM}")"
if [ -z "${MYPASS}" ]; then
      echo "${self}: error: empty password" 1>&2
      exit 1
fi

# keep file access rights for target file if exists
if [ ! -f "${USER_CFG_FILE}" ]; then
    if [ -f /usr/sbin/execaps ]; then
        # execaps is used to run "install" with PARSEC_CAP_INHERIT_INTEGRITY privilege to create new file with same
        # integrity level as user have
        /usr/sbin/execaps -c 0x20000 -- install -m 0600 /dev/null "${USER_CFG_FILE}"
    else
        # in case of execaps absence we simply run install without it, because MAC is not active in this case
        install -m 0600 /dev/null "${USER_CFG_FILE}"
    fi
fi
echo "GRUB2_USER=${USER_NAME}" > "${USER_CFG_FILE}"
echo "GRUB2_PASSWORD=${MYPASS}" >> "${USER_CFG_FILE}"

if ! grep -q "^### BEGIN /etc/grub.d/01_users ###$" "${OUTPUT_PATH}/grub.cfg"; then
    echo "WARNING: The current configuration lacks password support!"
    echo "Update your configuration with grub-mkconfig to support this feature."
fi
