# CH-J Server Manager - fixed remote helper for the chj.users capability (POSIX sh).
# Core runs this text with "sh -c SCRIPT chj-users OPERATION ARGS...". Plugins cannot supply code.
# Arguments are validated by Core and again here. Secrets and file contents never appear in argv:
# they arrive as base64 stdin frames ("CHJ<TAG>:<base64>") only after the READY marker.
set -u
umask 077
LC_ALL=C; export LC_ALL
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH
SSHD_MAIN=/etc/ssh/sshd_config
SSHD_DROPIN=/etc/ssh/sshd_config.d
SSHD_MANAGED=$SSHD_DROPIN/01-chj-users.conf
STATE_DIR=/var/lib/chj-server-manager/users
FRAME=
op=${1:-}
[ $# -gt 0 ] && shift
printf '\036CHJ_READY\n'

fail() { printf '\036CHJ_ERR=%s\n' "$1"; [ $# -gt 1 ] && printf '%s\n' "$2" | head -c 2000 >&2; exit 3; }
section() { printf '\036CHJ_SECTION=%s\n' "$1"; }
oneline() { tr '\r\n|\036' '    ' | cut -c1-400; }
step() { printf '\036CHJ_STEP=%s|%s|%s\n' "$1" "$2" "$(printf '%s' "${3:-}" | oneline)"; }
is_root() { [ "$(id -u)" = 0 ]; }
need_root() { is_root || fail PRIVILEGE_REQUIRED "Root privileges are required."; }
valid_name() { case $1 in ''|[!a-z_]*|*[!a-z0-9_-]*) return 1 ;; esac; [ ${#1} -le 32 ]; }
valid_addr() { case $1 in ''|*[!0-9a-fA-F:.]*) return 1 ;; esac; [ ${#1} -le 45 ]; }
valid_path() {
  case $1 in /*) ;; *) return 1 ;; esac
  case $1 in *[!A-Za-z0-9._/@+-]*|*/../*|*/..|*/./*|*//*) return 1 ;; esac
  [ ${#1} -le 255 ]
}
have() { command -v "$1" >/dev/null 2>&1; }
uid_min() {
  value=$(awk '$1=="UID_MIN"{print $2; exit}' /etc/login.defs 2>/dev/null)
  case $value in ''|*[!0-9]*) value=1000 ;; esac
  printf '%s' "$value"
}
b64d() {
  if printf 'YQ==' | base64 -d >/dev/null 2>&1; then base64 -d
  elif printf 'YQ==' | openssl base64 -d -A >/dev/null 2>&1; then openssl base64 -d -A
  else return 127; fi
}
b64e() { base64 | tr -d '\r\n'; }
sha256_of() {
  if [ -f "$1" ]; then
    if have sha256sum; then sha256sum < "$1" | cut -c1-64; else openssl dgst -sha256 < "$1" | sed 's/^.*= *//'; fi
  else printf 'absent'; fi
}
read_frame() {
  FRAME=
  while IFS= read -r line; do
    case $line in "CHJ$1:"*) FRAME=${line#"CHJ$1:"}; return 0 ;; esac
  done
  return 1
}
passwd_entry() { getent passwd "$1" 2>/dev/null | head -n 1; }
user_uid() { passwd_entry "$1" | cut -d: -f3; }
user_home() { passwd_entry "$1" | cut -d: -f6; }
group_members() { getent group "$1" 2>/dev/null | cut -d: -f4 | tr ',' '\n'; }
is_member() { group_members "$2" | grep -qx "$1"; }

# Destructive account changes are refused for root, system accounts and the sudo caller.
protect() {
  valid_name "$1" || fail NAME_INVALID "Invalid account name."
  uid=$(user_uid "$1")
  [ -n "$uid" ] || fail USER_NOT_FOUND "The account does not exist."
  [ "$uid" -ne 0 ] || fail PROTECTED_USER "The root account is protected."
  [ "$uid" -ge "$(uid_min)" ] && [ "$uid" -ne 65534 ] || fail PROTECTED_USER "System accounts are protected."
  [ "$1" != "${SUDO_USER:-}" ] || fail PROTECTED_USER "The account of the active SSH session is protected."
}

# Runs a command as the target account so file permissions, not root, govern access.
# BusyBox getent (Alpine) has no shadow database; root can read /etc/shadow directly. Only Core-side states leave the server.
shadow_db() {
  out=$(getent shadow 2>/dev/null)
  if [ -n "$out" ]; then printf '%s\n' "$out"; else cat /etc/shadow 2>/dev/null; fi
}
run_as() {
  target=$1; shift
  if ! is_root; then
    [ "$(id -un)" = "$target" ] || return 125
    "$@"; return $?
  fi
  if have runuser; then runuser -u "$target" -- "$@"
  # BusyBox setpriv (Alpine) has no --reuid; only the util-linux variant is used.
  elif have setpriv && setpriv --help 2>&1 | grep -q -e '--reuid'; then setpriv --reuid="$(id -u "$target")" --regid="$(id -g "$target")" --init-groups -- "$@"
  else
    # BusyBox su (Alpine) and util-linux su hand extra arguments to the shell differently, so the command is passed
    # as one single-quoted string instead of positional parameters.
    cmd=exec
    for arg in "$@"; do cmd="$cmd '$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")'"; done
    su -s /bin/sh -c "$cmd" "$target"
  fi
}

sudo_summary() {
  have sudo || { printf 'unavailable'; return; }
  sudo -n -l -U "$1" 2>/dev/null | awk '
    /is not allowed to run sudo/ { none=1 }
    /may run the following commands/ { rules=1; next }
    rules && /^[ \t]+\(/ {
      line=$0; nopw=(line ~ /NOPASSWD:/)
      sub(/^[ \t]+\([^)]*\)[ \t]*/, "", line)
      while (line ~ /^[A-Z_]+:[ \t]*/) sub(/^[A-Z_]+:[ \t]*/, "", line)
      gsub(/[ \t]+$/, "", line)
      if (line == "ALL") { full=1; if (nopw) fullnopw=1 } else any=1
    }
    END { if (full) print (fullnopw ? "full-nopasswd" : "full"); else if (any) print "limited"; else print "none" }'
}
sudoers_group_rule() {
  for file in /etc/sudoers /etc/sudoers.d/*; do
    [ -f "$file" ] || continue
    grep -hE "^[[:space:]]*%$1[[:space:]]" "$file" 2>/dev/null
  done | grep -q .
}
admin_group() {
  for group in sudo wheel admin; do
    getent group "$group" >/dev/null 2>&1 && sudoers_group_rule "$group" && { printf '%s' "$group"; return 0; }
  done
  return 1
}

sshd_bin() {
  if have sshd; then command -v sshd
  elif [ -x /usr/sbin/sshd ]; then printf '/usr/sbin/sshd'; fi
}
# "ok" only when sshd_config includes sshd_config.d before its first Match block.
layout_state() {
  [ -f "$SSHD_MAIN" ] || { printf 'missing-main'; return; }
  awk '{ l=tolower($0); sub(/^[ \t]+/, "", l) }
    l ~ /^include[ \t]/ && l ~ /sshd_config\.d\/\*/ && !inc { inc=NR }
    l ~ /^match[ \t]/ && !m { m=NR }
    END { if (inc && (!m || inc < m)) print "ok"; else if (inc) print "include-after-match"; else print "include-missing" }' "$SSHD_MAIN" 2>/dev/null || printf 'unreadable'
}
service_state() {
  if have systemctl && [ -d /run/systemd/system ]; then
    for unit in ssh.service sshd.service; do
      [ "$(systemctl is-active "$unit" 2>/dev/null)" = active ] && { printf 'systemd:%s:active' "$unit"; return; }
    done
    for unit in ssh.socket sshd.socket; do
      [ "$(systemctl is-active "$unit" 2>/dev/null)" = active ] && { printf 'systemd:%s:active' "$unit"; return; }
    done
    printf 'systemd:none:inactive'; return
  fi
  # OpenRC (Alpine) also writes /run/sshd.pid, so it is checked before the bare pid file.
  if have rc-service; then
    rc-service sshd status >/dev/null 2>&1 && { printf 'openrc:sshd:active'; return; }
  fi
  for file in /run/sshd.pid /var/run/sshd.pid; do
    [ -r "$file" ] || continue
    pid=$(head -n 1 "$file" 2>/dev/null)
    case $pid in ''|*[!0-9]*) continue ;; esac
    if kill -0 "$pid" 2>/dev/null; then printf 'pidfile:%s:active' "$file"; else printf 'pidfile:%s:unknown' "$file"; fi
    return
  done
  printf 'unknown:none:unknown'
}
# After SIGHUP sshd re-executes; some builds come back under a new PID and rewrite the pid file.
# Success = a live sshd listed in the pid file within 10 s.
sshd_running_after_reload() {
  tries=0
  while [ "$tries" -lt 20 ]; do
    for file in /run/sshd.pid /var/run/sshd.pid; do
      [ -r "$file" ] || continue
      pid=$(head -n 1 "$file" 2>/dev/null)
      case $pid in ''|*[!0-9]*) continue ;; esac
      kill -0 "$pid" 2>/dev/null && return 0
    done
    sleep 0.5 2>/dev/null || sleep 1
    tries=$((tries + 1))
  done
  return 1
}
reload_sshd() {
  state=$(service_state); kind=${state%%:*}; rest=${state#*:}; unit=${rest%%:*}
  case $kind in
    systemd)
      case $unit in
        *.socket) printf 'socket-activated'; return 0 ;;
        none) printf 'sshd service is not active'; return 1 ;;
      esac
      systemctl reload "$unit" 2>&1 ;;
    pidfile)
      pid=$(head -n 1 "$unit"); kill -HUP "$pid" 2>&1 || return 1
      sleep 1; sshd_running_after_reload || { printf 'sshd stopped after reload'; return 1; } ;;
    openrc)
      rc-service sshd reload 2>&1 || return 1
      sleep 1; sshd_running_after_reload || rc-service sshd status >/dev/null 2>&1 || { printf 'sshd stopped after reload'; return 1; } ;;
    *) printf 'sshd service manager was not recognised'; return 1 ;;
  esac
}
effective() {
  # $1 user, $2 address; prints only the authentication and access keywords Core evaluates.
  "$SSHD" -T -C "user=$1,host=$2,addr=$2" 2>&1 | grep -E '^(passwordauthentication|kbdinteractiveauthentication|challengeresponseauthentication|pubkeyauthentication|authenticationmethods|permitrootlogin|denyusers|allowusers|denygroups|allowgroups|authorizedkeysfile|authorizedkeyscommand|addressfamily|listenaddress|port|usepam|strictmodes) |^[^a-z]' | head -n 300
}
expand_keys_path() {
  awk -v s="$1" -v h="$2" -v u="$3" -v id="$4" 'BEGIN {
    out=""; n=length(s)
    for (i=1; i<=n; i++) {
      c=substr(s,i,1)
      if (c=="%" && i<n) { d=substr(s,i+1,1); i++
        if (d=="%") out=out "%"; else if (d=="h") out=out h; else if (d=="u") out=out u; else if (d=="U") out=out id; else { print "UNSUPPORTED"; exit }
      } else out=out c
    }
    if (substr(out,1,1)!="/") out=h "/" out
    print out }'
}
file_meta() {
  if [ -L "$1" ]; then printf 'symlink'
  elif [ -e "$1" ]; then stat -c '%U:%G:%a:%F' "$1" 2>/dev/null || printf 'unknown'
  else printf 'absent'; fi
}

