#!/usr/bin/env bash

#########################################################################################
#### user-audit.sh — Audit local user accounts, sudo access, SSH keys, and passwords ####
#### Shows last login, password age, group memberships, and security warnings        ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.00                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./user-audit.sh                                                               ####
####   ./user-audit.sh --system                                                      ####
####   ./user-audit.sh --section sudo,ssh                                            ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################

set -euo pipefail

# ── Defaults ──────────────────────────────────────────────────────────
SECTIONS="${SECTIONS:-all}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"
FILTER="${FILTER:-human}"
WARN_DAYS="${WARN_DAYS:-14}"

# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
TOTAL_USERS=0
SUDO_USERS=0
SSH_KEY_USERS=0
PASSWORD_WARNINGS=0

# ── Colors ────────────────────────────────────────────────────────────
setup_colors() {
    if [[ "$COLOR" == "never" ]]; then
        RED="" GREEN="" YELLOW="" CYAN="" BOLD="" DIM="" RESET=""
        return
    fi
    if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
        RED='\033[0;31m'
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        CYAN='\033[0;36m'
        BOLD='\033[1m'
        DIM='\033[2m'
        RESET='\033[0m'
    else
        RED="" GREEN="" YELLOW="" CYAN="" BOLD="" DIM="" RESET=""
    fi
}

# ── Logging ───────────────────────────────────────────────────────────
log()     { echo -e "${CYAN}[INFO]${RESET} $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }

# ── Helpers ───────────────────────────────────────────────────────────
section_header() {
    echo ""
    echo -e "  ${BOLD}${CYAN}── $1 ──${RESET}"
    echo ""
}

field() {
    printf "  ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
}

field_color() {
    printf "  ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
}

should_show() {
    [[ "$SECTIONS" == "all" ]] || [[ ",$SECTIONS," == *",$1,"* ]]
}

is_sudo_user() {
    local user="$1"
    local sudo_groups="sudo wheel adm"
    for grp in $sudo_groups; do
        if getent group "$grp" 2>/dev/null | grep -qw "$user"; then
            return 0
        fi
    done
    return 1
}

get_ssh_key_count() {
    local user="$1"
    local home_dir="$2"
    local auth_file="${home_dir}/.ssh/authorized_keys"
    if [[ -r "$auth_file" ]]; then
        grep -cE "^(ssh-|ecdsa-|sk-)" "$auth_file" 2>/dev/null || echo "0"
    else
        echo "0"
    fi
}

get_last_login() {
    local user="$1"
    local last_info
    last_info=$(lastlog -u "$user" 2>/dev/null | tail -1)
    if echo "$last_info" | grep -q "Never logged in"; then
        echo "Never"
    else
        echo "$last_info" | awk '{print $4, $5, $6, $7, $9}' 2>/dev/null || echo "Unknown"
    fi
}

get_password_age() {
    local user="$1"
    if [[ ! -r /etc/shadow ]]; then
        echo "no-access"
        return
    fi
    local shadow_entry
    shadow_entry=$(grep "^${user}:" /etc/shadow 2>/dev/null || true)
    if [[ -z "$shadow_entry" ]]; then
        echo "no-entry"
        return
    fi
    local last_change
    last_change=$(echo "$shadow_entry" | cut -d: -f3)
    if [[ -z "$last_change" || "$last_change" == "0" ]]; then
        echo "must-change"
        return
    fi
    local today
    today=$(( $(date +%s) / 86400 ))
    local age=$(( today - last_change ))
    echo "$age"
}

get_password_max_days() {
    local user="$1"
    if [[ ! -r /etc/shadow ]]; then
        echo ""
        return
    fi
    local shadow_entry
    shadow_entry=$(grep "^${user}:" /etc/shadow 2>/dev/null || true)
    if [[ -z "$shadow_entry" ]]; then
        echo ""
        return
    fi
    local max_days
    max_days=$(echo "$shadow_entry" | cut -d: -f5)
    if [[ -z "$max_days" || "$max_days" == "99999" ]]; then
        echo ""
    else
        echo "$max_days"
    fi
}

password_status_color() {
    local age="$1"
    local max_days="$2"

    if [[ "$age" == "no-access" ]]; then
        echo "${DIM}(shadow unreadable)${RESET}"
        return
    fi
    if [[ "$age" == "no-entry" ]]; then
        echo "${DIM}N/A${RESET}"
        return
    fi
    if [[ "$age" == "must-change" ]]; then
        echo "${YELLOW}must change${RESET}"
        PASSWORD_WARNINGS=$((PASSWORD_WARNINGS + 1))
        return
    fi

    local status="${age} days"
    if [[ -n "$max_days" ]]; then
        local remaining=$(( max_days - age ))
        if [[ "$remaining" -le 0 ]]; then
            status="${RED}EXPIRED (${age}d / ${max_days}d max)${RESET}"
            PASSWORD_WARNINGS=$((PASSWORD_WARNINGS + 1))
        elif [[ "$remaining" -le "$WARN_DAYS" ]]; then
            status="${YELLOW}${age}d (expires in ${remaining}d)${RESET}"
            PASSWORD_WARNINGS=$((PASSWORD_WARNINGS + 1))
        else
            status="${GREEN}${age}d (${remaining}d remaining)${RESET}"
        fi
    else
        status="${GREEN}${age}d (no expiry)${RESET}"
    fi
    echo "$status"
}

shell_color() {
    local shell="$1"
    case "$shell" in
        */nologin|*/false)
            echo "${RED}${shell}${RESET}" ;;
        *)
            echo "${GREEN}${shell}${RESET}" ;;
    esac
}

