#!/bin/bash

# This script acts as a basic sudo wrapper using run0.
# It handles the common case of switching to root to run a command.

__config_file="/etc/run0-wrappers/sudo.conf"
__run0_exec="run0"
RUN0_BACKGROUND="--background="
secure_path="/usr/sbin:/usr/bin:/sbin:/bin"

if [ -f "${__config_file}" ]; then
    # shellcheck source=/dev/null
    . "${__config_file}"
fi

# --- Function to display usage information ---

show_help() {
    cat << 'EOF'
run0-sudo - execute a command as another user via run0

usage: run0-sudo -h | -K | -k | -V
usage: run0-sudo -v [-g group] [-u user]
usage: run0-sudo [-EHkNnP] [-D directory] [-g group]
            [-u user] [VAR=value] [-i | -s] [command [arg ...]]

Options:
  -D, --chdir=<directory>   Change the working directory
  -E, --preserve-env        Preserve user environment
      --preserve-env=<list> Preserve specific environment variables
  -g, --group=<group>       Run command as specified group or GID
  -H, --set-home            Set HOME variable, default with run0
  -h, --help                Display help text and exit
  -i, --login               Run login shell as the target user
  -K, --remove-timestamp    Revoke all cached polkit credentials for the user
  -k, --reset-timestamp     Revoke cached polkit credentials for the current session;
                            when used with a command, forces re-authentication
  -s, --shell               Run the specified shell
  -u, --user=user           Run command as specified user or UID
  -V, --version             Display version and exit
  -v, --validate            Update timestamp without running a command
EOF
}

show_version() {
    run0_version="$(run0 --version 2>/dev/null | head -n 1 | cut -d' ' -f3 | sed 's/[()]//g')"
    echo "run0-sudo (run0-wrappers) 0.5.0"
    echo "run0 (systemd) $run0_version"
}

# --- Revoke polkit temporary authorizations ---

revoke_session_credentials() {
    local session_id="${XDG_SESSION_ID:-}"
    if [ -z "$session_id" ]; then
        return 0
    fi
    busctl call org.freedesktop.PolicyKit1 \
        /org/freedesktop/PolicyKit1/Authority \
        org.freedesktop.PolicyKit1.Authority \
        RevokeTemporaryAuthorizations "(sa{sv})" \
        "unix-session" 1 "session-id" s "$session_id" 2>/dev/null || true
}

revoke_all_credentials() {
    local current_user
    current_user=$(id -un)
    local found=0
    while read -r session_id _; do
        found=1
        busctl call org.freedesktop.PolicyKit1 \
            /org/freedesktop/PolicyKit1/Authority \
            org.freedesktop.PolicyKit1.Authority \
            RevokeTemporaryAuthorizations "(sa{sv})" \
            "unix-session" 1 "session-id" s "$session_id" 2>/dev/null || true
    done < <(loginctl list-sessions --no-legend 2>/dev/null | awk -v user="$current_user" '$3 == user {print $1}')
    if [ "$found" -eq 0 ]; then
        revoke_session_credentials
    fi
}

# --- Get shell for the target user or current user ---
get_shell() {
    local targetuser=$1

    if [ -n "$SHELL" ]; then
        echo "$SHELL"
    else
	# otherwise, get the shell of the target user
	local shell
	shell=$(getent passwd "$targetuser" 2>/dev/null | cut -d: -f7)
	echo "$shell"
    fi
}

# --- option handling ---

# Array to hold the arguments for run0
__run0_args=("$RUN0_BACKGROUND")
# Array to hold the final command for run0
__run0_command=()
__run0_env=()
# Shell to use if one is specified
__run0_shell=""
__run0_login_shell=""
__run0_validate_only=""
__run0_preserve_env=""
__run0_reset_timestamp=""
__run0_remove_timestamp=""
# Variables to track user and command
__run0_target_user=""
__run0_target_group=""
__run0_target_cwd=""