print_managed() {
  section managed
  if [ -L "$SSHD_MANAGED" ]; then printf 'unsafe=1\n'
  elif [ -f "$SSHD_MANAGED" ] && [ -r "$SSHD_MANAGED" ]; then
    printf 'sha256=%s\ndata=' "$(sha256_of "$SSHD_MANAGED")"; b64e < "$SSHD_MANAGED"; printf '\n'
  elif [ -e "$SSHD_MANAGED" ]; then printf 'unreadable=1\n'
  else printf 'sha256=absent\n'; fi
}

READ_KEYS='p=$1
if [ -L "$p" ]; then printf "type=symlink\n"; exit 0; fi
if [ ! -e "$p" ]; then if [ -d "${p%/*}" ]; then printf "type=absent\n"; else printf "type=absent-dir\n"; fi; exit 0; fi
if [ ! -f "$p" ]; then printf "type=other\n"; exit 0; fi
if [ ! -r "$p" ]; then printf "type=unreadable\n"; exit 0; fi
size=$(wc -c < "$p" | tr -d " ")
printf "type=file\nsize=%s\n" "$size"
[ "$size" -le 262144 ] || { printf "tooLarge=1\n"; exit 0; }
printf "data="; base64 < "$p" | tr -d "\r\n"; printf "\n"'

