#!/bin/bash

# Copyright Jean-Philippe Guillemin <jp.guillemin@free.fr>. This program is free software;
# you can redistribute it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 of the License,
# or (at your option) any later version. Please take a look at http://www.gnu.org/copyleft/gpl.htm
# Version 1.7 changes by Teran McKinney (sega01).
# Modernized yad GUI edition (X-only, no gettext) by Claude.

# Userconfig can:
# - list existing users/groups and their properties
# - create/delete users and groups
# - modify any user properties (single combined form)

if ! command -v yad >/dev/null 2>&1; then
  echo "yad is required but was not found in PATH." >&2
  exit 1
fi

if [ ! "$DISPLAY" ]; then
  echo "This program requires a running X session." >&2
  exit 1
fi

export PATH="${PATH}:/usr/sbin:/sbin"

skeleton='/etc/skel'
defaultshell='/bin/bash'
defaultgroup='users'
pwfile='/etc/passwd'
grpfile='/etc/group'
shadowfile='/etc/shadow'
title="User Manager"
icon="system-users"

YAD_COMMON=(--center --window-icon="$icon")

for isgroupinzen in audio video lp cdrom fuse dialout floppy plugdev netdev scanner power polkitd taratata ; do
  grep -q "^${isgroupinzen}:" $grpfile && useraddmembership="${isgroupinzen},${useraddmembership}"
done
useraddmembership="$( echo ${useraddmembership} | sed -e 's/^\(.*\),$/\1/' )"

# Common list of shells for the shell combo box
shelllist="/bin/bash!/bin/sh!/bin/zsh!/bin/dash!/usr/bin/fish!/sbin/nologin"

info_box() {
  yad "${YAD_COMMON[@]}" --title "$title" --text "$1" \
    --button="OK:0" --image="dialog-information" --width=380
}

warn_box() {
  yad "${YAD_COMMON[@]}" --title "Warning" --text "$1" \
    --button="OK:0" --image="dialog-warning" --width=420
}

confirm_box() {
  # returns 0 if confirmed (Proceed), 1 otherwise
  yad "${YAD_COMMON[@]}" --title "Confirm" --text "$1" \
    --image="dialog-question" --width=420 \
    --button="Cancel:1" --button="Proceed:0"
}

###############################################
# Main menu (home screen)

promptaction() {
  mode=$(yad "${YAD_COMMON[@]}" --width=480 --height=240 \
    --title "$title" \
    --text "<big><b>Welcome to $(basename $0)</b></big>\n\nSelect an action:" \
    --list --no-headers --separator="" \
    --column="icon:IMG" --column="Action" --column="Description" --column="key:HD" \
    "system-users" "List / Edit Users" "Browse existing users and edit their properties" "List users" \
    "contact-new" "New User" "Create a new user account" "New user" \
    "system-users" "List / Edit Groups" "Browse groups and their members" "List groups" \
    "folder-new" "New Group" "Create a new group" "New group" \
    --button="Exit:1" --button="Select:0" \
    --print-column=4 2>/dev/null)

  rc=$?
  mode="$(echo "$mode" | tr -d '\n' )"
  if [ $rc -ne 0 ] || [ ! "$mode" ]; then
    mode="none"
  fi
}

###############################################
# list existing users

showusers() {
  local rowargs=()
  while IFS=: read -r uname _ uuid _; do
    [ ! "$uname" ] && continue
    rowargs+=("avatar-default" "$uname" "$uuid")
  done < "$pwfile"

  user=$(yad "${YAD_COMMON[@]}" --width=460 --height=520 \
    --title "Users" \
    --text "<b>Users of the system</b>\nSelect a user to edit, or close to return." \
    --list --no-headers \
    --column "icon:IMG" --column "User" --column "UID" \
    --button="Back:1" --button="Edit:0" \
    --print-column=2 \
    "${rowargs[@]}")
  rc=$?
  user="${user%|*}"
  [ $rc -ne 0 ] && user=""
}

###############################################
# list existing groups

