Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
Executable
+348
@@ -0,0 +1,348 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#########################################################################################
|
||||
#### password-expiry-check.sh — Check password expiry for AD and local accounts ####
|
||||
#### Auto-detects domain vs local. Desktop notifications when expiry is near. ####
|
||||
#### Requires: bash, chage. Optional: ldapsearch, notify-send, zenity ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version 1.02 ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### ./password-expiry-check.sh ####
|
||||
#### ./password-expiry-check.sh -w 30 ####
|
||||
#### ./password-expiry-check.sh -u jdoe --test ####
|
||||
#### ####
|
||||
#### See --help for all options. ####
|
||||
#########################################################################################
|
||||
|
||||
set -u
|
||||
|
||||
DEBUG="${DEBUG:-}"
|
||||
TEST_MODE=false
|
||||
QUIET=false
|
||||
WARNING_DAYS=14
|
||||
USERNAME="${USER}"
|
||||
ACCOUNT_TYPE=""
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
Check password expiration for domain (AD/SSSD) and local Linux accounts.
|
||||
Auto-detects account type. Sends desktop notifications when expiry is near.
|
||||
|
||||
Options:
|
||||
-w, --warning-days N Warning threshold in days (default: 14)
|
||||
-u, --user USER Check a specific user (default: current user)
|
||||
-q, --quiet Suppress output unless password is within warning window
|
||||
--debug Show debug output
|
||||
--test Force notification even if expiry is not near
|
||||
-h, --help Show this help
|
||||
|
||||
Environment:
|
||||
DEBUG Set non-empty to enable debug output
|
||||
|
||||
Examples:
|
||||
$(basename "$0") # check current user, 14-day threshold
|
||||
$(basename "$0") -w 30 # warn if expiring within 30 days
|
||||
$(basename "$0") -u jdoe # check a specific user
|
||||
$(basename "$0") --test # force notification for testing
|
||||
$(basename "$0") --debug # show detection and query details
|
||||
$(basename "$0") -q # silent unless expiry is near (for /etc/bashrc)
|
||||
|
||||
Schedule:
|
||||
@daily /path/to/$(basename "$0")
|
||||
EOF
|
||||
}
|
||||
|
||||
# Parse flags
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--debug) DEBUG=1 ;;
|
||||
--test) TEST_MODE=true ;;
|
||||
-q|--quiet) QUIET=true ;;
|
||||
-w|--warning-days) WARNING_DAYS="$2"; shift ;;
|
||||
-u|--user) USERNAME="$2"; shift ;;
|
||||
[0-9]*) WARNING_DAYS="$1" ;; # legacy positional arg
|
||||
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Strip domain prefix (DOMAIN\user) or suffix (user@domain) for queries
|
||||
SAM_ACCOUNT_NAME="${USERNAME##*\\}"
|
||||
SAM_ACCOUNT_NAME="${SAM_ACCOUNT_NAME%%@*}"
|
||||
|
||||
debug_echo() {
|
||||
if [[ -n "$DEBUG" ]]; then
|
||||
echo "[DEBUG] $*" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
debug_echo "USERNAME=$USERNAME SAM_ACCOUNT_NAME=$SAM_ACCOUNT_NAME"
|
||||
|
||||
# --- Helper functions ---
|
||||
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
show_notification() {
|
||||
local title="$1"
|
||||
local message="$2"
|
||||
local urgency="${3:-normal}" # low, normal, critical
|
||||
|
||||
# Terminal banner (always shown)
|
||||
# Wrap message to fit within the box (max ~42 chars per line)
|
||||
local wrapped
|
||||
wrapped=$(echo "$message" | fold -s -w 42)
|
||||
|
||||
# Strip emoji for terminal (variable-width chars break alignment)
|
||||
local term_title="${title#⚠ }"
|
||||
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════╗"
|
||||
echo "║ ║"
|
||||
printf "║ %-42s ║\n" "$term_title"
|
||||
echo "║──────────────────────────────────────────────║"
|
||||
while IFS= read -r line; do
|
||||
printf "║ %-42s ║\n" "$line"
|
||||
done <<< "$wrapped"
|
||||
echo "║ ║"
|
||||
echo "╚══════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Desktop dialog — requires user to click OK to dismiss
|
||||
if command -v zenity &>/dev/null && [[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]]; then
|
||||
zenity --warning --title="$title" --text="$message" --width=400 2>/dev/null &
|
||||
elif command -v notify-send &>/dev/null && [[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]]; then
|
||||
# Fallback: notify-send with critical urgency (persistent on most DEs)
|
||||
notify-send --urgency=critical --icon=dialog-warning "$title" "$message"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Account type detection ---
|
||||
|
||||
detect_account_type() {
|
||||
# Check if the system is domain-joined
|
||||
local domain_joined=false
|
||||
|
||||
if command -v realm &>/dev/null && realm list 2>/dev/null | grep -q "configured:"; then
|
||||
domain_joined=true
|
||||
debug_echo "Domain detected via: realm"
|
||||
elif [[ -f /etc/sssd/sssd.conf ]] && systemctl is-active sssd &>/dev/null; then
|
||||
domain_joined=true
|
||||
debug_echo "Domain detected via: sssd.conf"
|
||||
elif grep -qE "^passwd:.*sss|winbind" /etc/nsswitch.conf 2>/dev/null; then
|
||||
domain_joined=true
|
||||
debug_echo "Domain detected via: nsswitch.conf"
|
||||
fi
|
||||
debug_echo "domain_joined=$domain_joined"
|
||||
|
||||
if [[ "$domain_joined" == true ]]; then
|
||||
# Check if this specific user is a domain account
|
||||
# Domain users typically have a UID >= 100000 (SSSD default range)
|
||||
# and their passwd entry comes from sss/winbind, not local files
|
||||
local user_source
|
||||
user_source=$(getent passwd "$SAM_ACCOUNT_NAME" 2>/dev/null | cut -d: -f3)
|
||||
|
||||
if [[ -n "$user_source" ]] && (( user_source >= 100000 )); then
|
||||
ACCOUNT_TYPE="domain"
|
||||
return
|
||||
fi
|
||||
|
||||
# Also check if user exists in /etc/passwd (local) vs only via NSS
|
||||
if grep -q "^${SAM_ACCOUNT_NAME}:" /etc/passwd 2>/dev/null; then
|
||||
ACCOUNT_TYPE="local"
|
||||
return
|
||||
fi
|
||||
|
||||
# User not in /etc/passwd but system is domain-joined — likely domain account
|
||||
ACCOUNT_TYPE="domain"
|
||||
return
|
||||
fi
|
||||
|
||||
ACCOUNT_TYPE="local"
|
||||
}
|
||||
|
||||
# --- Local account expiry ---
|
||||
|
||||
get_expiry_local() {
|
||||
local expiry_str
|
||||
expiry_str=$(chage -l "$SAM_ACCOUNT_NAME" 2>/dev/null | grep "Password expires" | cut -d: -f2- | xargs || true)
|
||||
|
||||
if [[ -z "$expiry_str" || "$expiry_str" == "never" ]]; then
|
||||
echo "never"
|
||||
return
|
||||
fi
|
||||
|
||||
# Convert to epoch
|
||||
date -d "$expiry_str" +%s 2>/dev/null || echo ""
|
||||
}
|
||||
|
||||
# --- Domain account expiry via SSSD/chage ---
|
||||
|
||||
get_expiry_sssd() {
|
||||
local expiry_str
|
||||
expiry_str=$(chage -l "$SAM_ACCOUNT_NAME" 2>/dev/null | grep "Password expires" | cut -d: -f2- | xargs || true)
|
||||
debug_echo "SSSD/chage result for '$SAM_ACCOUNT_NAME': '$expiry_str'"
|
||||
|
||||
if [[ -z "$expiry_str" || "$expiry_str" == "never" ]]; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
# Convert to epoch
|
||||
date -d "$expiry_str" +%s 2>/dev/null || echo ""
|
||||
}
|
||||
|
||||
# --- Domain account expiry via ldapsearch ---
|
||||
|
||||
get_expiry_ldap() {
|
||||
if ! command -v ldapsearch &>/dev/null; then
|
||||
debug_echo "ldapsearch not found, skipping LDAP method"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
local domain realm dc_host base_dn
|
||||
domain=$(hostname -d 2>/dev/null || dnsdomainname 2>/dev/null || echo "")
|
||||
debug_echo "LDAP domain: '$domain'"
|
||||
if [[ -z "$domain" ]]; then
|
||||
debug_echo "Could not determine domain, skipping LDAP"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
realm=$(echo "$domain" | tr '[:lower:]' '[:upper:]')
|
||||
local dc_hosts
|
||||
dc_hosts=$(host -t SRV "_ldap._tcp.${domain}" 2>/dev/null | awk '{print $NF}' | sed 's/\.$//')
|
||||
base_dn=$(echo "$domain" | sed 's/\./,DC=/g; s/^/DC=/')
|
||||
debug_echo "LDAP dc_hosts='$(echo $dc_hosts)' base_dn='$base_dn'"
|
||||
|
||||
if [[ -z "$dc_hosts" ]]; then
|
||||
debug_echo "Could not find domain controller via SRV lookup"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
local result=""
|
||||
local stderr_target="/dev/null"
|
||||
[[ -n "$DEBUG" ]] && stderr_target="/dev/stderr"
|
||||
|
||||
for dc_host in $dc_hosts; do
|
||||
debug_echo "Trying DC: ldapsearch -H ldap://${dc_host} -b $base_dn (sAMAccountName=${SAM_ACCOUNT_NAME})"
|
||||
result=$(ldapsearch -LLL -H "ldap://${dc_host}" -Y GSSAPI -Q \
|
||||
-b "$base_dn" \
|
||||
"(sAMAccountName=${SAM_ACCOUNT_NAME})" \
|
||||
msDS-UserPasswordExpiryTimeComputed 2>"$stderr_target" \
|
||||
| grep "msDS-UserPasswordExpiryTimeComputed:" | awk '{print $2}' || true)
|
||||
|
||||
if [[ -n "$result" ]]; then
|
||||
debug_echo "Got result from $dc_host"
|
||||
break
|
||||
fi
|
||||
debug_echo "No result from $dc_host, trying next..."
|
||||
done
|
||||
debug_echo "LDAP msDS-UserPasswordExpiryTimeComputed: '$result'"
|
||||
|
||||
if [[ -z "$result" || "$result" == "0" || "$result" == "9223372036854775807" ]]; then
|
||||
debug_echo "LDAP result empty, zero, or never-expire sentinel"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
# AD timestamp is 100-nanosecond intervals since 1601-01-01
|
||||
# Convert to Unix epoch: subtract 116444736000000000, divide by 10000000
|
||||
local unix_epoch
|
||||
unix_epoch=$(echo "($result - 116444736000000000) / 10000000" | bc 2>/dev/null)
|
||||
debug_echo "Converted epoch: $unix_epoch"
|
||||
echo "$unix_epoch"
|
||||
}
|
||||
|
||||
# --- Main ---
|
||||
|
||||
detect_account_type
|
||||
|
||||
expiry_epoch=""
|
||||
|
||||
if [[ "$ACCOUNT_TYPE" == "domain" ]]; then
|
||||
# Try SSSD/chage first (simplest on domain-joined systems)
|
||||
expiry_epoch=$(get_expiry_sssd)
|
||||
|
||||
# Fall back to ldapsearch
|
||||
if [[ -z "$expiry_epoch" ]]; then
|
||||
expiry_epoch=$(get_expiry_ldap)
|
||||
fi
|
||||
|
||||
if [[ -z "$expiry_epoch" ]]; then
|
||||
if [[ "$QUIET" != "true" ]]; then
|
||||
echo "Account Type: Domain"
|
||||
echo "User: ${USERNAME}"
|
||||
echo "Password is set to never expire or could not determine expiry date."
|
||||
echo "Ensure this host is domain-joined (realm/SSSD) or has LDAP access."
|
||||
fi
|
||||
[[ "$TEST_MODE" == true ]] && show_notification "Password Expiry Test" "Account: ${USERNAME} (domain) — password never expires or expiry unknown. (--test mode)" "normal"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
# Local account
|
||||
expiry_epoch=$(get_expiry_local)
|
||||
|
||||
if [[ "$expiry_epoch" == "never" ]]; then
|
||||
if [[ "$QUIET" != "true" ]]; then
|
||||
echo "Account Type: Local"
|
||||
echo "User: ${USERNAME}"
|
||||
echo "Password is set to never expire."
|
||||
fi
|
||||
[[ "$TEST_MODE" == true ]] && show_notification "Password Expiry Test" "Account: ${USERNAME} (local) — password never expires. (--test mode)" "normal"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "$expiry_epoch" ]]; then
|
||||
if [[ "$QUIET" != "true" ]]; then
|
||||
echo "Account Type: Local"
|
||||
echo "User: ${USERNAME}"
|
||||
echo "Could not determine password expiry date."
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
now_epoch=$(date +%s)
|
||||
days_remaining=$(( (expiry_epoch - now_epoch) / 86400 ))
|
||||
expiry_date=$(date -d "@${expiry_epoch}" "+%Y-%m-%d %H:%M" 2>/dev/null || date -r "${expiry_epoch}" "+%Y-%m-%d %H:%M" 2>/dev/null)
|
||||
|
||||
if [[ "$QUIET" == "true" ]] && (( days_remaining > WARNING_DAYS )) && [[ "$TEST_MODE" != true ]]; then
|
||||
# Quiet mode and password is not near expiry — suppress all output
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Account Type: ${ACCOUNT_TYPE^}"
|
||||
echo "User: ${USERNAME}"
|
||||
echo "Password Expires: ${expiry_date}"
|
||||
echo "Days Remaining: ${days_remaining}"
|
||||
|
||||
if (( days_remaining <= 0 )); then
|
||||
show_notification \
|
||||
"⚠ PASSWORD EXPIRED" \
|
||||
"Your password EXPIRED (${expiry_date}). Run passwd to change it now." \
|
||||
"critical"
|
||||
elif (( days_remaining <= WARNING_DAYS )); then
|
||||
urgency="normal"
|
||||
(( days_remaining <= 3 )) && urgency="critical"
|
||||
|
||||
show_notification \
|
||||
"⚠ Password Expiring Soon" \
|
||||
"Your password expires in ${days_remaining} day(s) on ${expiry_date}. Change it with: passwd" \
|
||||
"$urgency"
|
||||
elif [[ "$TEST_MODE" == true ]]; then
|
||||
show_notification \
|
||||
"Password Expiry Test" \
|
||||
"Your password expires in ${days_remaining} day(s) on ${expiry_date}. (--test mode)" \
|
||||
"normal"
|
||||
else
|
||||
echo "Password expiry is more than ${WARNING_DAYS} days away. No notification needed."
|
||||
fi
|
||||
Reference in New Issue
Block a user