# Executed as the key owner. Exit codes are mapped by Core.
WRITE_KEYS='set -u; target=$1; expected=$2; dir=${target%/*}
[ ! -L "$dir" ] || exit 21
if [ ! -e "$dir" ]; then mkdir -m 700 -- "$dir" || exit 22; fi
[ -d "$dir" ] || exit 23
[ ! -L "$target" ] || exit 24
if [ -e "$target" ] && [ ! -f "$target" ]; then exit 25; fi
if [ -f "$target" ]; then
  if command -v sha256sum >/dev/null 2>&1; then current=$(sha256sum < "$target" | cut -c1-64); else current=$(openssl dgst -sha256 < "$target" | sed "s/^.*= *//"); fi
else current=absent; fi
[ "$current" = "$expected" ] || exit 26
tmp=$(mktemp "$dir/.authorized_keys.chj-XXXXXX") || exit 27
if ! cat > "$tmp"; then rm -f -- "$tmp"; exit 28; fi
chmod 600 "$tmp" || { rm -f -- "$tmp"; exit 28; }
mv -f -- "$tmp" "$target" || { rm -f -- "$tmp"; exit 29; }'

op_probe() {
  printf 'user=%s\nuid=%s\n' "$(id -un)" "$(id -u)"
  printf 'groups=%s\n' "$(id -Gn 2>/dev/null | tr ' ' ',')"
  printf 'client=%s\n' "${SSH_CLIENT%% *}"
  if have sudo; then
    out=$(sudo -n -p '' true 2>&1 </dev/null); code=$?
    printf 'sudoInstalled=1\nsudoNonInteractive=%s\nsudoMessage=%s\n' "$code" "$(printf '%s' "$out" | head -n 1 | oneline)"
  else
    printf 'sudoInstalled=0\n'
  fi
}