showgroups() {
  local rowargs=()
  while IFS=: read -r gname _ ggid _; do
    [ ! "$gname" ] && continue
    rowargs+=("folder" "$gname" "$ggid")
  done < "$grpfile"

  group=$(yad "${YAD_COMMON[@]}" --width=460 --height=460 \
    --title "Groups" \
    --text "<b>Groups of the system</b>\nSelect a group, then Edit or view its Members." \
    --list --no-headers \
    --column "icon:IMG" --column "Group" --column "GID" \
    --button="Back:1" --button="Members:2" --button="Edit:0" \
    --print-column=2 \
    "${rowargs[@]}")
  rc=$?
  group="${group%|*}"

  case $rc in
    0) check="" ;;
    2) check="checked" ;;
    *) group=""; check="" ;;
  esac
}

###############################################
# Get user parameters from system

getsysteminfo() {
  uid="$(grep -e "^$user:.*$" $pwfile | cut -d: -f 3 )"
  gid="$(grep -e "^$user:.*$" $pwfile | cut -d: -f 4 )"
  homedir="$(grep -e "^$user:.*$" $pwfile | cut -d: -f 6 )"
  shell="$(grep -e "^$user:.*$" $pwfile | cut -d: -f 7 )"
  group="$(groups "$user" | sed -e 's/^.*: \(.*\)$/\1/' | cut -d \  -f 1 )"
  membership="$(groups "$user" | sed -e 's/^.*: \(.*\)$/\1/' | cut -d \  -f 2- )"
  expirydays="$(grep -e "^$user:.*$" $shadowfile | cut -d: -f 8 )"
  if [ "$expirydays" ]; then
    expiry="$(date -u --date "Jan 1, 1970 + $expirydays days" +%Y-%m-%d)"
  else
    expiry=""
  fi
  fingerinfo="$(grep -e "^$user:.*$" $pwfile | cut -d: -f 5 )"
  realname="$(echo $fingerinfo | cut -d , -f 1 )"
  office="$(echo $fingerinfo | cut -d , -f 2 )"
  workphone="$(echo $fingerinfo | cut -d , -f 3 )"
  homephone="$(echo $fingerinfo | cut -d , -f 4 )"
}

###############################################
# Edit a user: single combined form for everything

