#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2025-10-11; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.live.builder.git
# SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency
# SPDX-FileCopyrightText: 2024-2025; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: EUPL-1.2 OR LicenseRef-CCLA-1.0
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
# SPDX-PackageName: CISS.debian.live.builder
# SPDX-Security-Contact: security@coresecret.eu
set -Ceuo pipefail
#######################################
# Iterates all '/etc/shadow' entries and sets:
#   4=min age=0, 5=max age=16384, 6=warn=128, 7=inactive=42, 8=expire=17.09.2102
#   Safe: creates a timestamped backup and (if available) locks '/etc/.pwd.lock'.
# Globals:
#   RECOVERY
#   TARGET
#   VAR_RUN_RECOVERY
# Arguments:
#   None
# Returns:
#   0: on success
#######################################
update_shadow() {
  ### Declare Arrays, HashMaps, and Variables.

  declare -r  var_shadow="/etc/shadow"
  declare -r  var_backup="/root/.ciss/cdlb/backup/etc/shadow.$(date +%s).bak"
  declare -r  var_temp="${var_shadow}.new.$$"
  declare -r  var_exp_dt="17.09.2102"
  declare     var_exp_ds=""

  mkdir -p "/root/.ciss/cdlb/backup/etc"

  var_exp_ds="$(
    awk -v d="${var_exp_dt}" 'BEGIN{
      # Force UTC to avoid DST/timezone off-by-one errors
      ENVIRON["TZ"]="UTC";
      if (match(d, /^([0-9]{2})\.([0-9]{2})\.([0-9]{4})$/, a)) {
        dd=a[1]+0; mm=a[2]+0; yyyy=a[3]+0;
        sec = mktime(sprintf("%04d %02d %02d 00 00 00 0", yyyy, mm, dd));
        if (sec < 0) { print "ERR"; exit 1 }
        print int(sec/86400);
        exit 0
      } else { print "ERR"; exit 1 }
    }'
  )" || return 42

  # shellcheck disable=SC2249
  case "${var_exp_ds}" in

    ''|*ERR*)
      return 127
      ;;

  esac

  umask 0077
  cp --preserve=mode,ownership "${var_shadow}" "${var_backup}"

  ### Rewrite fields 4..8 for every line
  ### Preserve fields 1..3 and 9, keep password hashes untouched.
  ### Pad to 9 fields if shorter; keep empty lines intact (rare but safe).
  awk -v FS=":" -v OFS=":" -v v_exp="${var_exp_ds}" '
    NF==0 { print; next }                      # preserve blank lines verbatim
    {
      # pad missing trailing fields to 9
      for (i=NF+1; i<=9; i++) $i="";
      $4=0; $5=16384; $6=128; $7=42; $8=v_exp; # set required fields
      print
    }
  ' "${var_backup}" >| "${var_temp}"

  ### Defensive: ensure non-empty output.
  if [[ ! -s "${var_temp}" ]]; then
    rm -f "${var_temp}"
    return 42
  fi

  ### Preserve owner/mode (fallback to 0640 root:shadow if reference fails).
  chown --reference="${var_shadow}" "${var_temp}" 2>/dev/null || chown root:shadow "${var_temp}" 2>/dev/null || true
  chmod --reference="${var_shadow}" "${var_temp}" 2>/dev/null || chmod 0640 "${var_temp}" 2>/dev/null || true

  ### Atomic replace.
  mv -f "${var_temp}" "${var_shadow}"

  return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f update_shadow

printf "\e[95m++++ ++++ ++++ ++++ ++++ ++++ ++ 🧪 '%s' starting ... \e[0m\n" "${0}"

if ! command -v chage &>/dev/null; then

  printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ ✅ Info: 'chage' NOT found. Exiting hook ... \e[0m\n"
  printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ ✅ '%s' applied successfully. \e[0m\n" "${0}"

  exit 0

fi

declare -i max_days=16384
# shellcheck disable=SC2312
mapfile -t users_to_update < <(
  awk -F: '$2 !~ /^[!*]/ { print $1 }' /etc/shadow
)

if [[ ${#users_to_update[@]} -eq 0 ]]; then

  printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ ✅ No enabled-login accounts found in /etc/shadow. Exiting hook ... \e[0m\n"
  printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ ✅ '%s' applied successfully. \e[0m\n" "${0}"

  exit 0

fi

declare user
for user in "${users_to_update[@]}"; do
  printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ ✅ Setting max password age for user '%s' to '%s' days. \e[0m\n" "${user}" "${max_days}"
  chage --maxdays "${max_days}" "${user}"
done

unset max_days user users_to_update

awk -F: '$2 !~ /^\$[0-9]/ && length($2)==13 { print $1,$2 }' /etc/shadow

printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ ✅ All applicable accounts have been updated. \e[0m\n"

update_shadow

printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ ✅ '%s' applied successfully. \e[0m\n" "${0}"

exit 0
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