op_inventory() {
  section meta
  printf 'whoami=%s\nuid=%s\nuidMin=%s\n' "$(id -un)" "$(id -u)" "$(uid_min)"
  printf 'os=%s\n' "$(sed -n 's/^PRETTY_NAME=//p' /etc/os-release 2>/dev/null | head -n 1 | tr -d '"' | oneline)"
  printf 'osId=%s\n' "$(sed -n 's/^ID=//p' /etc/os-release 2>/dev/null | head -n 1 | tr -d '"' | oneline)"
  for tool in useradd usermod userdel gpasswd chpasswd runuser setpriv sudo systemctl pgrep base64; do
    have "$tool" && printf 'tool=%s\n' "$tool"
  done
  SSHD=$(sshd_bin); [ -n "$SSHD" ] && printf 'tool=sshd\n'
  printf 'layout=%s\n' "$(layout_state)"
  printf 'managedPath=%s\n' "$SSHD_MANAGED"
  section passwd; getent passwd | head -n 5000
  section group; getent group | head -n 5000
  section shells; grep -E '^/' /etc/shells 2>/dev/null
  print_managed
  if is_root; then
    section admingroups
    for group in sudo wheel admin; do
      getent group "$group" >/dev/null 2>&1 && sudoers_group_rule "$group" && printf '%s\n' "$group"
    done
    section shadow
    # Password hashes never leave the server: only a coarse state is printed.
    shadow_db | awk -F: '{
      h=$2; s="set"
      if (h=="") s="empty"
      else if (h=="!" || h=="!!" || h=="!*" || h=="*" || h=="*LK*") s="none"
      else if (substr(h,1,1)=="!") s="locked"
      print $1 ":" s ":" $3 ":" $8 }'
    section sudo
    min=$(uid_min)
    getent passwd | while IFS=: read -r name _ uid _; do
      case $uid in ''|*[!0-9]*) continue ;; esac
      if [ "$uid" -eq 0 ] || { [ "$uid" -ge "$min" ] && [ "$uid" -ne 65534 ]; } || is_member "$name" sudo || is_member "$name" wheel || is_member "$name" admin; then
        printf '%s:%s\n' "$name" "$(sudo_summary "$name")"
      fi
    done | head -n 500
  fi
  section end
}