edituser() {
  [ ! "$user" ] && return
  getsysteminfo

  # Build shell combo with the current shell pre-selected/first
  combo="$shell"
  IFS='!' read -ra shells <<< "$shelllist"
  for s in "${shells[@]}"; do
    [ "$s" != "$shell" ] && combo="${combo}!${s}"
  done

  result=$(yad "${YAD_COMMON[@]}" --width=520 --height=560 \
    --title "Edit user: $user" \
    --text "<big><b>$user</b></big>  (UID $uid)\nEdit any field below, then click Save." \
    --form --columns=1 \
    --field="Real Name" "$realname" \
    --field="User Name" "$user" \
    --field="Main Group" "$group" \
    --field="Home directory" "$homedir" \
    --field="Shell:CB" "$combo" \
    --field="Additional groups (space separated)" "$membership" \
    --field="Account expiry (YYYY-MM-DD, blank = never)" "$expiry" \
    --field="Office" "$office" \
    --field="Work phone" "$workphone" \
    --field="Home phone" "$homephone" \
    --field="Change password:CHK" FALSE \
    --field="Delete this user:CHK" FALSE \
    --button="Cancel:1" --button="Save:0" )
  rc=$?
  [ $rc -ne 0 ] && return

  IFS='|' read -r new_realname new_username new_group new_home new_shell new_membership \
    new_expiry new_office new_workphone new_homephone new_changepw new_delete <<< "$result"

  if [ "$new_delete" == "TRUE" ]; then
    killuser
    return
  fi

  local errors=""

  if [ "$new_username" != "$user" ] && [ "$new_username" ]; then
    message="$(usermod -l "$new_username" "$user" 2>&1)"
    if [ $? -eq 0 ]; then
      user="$new_username"
    else
      errors="${errors}Rename: $message\n"
    fi
  fi

  if [ "$new_realname" != "$realname" ]; then
    message="$(chfn -f "$new_realname" "$user" 2>&1)"
    [ $? -ne 0 ] && errors="${errors}Real name: $message\n"
  fi

  if [ "$new_group" != "$group" ] && [ "$new_group" ]; then
    message="$(usermod -g "$new_group" "$user" 2>&1)"
    [ $? -ne 0 ] && errors="${errors}Main group: $message\n"
  fi

  if [ "$new_home" != "$homedir" ] && [ "$new_home" ]; then
    message="$(usermod -d "$new_home" "$user" 2>&1)"
    if [ $? -eq 0 ]; then
      if [ -e "$new_home" ]; then
        chown -R "$user:$new_group" "$new_home" 2>/dev/null
      else
        cp -rf $skeleton "$new_home" 2>/dev/null
        chown -R "$user:$new_group" "$new_home" 2>/dev/null
      fi
    else
      errors="${errors}Home directory: $message\n"
    fi
  fi

  if [ "$new_shell" != "$shell" ] && [ "$new_shell" ]; then
    message="$(usermod -s "$new_shell" "$user" 2>&1)"
    [ $? -ne 0 ] && errors="${errors}Shell: $message\n"
  fi

  if [ "$new_membership" != "$membership" ]; then
    usermodmembership="$(echo $new_membership | tr " " "," )"
    message="$(usermod -G "$usermodmembership" "$user" 2>&1)"
    [ $? -ne 0 ] && errors="${errors}Membership: $message\n"
  fi

  if [ "$new_expiry" != "$expiry" ]; then
    usermod -e "" "$user" 2>/dev/null
    if [ "$new_expiry" ]; then
      message="$(usermod -e "$new_expiry" "$user" 2>&1)"
      [ $? -ne 0 ] && errors="${errors}Expiry: $message\n"
    fi
  fi

  if [ "$new_office" != "$office" ]; then
    message="$(chfn -r "$new_office" "$user" 2>&1)"
    [ $? -ne 0 ] && errors="${errors}Office: $message\n"
  fi

  if [ "$new_workphone" != "$workphone" ]; then
    message="$(chfn -w "$new_workphone" "$user" 2>&1)"
    [ $? -ne 0 ] && errors="${errors}Work phone: $message\n"
  fi

  if [ "$new_homephone" != "$homephone" ]; then
    message="$(chfn -h "$new_homephone" "$user" 2>&1)"
    [ $? -ne 0 ] && errors="${errors}Home phone: $message\n"
  fi

  if [ "$new_changepw" == "TRUE" ]; then
    setpw
  fi

  if [ "$errors" ]; then
    warn_box "Some changes could not be applied:\n\n$errors"
  else
    info_box "Changes saved for user <b>$user</b>."
  fi
}

###############################################
# Show group members

showgroupmembers() {
  gid="$(grep -e "^$group:.*$" $grpfile | cut -d: -f 3)"
  members="$(grep -e "^$group:.*$" $grpfile | cut -d: -f 4 | tr "," " ")"
  for u in $(grep -e "^.*:x:.*:$gid:.*$" $pwfile \
    | sed -e 's/^\(.*\):x:.*:.*:.*:.*:.*$/\1/' \
    | tr "\n" " ") ; do
    members="$(echo $members | sed -e "s/^\(.*\)$u\(.*\)$/\1\2/")"
    members="$members $u"
  done

  if [ "$members" ]; then
    local rowargs=()
    for u in $members; do
      uu="$(grep -e "^$u:.*$" $pwfile | cut -d: -f 3 )"
      rowargs+=("avatar-default" "$u" "$uu")
    done

    user=$(yad "${YAD_COMMON[@]}" --width=460 --height=420 \
      --title "Members of $group" \
      --text "<b>Members of group $group</b>" \
      --list --no-headers \
      --column "icon:IMG" --column "User" --column "UID" \
      --button="Back:1" --button="Edit:0" \
      --print-column=2 \
      "${rowargs[@]}")
    rc=$?
    user="${user%|*}"
    [ $rc -ne 0 ] && user=""
  else
    info_box "No user in group <b>$group</b>."
    user=""
  fi
}