# ══════════════════════════════════════════════════════════════════════
#                       USER LISTING
# ══════════════════════════════════════════════════════════════════════

show_users() {
    section_header "User Accounts"

    printf "  ${BOLD}%-16s %6s  %-18s  %-14s  %s${RESET}\n" "USERNAME" "UID" "SHELL" "LAST LOGIN" "PASSWORD AGE"
    printf "  %s\n" "$(printf '%.0s─' {1..78})"

    while IFS=: read -r username _ uid _ _ home shell; do
        if [[ "$FILTER" == "human" && "$uid" -lt 1000 && "$username" != "root" ]]; then
            continue
        fi

        TOTAL_USERS=$((TOTAL_USERS + 1))

        local last_login
        last_login=$(get_last_login "$username")
        if [[ ${#last_login} -gt 14 ]]; then
            last_login="${last_login:0:14}"
        fi

        local pw_age pw_max pw_status
        pw_age=$(get_password_age "$username")
        pw_max=$(get_password_max_days "$username")
        pw_status=$(password_status_color "$pw_age" "$pw_max")

        local shell_display
        shell_display=$(shell_color "$shell")

        printf "  %-16s %6s  %b  %-14s  %b\n" \
            "$username" "$uid" \
            "$(printf '%-18b' "$shell_display")" \
            "$last_login" "$pw_status"

        verbose "  ${username}: home=${home} shell=${shell}"
    done < /etc/passwd
}

# ══════════════════════════════════════════════════════════════════════
#                       SUDO ACCESS
# ══════════════════════════════════════════════════════════════════════

show_sudo() {
    section_header "Sudo / Wheel Membership"

    local found=0
    while IFS=: read -r username _ uid _ _ _ _; do
        if [[ "$FILTER" == "human" && "$uid" -lt 1000 && "$username" != "root" ]]; then
            continue
        fi
        if is_sudo_user "$username"; then
            local groups_list
            groups_list=$(groups "$username" 2>/dev/null | cut -d: -f2 | xargs)
            printf "  ${GREEN}✓${RESET} %-20s %s\n" "$username" "$groups_list"
            SUDO_USERS=$((SUDO_USERS + 1))
            found=1
        fi
    done < /etc/passwd

    if [[ "$found" -eq 0 ]]; then
        echo "  No sudo/wheel users found"
    fi

    # Check sudoers.d
    if [[ -d /etc/sudoers.d ]] && [[ -r /etc/sudoers.d ]]; then
        local sudoers_files
        sudoers_files=$(find /etc/sudoers.d -type f ! -name '.*' 2>/dev/null | wc -l)
        if [[ "$sudoers_files" -gt 0 ]]; then
            echo ""
            field "sudoers.d files:" "$sudoers_files"
        fi
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       SSH KEYS
# ══════════════════════════════════════════════════════════════════════

show_ssh() {
    section_header "SSH Authorized Keys"

    printf "  ${BOLD}%-20s %s${RESET}\n" "USERNAME" "KEY COUNT"
    printf "  %s\n" "$(printf '%.0s─' {1..35})"

    local found=0
    while IFS=: read -r username _ uid _ _ home _; do
        if [[ "$FILTER" == "human" && "$uid" -lt 1000 && "$username" != "root" ]]; then
            continue
        fi
        local key_count
        key_count=$(get_ssh_key_count "$username" "$home")
        if [[ "$key_count" -gt 0 ]]; then
            printf "  %-20s ${GREEN}%s${RESET}\n" "$username" "$key_count"
            SSH_KEY_USERS=$((SSH_KEY_USERS + 1))
            found=1
        fi
    done < /etc/passwd

    if [[ "$found" -eq 0 ]]; then
        echo "  No SSH authorized keys found"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       PASSWORD STATUS
# ══════════════════════════════════════════════════════════════════════

show_passwords() {
    section_header "Password Status"

    if [[ ! -r /etc/shadow ]]; then
        warn "Cannot read /etc/shadow — run as root for password details"
        return
    fi

    printf "  ${BOLD}%-16s %-12s %-12s %s${RESET}\n" "USERNAME" "LAST CHANGE" "MAX DAYS" "STATUS"
    printf "  %s\n" "$(printf '%.0s─' {1..62})"

    while IFS=: read -r username _ uid _ _ _ _; do
        if [[ "$FILTER" == "human" && "$uid" -lt 1000 && "$username" != "root" ]]; then
            continue
        fi

        local shadow_entry
        shadow_entry=$(grep "^${username}:" /etc/shadow 2>/dev/null || true)
        if [[ -z "$shadow_entry" ]]; then
            continue
        fi

        local pw_field last_change max_days
        pw_field=$(echo "$shadow_entry" | cut -d: -f2)
        last_change=$(echo "$shadow_entry" | cut -d: -f3)
        max_days=$(echo "$shadow_entry" | cut -d: -f5)

        # Skip locked/disabled accounts
        if [[ "$pw_field" == "!" || "$pw_field" == "!!" || "$pw_field" == "*" ]]; then
            local lock_status="${DIM}locked${RESET}"
            printf "  %-16s %-12s %-12s %b\n" "$username" "-" "-" "$lock_status"
            continue
        fi

        local change_date="-"
        if [[ -n "$last_change" && "$last_change" != "0" ]]; then
            change_date=$(date -d "1970-01-01 + ${last_change} days" +%Y-%m-%d 2>/dev/null || echo "$last_change")
        fi

        local max_display="-"
        if [[ -n "$max_days" && "$max_days" != "99999" ]]; then
            max_display="${max_days}d"
        fi

        local pw_age pw_status
        pw_age=$(get_password_age "$username")
        pw_status=$(password_status_color "$pw_age" "$(get_password_max_days "$username")")

        printf "  %-16s %-12s %-12s %b\n" "$username" "$change_date" "$max_display" "$pw_status"
    done < /etc/passwd
}

# ══════════════════════════════════════════════════════════════════════
#                       SUMMARY
# ══════════════════════════════════════════════════════════════════════

print_summary() {
    echo ""
    echo -e "  ${BOLD}══════════════════════════════════════════${RESET}"
    echo -e "  ${BOLD}User Audit Summary${RESET}"
    echo -e "  ${BOLD}══════════════════════════════════════════${RESET}"

    field "Total users shown:" "$TOTAL_USERS"
    field_color "Sudo/wheel users:" "${GREEN}${SUDO_USERS}${RESET}"
    field "Users with SSH keys:" "$SSH_KEY_USERS"

    if [[ "$PASSWORD_WARNINGS" -gt 0 ]]; then
        field_color "Password warnings:" "${YELLOW}${PASSWORD_WARNINGS}${RESET}"
    else
        field_color "Password warnings:" "${GREEN}0${RESET}"
    fi

    echo ""
}

# ══════════════════════════════════════════════════════════════════════
#                       USAGE
# ══════════════════════════════════════════════════════════════════════

usage() {
    cat <<EOF
${SCRIPT_NAME} — Audit local user accounts, sudo access, SSH keys, and passwords

USAGE:
  ${SCRIPT_NAME} [OPTIONS]

OPTIONS:
  --human               Show only human users (UID >= 1000 + root, default)
  --system              Include system users (all UIDs)
  --section SECTIONS    Comma-separated sections to show (default: all)
                        Available: users, sudo, ssh, passwords
  --warn-days DAYS      Password expiry warning threshold (default: ${WARN_DAYS})
  --verbose             Enable debug output
  --no-color            Disable colored output
  --help                Show this help

EXAMPLES:
  # Audit human users (default)
  ./user-audit.sh

  # Include system accounts
  ./user-audit.sh --system

  # Show only sudo and SSH key info
  ./user-audit.sh --section sudo,ssh

  # Full audit with password details (needs root)
  sudo ./user-audit.sh --section passwords
EOF
}

# ══════════════════════════════════════════════════════════════════════
#                       ARGUMENT PARSING
# ══════════════════════════════════════════════════════════════════════

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --human)
                FILTER="human"; shift ;;
            --system)
                FILTER="system"; shift ;;
            --section)
                SECTIONS="$2"; shift 2 ;;
            --warn-days)
                WARN_DAYS="$2"; shift 2 ;;
            --verbose)
                VERBOSE="true"; shift ;;
            --no-color)
                COLOR="never"; shift ;;
            --help|-h)
                setup_colors
                usage
                exit 0 ;;
            *)
                echo "Unknown option: $1" >&2
                echo "Run ${SCRIPT_NAME} --help for usage" >&2
                exit 1 ;;
        esac
    done
}

# ══════════════════════════════════════════════════════════════════════
#                       MAIN
# ══════════════════════════════════════════════════════════════════════

main() {
    parse_args "$@"
    setup_colors

    echo ""
    echo -e "${BOLD}User Audit — $(hostname -f 2>/dev/null || hostname)${RESET}"
    echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}"
    echo -e "Filter: ${FILTER}"

    should_show "users"     && show_users
    should_show "sudo"      && show_sudo
    should_show "ssh"       && show_ssh
    should_show "passwords" && show_passwords

    print_summary
}

main "$@"