op_detail() {
  [ $# -eq 3 ] || fail ARGUMENTS_INVALID "Invalid arguments."
  name=$1; addr4=$2; addr6=$3
  valid_name "$name" || fail NAME_INVALID "Invalid account name."
  { [ -z "$addr4" ] || valid_addr "$addr4"; } && { [ -z "$addr6" ] || valid_addr "$addr6"; } || fail ARGUMENTS_INVALID "Invalid address."
  entry=$(passwd_entry "$name"); [ -n "$entry" ] || fail USER_NOT_FOUND "The account does not exist."
  uid=$(printf '%s' "$entry" | cut -d: -f3); home=$(printf '%s' "$entry" | cut -d: -f6)
  section account
  printf 'passwd=%s\n' "$entry"
  printf 'uidMin=%s\n' "$(uid_min)"
  printf 'groups=%s\n' "$(id -Gn "$name" 2>/dev/null | tr ' ' ',')"
  if have pgrep; then printf 'processes=%s\n' "$(pgrep -u "$name" 2>/dev/null | wc -l | tr -d ' ')"; fi
  printf 'homeMeta=%s\n' "$(file_meta "$home")"
  if is_root; then
    printf 'shadow=%s\n' "$(shadow_db | awk -F: -v n="$name" '$1==n' | awk -F: '{
      h=$2; s="set"; if (h=="") s="empty"; else if (h=="!" || h=="!!" || h=="!*" || h=="*" || h=="*LK*") s="none"; else if (substr(h,1,1)=="!") s="locked"
      print s ":" $3 ":" $8 }')"
    printf 'sudo=%s\n' "$(sudo_summary "$name")"
  fi
  section ssh
  SSHD=$(sshd_bin)
  akf=".ssh/authorized_keys .ssh/authorized_keys2"
  if [ -z "$SSHD" ]; then printf 'sshd=missing\n'
  else
    printf 'sshd=%s\nversion=%s\n' "$SSHD" "$("$SSHD" -V 2>&1 | head -n 1 | oneline)"
    printf 'layout=%s\nservice=%s\n' "$(layout_state)" "$(service_state)"
    if is_root; then
      out=$("$SSHD" -t 2>&1); code=$?
      printf 'syntax=%s\nsyntaxMessage=%s\n' "$code" "$(printf '%s' "$out" | head -n 3 | oneline)"
      if [ -n "$addr4" ]; then section effective4; effective "$name" "$addr4"; fi
      if [ -n "$addr6" ]; then section effective6; effective "$name" "$addr6"; fi
      configured=$(effective "$name" "${addr4:-${addr6:-192.0.2.10}}" | sed -n 's/^authorizedkeysfile //p' | head -n 1)
      [ -n "$configured" ] && akf=$configured
    fi
  fi
  print_managed
  section keys
  printf 'home=%s\n' "$home"
  set -f
  count=0
  for spec in $akf; do
    count=$((count + 1)); [ "$count" -le 4 ] || break
    [ "$spec" != none ] || continue
    path=$(expand_keys_path "$spec" "$home" "$name" "$uid")
    printf '\036CHJ_KEYFILE=%s\n' "$path"
    printf 'spec=%s\n' "$spec"
    case $path in UNSUPPORTED) printf 'type=unsupported\n'; continue ;; esac
    printf 'meta=%s\ndirMeta=%s\n' "$(file_meta "$path")" "$(file_meta "${path%/*}")"
    run_as "$name" sh -c "$READ_KEYS" chj-keys "$path" 2>/dev/null || printf 'type=unreadable\n'
  done
  set +f
  section end
}

op_keys_write() {
  [ $# -eq 3 ] || fail ARGUMENTS_INVALID "Invalid arguments."
  name=$1; path=$2; expected=$3
  valid_name "$name" || fail NAME_INVALID "Invalid account name."
  valid_path "$path" || fail KEYS_PATH_UNSUPPORTED "The authorized_keys path is not supported."
  case $expected in absent|[0-9a-f]*) ;; *) fail ARGUMENTS_INVALID "Invalid checksum." ;; esac
  uid=$(user_uid "$name"); [ -n "$uid" ] || fail USER_NOT_FOUND "The account does not exist."
  home=$(user_home "$name")
  valid_path "$home" && [ "$home" != / ] || fail KEYS_PATH_UNSUPPORTED "The home directory is not usable."
  case $path in "$home"/*) ;; *) fail KEYS_PATH_UNSUPPORTED "Only key files inside the home directory are managed." ;; esac
  read_frame DATA || fail PROTOCOL_ERROR "Key data was not received."
  work=$(mktemp -d) || fail TEMPORARY_FAILED "Temporary directory could not be created."
  trap 'rm -rf -- "$work"' EXIT
  printf '%s' "$FRAME" | b64d > "$work/data" || fail PROTOCOL_ERROR "Key data could not be decoded."
  run_as "$name" sh -c "$WRITE_KEYS" chj-keys "$path" "$expected" < "$work/data"
  code=$?
  case $code in
    0) ;;
    21|24) fail KEYS_SYMLINK "The key file or its directory is a symbolic link; it was not changed." ;;
    22|23) fail KEYS_DIRECTORY_FAILED "The .ssh directory could not be created or is not a directory." ;;
    25) fail KEYS_NOT_REGULAR "The key file is not a regular file." ;;
    26) fail KEYS_CHANGED "The key file changed after it was read. Reload and try again." ;;
    125) fail PRIVILEGE_REQUIRED "Root privileges are required for keys of another account." ;;
    *) fail KEYS_WRITE_FAILED "The key file could not be written (exit $code)." ;;
  esac
  if is_root && have restorecon; then restorecon -F "${path%/*}" "$path" >/dev/null 2>&1 || true; fi
  printf 'sha256=%s\nmeta=%s\ndirMeta=%s\n' "$(run_as "$name" sh -c 'if command -v sha256sum >/dev/null 2>&1; then sha256sum < "$1" | cut -c1-64; else openssl dgst -sha256 < "$1" | sed "s/^.*= *//"; fi' chj-keys "$path")" "$(file_meta "$path")" "$(file_meta "${path%/*}")"
  printf '\036CHJ_RESULT=ok\n'
}

check_shell() {
  valid_path "$1" || fail SHELL_INVALID "Invalid shell path."
  [ -x "$1" ] || fail SHELL_INVALID "The shell does not exist or is not executable."
  case ${1##*/} in nologin|false) return 0 ;; esac
  grep -qxF "$1" /etc/shells 2>/dev/null || fail SHELL_INVALID "The shell is not listed in /etc/shells."
}
set_password() {
  have chpasswd || { ERRMSG="chpasswd is not available"; return 127; }
  read_frame SECRET || { ERRMSG="password was not received"; return 2; }
  secret=$(printf '%s' "$FRAME" | b64d) || { FRAME=; ERRMSG="password could not be decoded"; return 3; }
  FRAME=
  [ -n "$secret" ] || { ERRMSG="empty password"; return 4; }
  ERRMSG=$(printf '%s:%s\n' "$1" "$secret" | chpasswd 2>&1); code=$?
  secret=
  return $code
}
grant_sudo() {
  group=$(admin_group) || { ERRMSG="No sudo, wheel or admin group is referenced by sudoers; sudoers was not changed."; return 2; }
  ERRMSG=$(usermod -a -G "$group" -- "$1" 2>&1) || return 1
  ERRMSG="group $group"
}
revoke_sudo() {
  removed=
  for group in sudo wheel admin; do
    is_member "$1" "$group" || continue
    if have gpasswd; then ERRMSG=$(gpasswd -d "$1" "$group" 2>&1) || return 1
    elif have deluser; then ERRMSG=$(deluser "$1" "$group" 2>&1) || return 1
    else ERRMSG="gpasswd is not available"; return 127; fi
    removed="$removed $group"
  done
  ERRMSG="removed from:${removed:- none}"
}