###############################################
# Create new user

newuser() {
  result="nok"

  newuserresult=$(yad "${YAD_COMMON[@]}" --width=460 \
    --title "New user" \
    --text "<big><b>Create a new user</b></big>" \
    --form --columns=1 \
    --field="User name" "" \
    --field="Real name" "" \
    --field="Set password now:CHK" TRUE \
    --field="System / service account (no login, no home dir):CHK" FALSE \
    --button="Cancel:1" --button="Create:0")
  rc=$?
  [ $rc -ne 0 ] && return

  IFS='|' read -r newname newrealname setpwnow sysaccount <<< "$newuserresult"

  if [ "$newname" ]; then
    for membershipgroup in $(echo $useraddmembership | tr "," " ") ; do
      groupadd $membershipgroup 2>/dev/null
    done
    if [ "$sysaccount" != "TRUE" ]; then
      message="$(useradd -s $defaultshell -g $defaultgroup -m -k $skeleton -G $useraddmembership "$newname" 2>&1 && result="ok")"
      if [ ! $? = 0 ]; then
        warn_box "$message"
      else
        chfn -f "${newrealname:-$newname}" "$newname" 2>/dev/null
        chfn -r "Zenwalk users" "$newname" 2>/dev/null
        user="$newname"
        if [ "$setpwnow" == "TRUE" ]; then
          setpw
        fi
      fi
    else
      message="$(useradd -s $defaultshell -g nobody -r -M "$newname" 2>&1)"
      if [ ! $? = 0 ]; then
        warn_box "$message"
      else
        result="ok"
        user="$newname"
      fi
    fi
  fi
}

###############################################
# Password change

changepw(){
  maxcount=3
  minlength=5

  count=1 ; pw='_'
  while [ "${pw}" != "${password}" ]; do
    password="" ; pw='_'
    if [ $count -gt $maxcount ]; then
      echo -n ""
      break
    fi

    message="Enter new password for user ${1} (#${count} try):"
    [ $count -ge 2 ] && message="Passwords do not match. Enter password for user ${1} (#${count} try):"
    while [ "$(echo -n "${pw}" | wc --chars)" -lt ${minlength} ]; do
      pw="$(yad "${YAD_COMMON[@]}" --width=420 --title "Password" --text "${message}" --entry --hide-text --button="Cancel:1" --button="OK:0")"
      [ $? != 0 ] && return 1
      message="Password is too short (${minlength} chars minimum), please try another one:"
    done
    password="$pw" ; pw='_'
    message="Confirm new password for user ${1} (#${count} try):"
    pw="$(yad "${YAD_COMMON[@]}" --width=420 --title "Password" --text "${message}" --entry --hide-text --button="Cancel:1" --button="OK:0")"
    [ $? != 0 ] && return 1
    count=$((count+1))
  done

  echo -n $password
}

setpw() {
  [ "$user" ] && pass="$(changepw ${user})"
  [ $? == 1 ] && return 1
  if [ ! "$pass" ] ; then
    warn_box "Failed to set password for user \"${user}\"."
  else
    if [ "$(id -u)" == "0" ]; then
      [ "$user" ] && echo "${user}:${pass}" | /usr/sbin/chpasswd 1>/dev/null 2>&1 && result="ok"
    else
      [ "$user" ] && echo "${user}:${pass}" | /usr/bin/pkexec /usr/sbin/chpasswd 1>/dev/null 2>&1 && result="ok"
    fi
    [ "$result" == "ok" ] && info_box "Password changed for user <b>$user</b>."
  fi
}

###############################################
# Delete a user

