#!/bin/bash
###############################################################################
# BRLTTY - A background process providing access to the console screen (when in
#          text mode) for a blind person using a refreshable braille display.
#
# Copyright (C) 1995-2025 by The BRLTTY Developers.
#
# BRLTTY comes with ABSOLUTELY NO WARRANTY.
#
# This is free software, placed under the terms of the
# GNU Lesser General Public License, as published by the Free Software
# Foundation; either version 2.1 of the License, or (at your option) any
# later version. Please see the file LICENSE-LGPL for details.
#
# Web Page: http://brltty.app/
#
# This software is maintained by Dave Mielke <dave@mielke.cc>.
###############################################################################

set -e
. "`dirname "${0}"`/brltty-prologue.sh"
. "${programDirectory}/brltty-config.sh"

showProgramUsagePurpose() {
cat <<END_OF_PROGRAM_USAGE_PURPOSE
Simplify the job of Creating and making changes to BRLTTY's unprivileged user.
END_OF_PROGRAM_USAGE_PURPOSE
}

declare -A privilegeParameters
parseParameterString privilegeParameters "${BRLTTY_PRIVILEGE_PARAMETERS}" lx

readonly defaultUserName="${privilegeParameters["USER"]}"
readonly defaultUserInformation="Braille Device Daemon"
readonly defaultHomeDirectory="${BRLTTY_UPDATABLE_DIRECTORY}"
readonly defaultLoginShell="/sbin/nologin"

showProgramUsageNotes() {
cat <<END_OF_PROGRAM_USAGE_NOTES
When creating a new user, these options have the following defaults:
  -d  ${defaultHomeDirectory:-the home directory is not defined}
  -g  a new group with the same name as the user
  -i  ${defaultUserInformation}
  -l  ${defaultLoginShell}
These defaults don't apply when making changes to an existing user.
END_OF_PROGRAM_USAGE_NOTES
}

executeCommand() {
   "${useSudo}" && set -- sudo -- "${@}"

   if "${testMode}"
   then
      echo "${*}"
   else
      "${@}"
   fi
}

groupList=()
addGroups() {
   while [ "${#}" -gt 0 ]
   do
      local group="${1}"
      shift 1
      [ -n "${group}" ] || continue

      [ "${group#*/}" = "${group}" ] || {
         [ -e "${group}" ] || continue
         group="$(stat -c "%G" "${group}")" || :
         [ -n "${group}" ] || continue
      }

      local g
      for g in "${groupList[@]}"
      do
         [ "${g}" = "${group}" ] && {
            group=""
            break
         }
      done

      [ -n "${group}" ] && groupList[${#groupList[*]}]="${group}"
   done

   return 0
}

addProgramOption a flag noALSA "don't allow playing sound via the ALSA framework"
addProgramOption b flag noBrlapi "don't allow reading BrlAPI's authorization key file"
addProgramOption c flag noConsoles "don't allow access to the virtual consoles"
addProgramOption d string.path homeDirectory "set/change the home (login) directory"
addProgramOption g string.group primaryGroup "set/change the primary (login) group"
addProgramOption i string.text userInformation "set/change the user information (gecos) text"
addProgramOption k flag noKeyboards "don't allow keyboard monitoring"
addProgramOption l string.path loginShell "set/change the login shell"
addProgramOption p flag noPulse "don't allow playing sound via the Pulse Audio server"
addProgramOption s flag noSerial "don't allow access to serial devices"
addProgramOption u flag noUSB "don't allow access to USB devices"
addProgramOption E flag allowChanges "allow changes to an existing user"
addProgramOption G flag noGroups "don't set/change the supplementary group list"
addProgramOption N flag allowCreate "allow creating a new user"
addProgramOption S flag useSudo "use sudo to execute the commands as root"
addProgramOption T flag testMode "test mode - show the commands that would be executed"
addProgramOption U string.user userName "the user to be created/changed" "${defaultUserName}"
parseProgramArguments "${@}"

[ -n "${userName}" ] || userName="${defaultUserName}"
[ -n "${userName}" ] || syntaxError "unprivileged user not configured - use -U (user) to specify"

if id -u "${userName}" >/dev/null 2>&1
then
   "${allowChanges}" || semanticError "user already exists: ${userName} - use -E (existing) to allow"
   command=(usermod)
else
   "${allowCreate}" || semanticError "user doesn't exist: ${userName} - use -N (new) to allow"
   command=(useradd --system)
   [ -n "${primaryGroup}" ] || command+=(--user-group)

   [ -n "${userInformation}" ] || userInformation="${defaultUserInformation}"
   [ -n "${homeDirectory}" ] || homeDirectory="${defaultHomeDirectory}"
   [ -n "${loginShell}" ] || loginShell="${defaultLoginShell}"

   if [ -z "${homeDirectory}" ]
   then
      createHomeDirectory=false
   elif [ -e "${homeDirectory}" ]
   then
      [ -d "${homeDirectory}" ] || semanticError "not a directory: ${homeDirectory}"
      createHomeDirectory=false
   else
      createHomeDirectory=true
   fi

   if "${createHomeDirectory}"
   then
      command+=(--create-home)
   else
      command+=(--no-create-home)
   fi
fi

[ -n "${homeDirectory}" ] && command+=(--home "${homeDirectory}")
[ -n "${primaryGroup}" ] && command+=(--gid "${primaryGroup}")
[ -n "${userInformation}" ] && command+=(--comment "${userInformation}")
[ -n "${loginShell}" ] && command+=(--shell "${loginShell}")

"${noGroups}" || {
   "${noALSA}" || addGroups audio
   "${noBrlapi}" || addGroups "${BRLAPI_KEY_FILE}"
   "${noConsoles}" || addGroups tty /dev/vcs1 /dev/tty1
   "${noKeyboards}" || addGroups input
   "${noPulse}" || addGroups pulse-access
   "${noSerial}" || addGroups /dev/ttyS0
   "${noUSB}" || addGroups /dev/bus/usb

   groupsOperand="${groupList[*]}"
   groupsOperand="${groupsOperand// /,}"
   command+=(--groups "${groupsOperand}")
}

"${testMode}" || {
   if "${useSudo}"
   then
      sudo -v
   elif [ "$(id -u)" -ne 0 ]
   then
      semanticError "not executing as root"
   fi
}

command+=(-- "${userName}")
executeCommand "${command[@]}"
exit 0