op_create() {
  [ $# -eq 9 ] || fail ARGUMENTS_INVALID "Invalid arguments."
  name=$1; shell=$2; home=$3; mkhome=$4; primary=$5; groups=$6; comment=$7; admin=$8; withpw=$9
  need_root
  valid_name "$name" || fail NAME_INVALID "Invalid account name."
  have useradd || fail TOOL_MISSING "useradd is not available on this server."
  [ -z "$(passwd_entry "$name")" ] || fail USER_EXISTS "The account already exists."
  if [ -z "$primary" ] && getent group "$name" >/dev/null 2>&1; then fail GROUP_EXISTS "A group with this name already exists; select a primary group."; fi
  check_shell "$shell"
  if [ -n "$home" ]; then valid_path "$home" || fail HOME_INVALID "Invalid home directory."; fi
  if [ -n "$home" ] && [ "$mkhome" = 1 ] && [ -e "$home" ]; then fail HOME_EXISTS "The home directory already exists."; fi
  if [ -n "$primary" ]; then valid_name "$primary" && getent group "$primary" >/dev/null 2>&1 || fail GROUP_NOT_FOUND "Primary group does not exist."; fi
  for group in $(printf '%s' "$groups" | tr ',' ' '); do
    valid_name "$group" && getent group "$group" >/dev/null 2>&1 || fail GROUP_NOT_FOUND "Group $group does not exist."
  done
  set -- -s "$shell"
  if [ "$mkhome" = 1 ]; then set -- "$@" -m; else set -- "$@" -M; fi
  [ -n "$home" ] && set -- "$@" -d "$home"
  if [ -n "$primary" ]; then set -- "$@" -g "$primary"; else set -- "$@" -U; fi
  [ -n "$groups" ] && set -- "$@" -G "$groups"
  [ -n "$comment" ] && set -- "$@" -c "$comment"
  out=$(useradd "$@" -- "$name" 2>&1) || fail USERADD_FAILED "$out"
  step create ok "$out"
  result=ok
  if [ "$withpw" = 1 ]; then
    if set_password "$name"; then step password ok; else step password failed "$ERRMSG"; result=partial; fi
  fi
  if [ "$admin" = 1 ]; then
    if grant_sudo "$name"; then step sudo ok "$ERRMSG"; else step sudo failed "$ERRMSG"; result=partial; fi
  fi
  printf '\036CHJ_RESULT=%s\n' "$result"
}

op_account() {
  [ $# -ge 2 ] || fail ARGUMENTS_INVALID "Invalid arguments."
  action=$1; name=$2; value=${3:-}; flag=${4:-0}
  need_root
  protect "$name"
  have usermod || fail TOOL_MISSING "usermod is not available on this server."
  case $action in
    lock) out=$(usermod -L -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
    unlock) out=$(usermod -U -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
    expire) out=$(usermod -e 1 -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
    unexpire) out=$(usermod -e '' -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
    password) set_password "$name" || fail PASSWORD_FAILED "$ERRMSG" ;;
    shell) check_shell "$value"; out=$(usermod -s "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
    comment) out=$(usermod -c "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
    home)
      valid_path "$value" && [ "$value" != / ] || fail HOME_INVALID "Invalid home directory."
      if [ "$flag" = 1 ]; then
        [ ! -e "$value" ] || fail HOME_EXISTS "The target directory already exists."
        out=$(usermod -d "$value" -m -- "$name" 2>&1) || fail USERMOD_FAILED "$out"
      else out=$(usermod -d "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out"; fi ;;
    primary)
      valid_name "$value" && getent group "$value" >/dev/null 2>&1 || fail GROUP_NOT_FOUND "The group does not exist."
      out=$(usermod -g "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
    group-add)
      valid_name "$value" && getent group "$value" >/dev/null 2>&1 || fail GROUP_NOT_FOUND "The group does not exist."
      out=$(usermod -a -G "$value" -- "$name" 2>&1) || fail USERMOD_FAILED "$out" ;;
    group-remove)
      valid_name "$value" || fail GROUP_NOT_FOUND "Invalid group."
      is_member "$name" "$value" || fail GROUP_NOT_MEMBER "The account is not a supplementary member of this group."
      if have gpasswd; then out=$(gpasswd -d "$name" "$value" 2>&1) || fail GROUP_CHANGE_FAILED "$out"
      elif have deluser; then out=$(deluser "$name" "$value" 2>&1) || fail GROUP_CHANGE_FAILED "$out"
      else fail TOOL_MISSING "gpasswd is not available on this server."; fi ;;
    sudo-grant) grant_sudo "$name" || fail SUDO_GROUP_UNAVAILABLE "$ERRMSG"; step sudo ok "$ERRMSG" ;;
    sudo-revoke) revoke_sudo "$name" || fail GROUP_CHANGE_FAILED "$ERRMSG"; step sudo ok "$ERRMSG" ;;
    *) fail ACTION_INVALID "Unsupported account action." ;;
  esac
  [ "$action" = sudo-grant ] || [ "$action" = sudo-revoke ] || step "$action" ok
  if [ "$action" = sudo-grant ] || [ "$action" = sudo-revoke ]; then printf '\036CHJ_SUDO=%s\n' "$(sudo_summary "$name")"; fi
  printf '\036CHJ_RESULT=ok\n'
}