killuser() {
  result="nok"
  if [ "$user" ]; then
    if [ "$user" == "root" ]; then
      warn_box "No way to delete $user."
      return 0
    else
      confirm_box "Delete user <b>$user</b>: are you sure? This cannot be undone."
      [ ! $? = 0 ] && return 0
      message="$(userdel -f $user 2>&1 && result="ok")"
      if [ ! $? = 0 ]; then
        warn_box "$message"
      else
        info_box "User <b>$user</b> deleted."
      fi
      return 0
    fi
  fi
}

###############################################
# Delete a group

killgroup() {
  result="nok"
  if [ "$group" ]; then
    if [ "$group" == "root" ]; then
      warn_box "No way to delete $group."
      return 0
    else
      confirm_box "Delete group <b>$group</b>: are you sure?"
      [ ! $? = 0 ] && return 0
      message="$(groupdel $group 2>&1 && result="ok")"
      if [ ! $? = 0 ] ; then
        warn_box "$message"
      else
        info_box "Group <b>$group</b> deleted."
      fi
      return 0
    fi
  fi
}

###############################################
# Edit a group (rename or delete)

editgroup() {
  result="nok"
  current="$group"

  editgroupresult=$(yad "${YAD_COMMON[@]}" --width=440 \
    --title "Edit group" \
    --text "<big><b>Group: $current</b></big>" \
    --form --columns=1 \
    --field="Group name" "$group" \
    --field="Delete this group:CHK" FALSE \
    --button="Cancel:1" --button="Save:0")
  rc=$?
  [ $rc -ne 0 ] && { current=""; return; }

  group="$(echo "$editgroupresult" | cut -d'|' -f1)"
  delcheck="$(echo "$editgroupresult" | cut -d'|' -f2)"

  if [ "$delcheck" == "TRUE" ]; then
    group=$current
    killgroup
  else
    if [ "$current" ] && [ "$group" ] && [ "$group" != "$current" ]; then
      message="$(groupmod -n $group $current 2>&1 && result="ok")"
      [ ! $? = 0 ] && warn_box "$message"
    fi
  fi
  current=""
}

###############################################
# Create a new group

newgroup() {
  result="nok"
  group=$(yad "${YAD_COMMON[@]}" --width=420 \
    --title "New group" \
    --text "<big><b>Create a new group</b></big>" \
    --entry --entry-text="" --entry-label="Group name:" \
    --button="Cancel:1" --button="Create:0")

  if [ "$group" ]; then
    message="$(groupadd $group 2>&1 && result="ok")"
    [ ! $? = 0 ] && warn_box "$message"
  fi
}

###############################################
# Quick options for unprivileged user

if [ "$1" ]; then
  case "$1" in
    'passwd')
    result="nok"
    user=$2
    [ "$user" == "" ] && user="$(whoami)"
    setpw
    exit
    ;;
  esac
fi

if [ "$(id -u)" != "0" ]; then
  warn_box "You need to be root to run $(basename $0)."
  exit
fi

# Quick options for root
if [ "$1" ]; then
  case "$1" in
    'adduser')
      info_box "Ready to create a new user."
      user=""
      newuser
      while [ "$result" != "ok" ] ; do
        confirm_box "User creation failed or was cancelled. Retry?"
        [ ! $? = 0 ] && break
        user=""
        newuser
      done
      if [ "$result" == "ok" ]; then
        edituser
      fi
    exit 0
    ;;
  esac
fi

###############################################
# main()

while [ "1" = "1" ] ; do
  promptaction
  case $mode in
    "List users")
      while [ "1" = "1" ] ; do
        user=""
        showusers
        if [ "$user" ]; then
          edituser
        else
          break
        fi
      done
      ;;
    "New user")
      user=""
      newuser
      if [ "$result" == "ok" ]; then
        edituser
      fi
      ;;
    "List groups")
      while [ "1" = "1" ] ; do
        group=""
        showgroups
        if [ ! "$group" ]; then
          break
        fi
        if [ "$check" == "checked" ]; then
          showgroupmembers
          if [ "$user" ]; then
            edituser
          fi
        else
          editgroup
        fi
      done
      ;;
    "New group")
      group=""
      newgroup
      ;;
    none)
      exit 0
  esac
done

# end