command_args() {
    if [ -n "$__run0_shell" ]; then
	__run0_command+=("$__run0_shell")
	if [ -n "$__run0_login_shell" ]; then
	    __run0_command+=("$__run0_login_shell")
	fi
	if [ $# -gt 0 ]; then
    	    __run0_command+=("-c" "$*")
	fi
    else
	__run0_command=("$@")
    fi
}

parse_arguments() {
    # Define short and long options
    # + prefix means stop at first non-option (critical for sudo behavior)
    local short_opts="+AbBC:D:Eu:g:isHnkKPlvhVN"
    local long_opts="askpass,background,bell,close-from:,chdir:,user:,group:,login,shell,set-home,preserve-env::,non-interactive,reset-timestamp,remove-timestamp,preserve-groups,list,validate,help,version,no-update"

    # Parse arguments using getopt
    local parsed_args
    if ! parsed_args=$(getopt -o "$short_opts" -l "$long_opts" -n "run0-sudo" -- "$@"); then
	show_help >&2
	exit 1
    fi

    # Set parsed arguments
    eval set -- "$parsed_args"

    # Process parsed options
    while true; do
        case "$1" in
            -A|--askpass)
		echo "-A/--askpass is not supported" >&2
		shift
		;;
	    -b|--background)
		echo "-b/--background is not supported" >&2
		shift
		;;
	    -B|--bell)
		echo "-B/--bell is not supported" >&2
		shift
		;;
	    -C|--close-from)
		echo "-C/--close-from <num> is not supported" >&2
		shift 2
		;;
            -D|--chdir)
                __run0_target_cwd="$2"
                shift 2
                ;;
            -E|--preserve-env)
                if [[ "$1" == "--preserve-env" && $# -gt 1 && "$2" != "--" ]]; then
                    # --preserve-env with a value (could be empty)
                    if [[ -z "$2" ]]; then
                        # Empty value - this is an error
                        echo "run0-sudo: option '--preserve-env' requires a variable list" >&2
                        show_help >&2
                        exit 1
                    fi
		    __run0_preserve_env="$2"
                    shift 2
                else
                    # -E format (preserve all) or --preserve-env without argument
                    __run0_preserve_env="all"
                    shift 1
                fi
                ;;
            -g|--group)
		__run0_target_group="$2"
                shift 2
                ;;
            -H|--set-home)
		# HOME is set by run0, no need to do anything
                shift
                ;;
            -h|--help)
                show_help
                exit 0
                ;;
            -i|--login)
		__run0_shell=$(get_shell "$__run0_target_user")
		__run0_login_shell="-l"
		# We need to change CWD to ~
		# run0 will do that if a target user is specified
		if [ -z "$__run0_target_user" ]; then
		    __run0_target_user="root"
		fi
		shift
		;;
            -k|--reset-timestamp)
		__run0_reset_timestamp=1
		shift
		;;
            -K|--remove-timestamp)
		__run0_remove_timestamp=1
		shift
		;;
            -l|--list)
		echo "-l/--list is not supported" >&2
		shift
		;;
	    -N|--no-update)
		echo "-N/--no-update is not supported" >&2
                shift
                ;;
            -n|--non-interactive)
		__run0_args+=("--no-ask-password")
		shift
		;;
            -P|--preserve-groups)
    		 echo "-P|--preserve-groups is not supported" >&2
		 shift
		 ;;
            -s|--shell)
		__run0_shell=$(get_shell "$__run0_target_user")
                shift
                ;;
            -u|--user)
                __run0_target_user="$2"
                shift 2
                ;;
            -v|--validate)
		__run0_validate_only=1
		shift
		;;
            -V|--version)
                show_version
                exit 0
                ;;
            --)
                shift
                break
                ;;
            *)
		echo "Invalid option: $1" >&2
		;;
        esac
    done

    # Show usage if no command is provided (default run0 behavior is to run a shell)
    if [ $# -eq 0 ] && \
       [ -z "$__run0_login_shell" ] && \
       [ -z "$__run0_shell" ] && \
       [ -z "$__run0_validate_only" ] && \
       [ -z "$__run0_reset_timestamp" ] && \
       [ -z "$__run0_remove_timestamp" ]; then
        show_help >&2
        exit 1
    fi

    # Parse VAR=value assignments before the command (like sudo supports)
    while [ $# -gt 0 ]; do
        case "$1" in
            *=*)
                __varname="${1%%=*}"
                if [[ "$__varname" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
                    __run0_env+=("--setenv=$1")
                    shift
                else
                    break
                fi
                ;;
            *)
                break
                ;;
        esac
    done

    # Build run0 arguments and the command to execute
    command_args "$@"
}


# --- Main Logic ---

parse_arguments "$@"

# -K must not be combined with a command or other meaningful options
if [ -n "$__run0_remove_timestamp" ] && [ ${#__run0_command[@]} -gt 0 ]; then
    echo "run0-sudo: -K cannot be used with a command" >&2
    exit 1
fi

# Handle credential revocation
if [ -n "$__run0_remove_timestamp" ]; then
    revoke_all_credentials
    exit 0
fi

if [ -n "$__run0_reset_timestamp" ] && [ ${#__run0_command[@]} -eq 0 ] && \
   [ -z "$__run0_login_shell" ] && [ -z "$__run0_shell" ]; then
    revoke_session_credentials
    exit 0
fi

# -k combined with a command: revoke credentials first so polkit re-authenticates
if [ -n "$__run0_reset_timestamp" ]; then
    revoke_session_credentials
fi

if [ -n "$__run0_target_user" ]; then
    __run0_args+=("--user=${__run0_target_user}")
fi
if [ -n "$__run0_target_group" ]; then
    __run0_args+=("--group=${__run0_target_group}")
fi
if [ -n "$__run0_target_cwd" ]; then
    __run0_args+=("--chdir=${__run0_target_cwd}")
fi

if [ "$__run0_preserve_env" == "all" ]; then
    while IFS= read -r -d '' __entry; do
        __varname="${__entry%%=*}"
        case "$__varname" in
            LOGNAME|USER|PATH|HOME) continue ;;
            _*|SUDO_*) continue ;;
        esac
        __run0_env+=("--setenv=${__entry}")
    done < <(printenv -0)
elif [ -n "$__run0_preserve_env" ]; then
    __vars=$(echo "$__run0_preserve_env" | tr ',' ' ')
    for var in $__vars; do
        # Remove whitespace
	var="${var//[[:blank:]]/}"
	__run0_env+=("--setenv=$var")
    done
fi

export PATH="${secure_path}"
unset secure_path

if [ "$__run0_validate_only" == "1" ]; then
    exec "${__run0_exec}" "${__run0_args[@]}" "${__run0_env[@]}" -- true
else
    exec "${__run0_exec}" "${__run0_args[@]}" "${__run0_env[@]}" -- "${__run0_command[@]}"
fi