op_delete() {
  [ $# -eq 2 ] || fail ARGUMENTS_INVALID "Invalid arguments."
  name=$1; remove_home=$2
  need_root
  protect "$name"
  have userdel || fail TOOL_MISSING "userdel is not available on this server."
  if have pgrep && pgrep -u "$name" >/dev/null 2>&1; then fail USER_HAS_PROCESSES "The account still has running processes."; fi
  if [ "$remove_home" = 1 ]; then out=$(userdel -r -- "$name" 2>&1); code=$?; else out=$(userdel -- "$name" 2>&1); code=$?; fi
  [ -z "$(passwd_entry "$name")" ] || fail USERDEL_FAILED "$out"
  if [ "$code" -eq 0 ]; then step delete ok; printf '\036CHJ_RESULT=ok\n'
  else step delete ok; step home failed "$out"; printf '\036CHJ_RESULT=partial\n'; fi
}

# Staged and confirmed change of the CH-J managed sshd drop-in. Core decides via stdin:
# COMMIT reloads sshd, CONFIRM keeps it; ROLLBACK, EOF or the watchdog restores the previous file.
op_ssh_apply() {
  [ $# -ge 2 ] || fail ARGUMENTS_INVALID "Invalid arguments."
  expected=$1; timeout=$2; shift 2
  case $expected in absent|[0-9a-f]*) ;; *) fail ARGUMENTS_INVALID "Invalid checksum." ;; esac
  case $timeout in ''|*[!0-9]*) fail ARGUMENTS_INVALID "Invalid timeout." ;; esac
  for tuple in "$@"; do valid_name "${tuple%%|*}" && valid_addr "${tuple#*|}" || fail ARGUMENTS_INVALID "Invalid verification target."; done
  need_root
  SSHD=$(sshd_bin); [ -n "$SSHD" ] || fail SSHD_MISSING "sshd was not found."
  [ "$(layout_state)" = ok ] || fail SSH_LAYOUT_UNSUPPORTED "sshd_config does not include sshd_config.d before its first Match block."
  [ -d "$SSHD_DROPIN" ] && [ ! -L "$SSHD_DROPIN" ] || fail SSH_LAYOUT_UNSUPPORTED "sshd_config.d is missing or is a symbolic link."
  if [ -e "$SSHD_MANAGED" ] || [ -L "$SSHD_MANAGED" ]; then
    [ -f "$SSHD_MANAGED" ] && [ ! -L "$SSHD_MANAGED" ] || fail SSH_MANAGED_FILE_UNSAFE "The managed file is not a regular file."
  fi
  [ "$(sha256_of "$SSHD_MANAGED")" = "$expected" ] || fail SSH_POLICY_CHANGED "The managed SSH policy changed after it was read."
  out=$("$SSHD" -t 2>&1) || fail SSH_CONFIG_ALREADY_INVALID "$out"
  read_frame DATA || fail PROTOCOL_ERROR "Policy data was not received."
  mkdir -p "$STATE_DIR" && chmod 700 "$STATE_DIR" || fail STATE_FAILED "State directory could not be created."
  work=$(mktemp -d "$STATE_DIR/apply.XXXXXX") || fail STATE_FAILED "Work directory could not be created."
  printf '%s' "$FRAME" | b64d > "$work/new" || { rm -rf -- "$work"; fail PROTOCOL_ERROR "Policy data could not be decoded."; }
  stamp=$(date -u +%Y%m%dT%H%M%SZ)
  if [ -f "$SSHD_MANAGED" ]; then had=1; backup="$STATE_DIR/sshd-managed.$stamp.conf.bak"; cp -p "$SSHD_MANAGED" "$backup" || fail STATE_FAILED "Backup failed."
  else had=0; backup="$STATE_DIR/sshd-managed.$stamp.absent"; : > "$backup"; fi
  ls -1t "$STATE_DIR"/sshd-managed.* 2>/dev/null | tail -n +11 | while IFS= read -r old; do rm -f -- "$old"; done
  restore() {
    if [ "$had" = 1 ]; then cp -p "$backup" "$SSHD_DROPIN/.chj-users.restore" && mv -f "$SSHD_DROPIN/.chj-users.restore" "$SSHD_MANAGED"
    else rm -f -- "$SSHD_MANAGED"; fi
  }
  claim() { mv "$work/pending" "$work/claimed" 2>/dev/null; }
  for tuple in "$@"; do printf '\036CHJ_BEFORE=%s\n' "$tuple"; effective "${tuple%%|*}" "${tuple#*|}"; done
  if [ -s "$work/new" ]; then
    cp "$work/new" "$SSHD_DROPIN/.chj-users.tmp" && chmod 644 "$SSHD_DROPIN/.chj-users.tmp" && chown 0:0 "$SSHD_DROPIN/.chj-users.tmp" \
      && mv -f "$SSHD_DROPIN/.chj-users.tmp" "$SSHD_MANAGED" || { rm -f -- "$SSHD_DROPIN/.chj-users.tmp"; restore; fail SSH_WRITE_FAILED "The managed file could not be written."; }
  else rm -f -- "$SSHD_MANAGED"; fi
  if ! out=$("$SSHD" -t 2>&1); then restore; rm -rf -- "$work"; fail SSH_CONFIG_INVALID "$out"; fi
  for tuple in "$@"; do printf '\036CHJ_AFTER=%s\n' "$tuple"; effective "${tuple%%|*}" "${tuple#*|}"; done
  : > "$work/pending"
  ( trap '' HUP INT
    sleep "$timeout"
    if mv "$work/pending" "$work/claimed" 2>/dev/null; then
      restore
      [ -e "$work/reloaded" ] && reload_sshd >/dev/null 2>&1
      printf 'watchdog rollback %s\n' "$stamp" > "$STATE_DIR/last-watchdog-rollback"
    fi
    rm -rf -- "$work" ) </dev/null >/dev/null 2>&1 &
  printf '\036CHJ_STAGED\n'
  IFS= read -r decision || decision=EOF
  if [ "$decision" != COMMIT ]; then
    claim && restore
    printf '\036CHJ_ROLLED_BACK=%s\n' "$decision"; exit 0
  fi
  if ! out=$(reload_sshd); then
    claim && restore
    "$SSHD" -t >/dev/null 2>&1 && reload_sshd >/dev/null 2>&1
    fail SSH_RELOAD_FAILED "$out"
  fi
  : > "$work/reloaded"
  printf '\036CHJ_RELOADED=%s|%s\n' "$(service_state)" "$(printf '%s' "$out" | oneline)"
  IFS= read -r decision || decision=EOF
  if [ "$decision" = CONFIRM ] && claim; then
    printf '\036CHJ_CONFIRMED=%s\n' "$backup"; exit 0
  fi
  if claim; then restore; reload_sshd >/dev/null 2>&1; printf '\036CHJ_ROLLED_BACK=%s\n' "$decision"
  else printf '\036CHJ_ROLLED_BACK=watchdog\n'; fi
  exit 0
}

case $op in
  probe) op_probe ;;
  inventory) op_inventory ;;
  detail) op_detail "$@" ;;
  keys-write) op_keys_write "$@" ;;
  create) op_create "$@" ;;
  account) op_account "$@" ;;
  delete) op_delete "$@" ;;
  ssh-apply) op_ssh_apply "$@" ;;
  *) fail ACTION_INVALID "Unsupported operation." ;;
esac
