a1a17e81a1
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.
434 lines
18 KiB
Bash
434 lines
18 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
#########################################################################################
|
|
#### cron-lister.sh — List all cron jobs across users, system cron, and timers ####
|
|
#### Scans user crontabs, /etc/cron.*, systemd timers, and anacron ####
|
|
#### ####
|
|
#### Author: Phil Connor ####
|
|
#### Contact: contact@mylinux.work ####
|
|
#### License: MIT ####
|
|
#### Version 1.00 ####
|
|
#### ####
|
|
#### Usage: ####
|
|
#### ./cron-lister.sh ####
|
|
#### ./cron-lister.sh --format raw ####
|
|
#### ####
|
|
#### See --help for all options. ####
|
|
#########################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────
|
|
FORMAT="${FORMAT:-table}"
|
|
VERBOSE="${VERBOSE:-false}"
|
|
COLOR="${COLOR:-auto}"
|
|
|
|
# ── State ─────────────────────────────────────────────────────────────
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_NAME
|
|
COUNT_USER_CRONTAB=0
|
|
COUNT_SYSTEM_CRONTAB=0
|
|
COUNT_CRON_D=0
|
|
COUNT_CRON_DIRS=0
|
|
COUNT_SYSTEMD_TIMER=0
|
|
COUNT_ANACRON=0
|
|
|
|
# ── Colors ────────────────────────────────────────────────────────────
|
|
setup_colors() {
|
|
if [[ "$COLOR" == "never" ]]; then
|
|
GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" BOLD="" DIM="" RESET=""
|
|
return
|
|
fi
|
|
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
MAGENTA='\033[0;35m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
RESET='\033[0m'
|
|
else
|
|
GREEN="" YELLOW="" BLUE="" MAGENTA="" CYAN="" BOLD="" DIM="" RESET=""
|
|
fi
|
|
}
|
|
|
|
# ── Logging ───────────────────────────────────────────────────────────
|
|
log() { echo -e "${DIM}[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 ""
|
|
}
|
|
|
|
print_table_header() {
|
|
printf " ${BOLD}%-18s %-14s %-22s %s${RESET}\n" "SOURCE" "USER/UNIT" "SCHEDULE" "COMMAND"
|
|
printf " %s\n" "$(printf '%.0s─' {1..80})"
|
|
}
|
|
|
|
print_job() {
|
|
local source="$1"
|
|
local user="$2"
|
|
local schedule="$3"
|
|
local command="$4"
|
|
|
|
# Truncate long commands
|
|
if [[ ${#command} -gt 60 ]]; then
|
|
command="${command:0:57}..."
|
|
fi
|
|
|
|
if [[ "$FORMAT" == "raw" ]]; then
|
|
printf "%s\t%s\t%s\t%s\n" "$source" "$user" "$schedule" "$command"
|
|
return
|
|
fi
|
|
|
|
local color
|
|
case "$source" in
|
|
user-crontab) color="$GREEN" ;;
|
|
/etc/crontab) color="$BLUE" ;;
|
|
/etc/cron.d/*) color="$CYAN" ;;
|
|
cron.hourly|cron.daily|cron.weekly|cron.monthly) color="$MAGENTA" ;;
|
|
systemd-timer) color="$YELLOW" ;;
|
|
anacron) color="$DIM" ;;
|
|
*) color="" ;;
|
|
esac
|
|
|
|
printf " %b%-18s%b %-14s %-22s %s\n" "$color" "$source" "$RESET" "$user" "$schedule" "$command"
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# USER CRONTABS
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
scan_user_crontabs() {
|
|
section_header "User Crontabs"
|
|
|
|
local crontab_dir="/var/spool/cron/crontabs"
|
|
local found=false
|
|
|
|
if [[ -d "$crontab_dir" ]] && [[ -r "$crontab_dir" ]]; then
|
|
while IFS= read -r crontab_file; do
|
|
[[ -z "$crontab_file" ]] && continue
|
|
found=true
|
|
local username
|
|
username=$(basename "$crontab_file")
|
|
verbose "Reading crontab for user: $username"
|
|
|
|
while IFS= read -r line; do
|
|
# Skip comments and empty lines
|
|
[[ -z "$line" || "$line" == "#"* || "$line" == "SHELL="* || "$line" == "PATH="* || "$line" == "MAILTO="* ]] && continue
|
|
|
|
local schedule cmd
|
|
schedule=$(echo "$line" | awk '{print $1, $2, $3, $4, $5}')
|
|
cmd=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/ *$//')
|
|
|
|
print_job "user-crontab" "$username" "$schedule" "$cmd"
|
|
COUNT_USER_CRONTAB=$((COUNT_USER_CRONTAB + 1))
|
|
done < "$crontab_file"
|
|
done < <(find "$crontab_dir" -type f 2>/dev/null)
|
|
fi
|
|
|
|
if [[ "$found" == "false" ]]; then
|
|
verbose "No user crontabs found in $crontab_dir"
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# SYSTEM CRONTAB
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
scan_system_crontab() {
|
|
section_header "/etc/crontab"
|
|
|
|
if [[ ! -f /etc/crontab ]]; then
|
|
verbose "/etc/crontab not found"
|
|
return
|
|
fi
|
|
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" || "$line" == "#"* || "$line" == "SHELL="* || "$line" == "PATH="* || "$line" == "MAILTO="* || "$line" == "HOME="* ]] && continue
|
|
|
|
local schedule user cmd
|
|
schedule=$(echo "$line" | awk '{print $1, $2, $3, $4, $5}')
|
|
user=$(echo "$line" | awk '{print $6}')
|
|
cmd=$(echo "$line" | awk '{for(i=7;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/ *$//')
|
|
|
|
if [[ -n "$cmd" ]]; then
|
|
print_job "/etc/crontab" "$user" "$schedule" "$cmd"
|
|
COUNT_SYSTEM_CRONTAB=$((COUNT_SYSTEM_CRONTAB + 1))
|
|
fi
|
|
done < /etc/crontab
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# /etc/cron.d
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
scan_cron_d() {
|
|
section_header "/etc/cron.d"
|
|
|
|
if [[ ! -d /etc/cron.d ]]; then
|
|
verbose "/etc/cron.d not found"
|
|
return
|
|
fi
|
|
|
|
while IFS= read -r cron_file; do
|
|
[[ -z "$cron_file" ]] && continue
|
|
local filename
|
|
filename=$(basename "$cron_file")
|
|
|
|
# Skip dpkg and package manager files
|
|
[[ "$filename" == *.dpkg-* || "$filename" == *.ucf-* || "$filename" == "." || "$filename" == ".." ]] && continue
|
|
|
|
verbose "Reading /etc/cron.d/$filename"
|
|
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" || "$line" == "#"* || "$line" == "SHELL="* || "$line" == "PATH="* || "$line" == "MAILTO="* || "$line" == "HOME="* ]] && continue
|
|
|
|
local schedule user cmd
|
|
schedule=$(echo "$line" | awk '{print $1, $2, $3, $4, $5}')
|
|
user=$(echo "$line" | awk '{print $6}')
|
|
cmd=$(echo "$line" | awk '{for(i=7;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/ *$//')
|
|
|
|
if [[ -n "$cmd" ]]; then
|
|
print_job "/etc/cron.d/$filename" "$user" "$schedule" "$cmd"
|
|
COUNT_CRON_D=$((COUNT_CRON_D + 1))
|
|
fi
|
|
done < "$cron_file"
|
|
done < <(find /etc/cron.d -maxdepth 1 -type f 2>/dev/null)
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# CRON DIRECTORIES
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
scan_cron_dirs() {
|
|
section_header "Cron Directories"
|
|
|
|
local period
|
|
for period in hourly daily weekly monthly; do
|
|
local dir="/etc/cron.${period}"
|
|
if [[ ! -d "$dir" ]]; then
|
|
continue
|
|
fi
|
|
|
|
while IFS= read -r script; do
|
|
[[ -z "$script" ]] && continue
|
|
local script_name
|
|
script_name=$(basename "$script")
|
|
|
|
# Skip non-executable and package manager leftovers
|
|
[[ "$script_name" == *.dpkg-* || "$script_name" == *.ucf-* || "$script_name" == "." || "$script_name" == ".." ]] && continue
|
|
|
|
if [[ -x "$script" ]]; then
|
|
print_job "cron.${period}" "root" "$period" "$script_name"
|
|
COUNT_CRON_DIRS=$((COUNT_CRON_DIRS + 1))
|
|
else
|
|
verbose "Skipping non-executable: $script"
|
|
fi
|
|
done < <(find "$dir" -maxdepth 1 -type f 2>/dev/null)
|
|
done
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# SYSTEMD TIMERS
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
scan_systemd_timers() {
|
|
section_header "Systemd Timers"
|
|
|
|
if ! command -v systemctl &>/dev/null; then
|
|
verbose "systemd not available"
|
|
return
|
|
fi
|
|
|
|
systemctl list-timers --all --no-legend --no-pager 2>/dev/null | while IFS= read -r line; do
|
|
[[ -z "$line" ]] && continue
|
|
|
|
local unit_name schedule_info
|
|
# Timer unit is the second-to-last field, schedule is NEXT + LEFT
|
|
unit_name=$(echo "$line" | awk '{print $(NF-1)}')
|
|
schedule_info=$(echo "$line" | awk '{print $1, $2, $3}')
|
|
|
|
if [[ -n "$unit_name" && "$unit_name" != "UNIT" ]]; then
|
|
# Get the trigger schedule from the timer unit
|
|
local on_calendar
|
|
on_calendar=$(systemctl show "$unit_name" --property=TimersCalendar 2>/dev/null | sed 's/TimersCalendar=//' | head -1)
|
|
|
|
if [[ -z "$on_calendar" || "$on_calendar" == "" ]]; then
|
|
on_calendar=$(systemctl show "$unit_name" --property=TimersMonotonic 2>/dev/null | sed 's/TimersMonotonic=//' | head -1)
|
|
fi
|
|
|
|
on_calendar="${on_calendar:-$schedule_info}"
|
|
|
|
# Truncate schedule if too long
|
|
if [[ ${#on_calendar} -gt 20 ]]; then
|
|
on_calendar="${on_calendar:0:17}..."
|
|
fi
|
|
|
|
print_job "systemd-timer" "$unit_name" "$on_calendar" "$(echo "$line" | awk '{print $NF}')"
|
|
COUNT_SYSTEMD_TIMER=$((COUNT_SYSTEMD_TIMER + 1))
|
|
fi
|
|
done
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# ANACRON
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
scan_anacron() {
|
|
section_header "Anacron"
|
|
|
|
if [[ ! -f /etc/anacrontab ]]; then
|
|
verbose "/etc/anacrontab not found"
|
|
return
|
|
fi
|
|
|
|
while IFS= read -r line; do
|
|
[[ -z "$line" || "$line" == "#"* || "$line" == "SHELL="* || "$line" == "PATH="* || "$line" == "MAILTO="* || "$line" == "HOME="* || "$line" == "START_HOURS_RANGE="* || "$line" == "RANDOM_DELAY="* ]] && continue
|
|
|
|
local period delay ident cmd
|
|
period=$(echo "$line" | awk '{print $1}')
|
|
delay=$(echo "$line" | awk '{print $2}')
|
|
ident=$(echo "$line" | awk '{print $3}')
|
|
cmd=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/ *$//')
|
|
|
|
if [[ -n "$cmd" ]]; then
|
|
print_job "anacron" "$ident" "every ${period}d +${delay}m" "$cmd"
|
|
COUNT_ANACRON=$((COUNT_ANACRON + 1))
|
|
fi
|
|
done < /etc/anacrontab
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# SUMMARY
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
print_summary() {
|
|
local total=$((COUNT_USER_CRONTAB + COUNT_SYSTEM_CRONTAB + COUNT_CRON_D + COUNT_CRON_DIRS + COUNT_SYSTEMD_TIMER + COUNT_ANACRON))
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}══════════════════════════════════════════${RESET}"
|
|
echo -e " ${BOLD}Summary${RESET}"
|
|
echo -e " ${BOLD}══════════════════════════════════════════${RESET}"
|
|
|
|
printf " %-22s %d\n" "User crontabs:" "$COUNT_USER_CRONTAB"
|
|
printf " %-22s %d\n" "/etc/crontab:" "$COUNT_SYSTEM_CRONTAB"
|
|
printf " %-22s %d\n" "/etc/cron.d:" "$COUNT_CRON_D"
|
|
printf " %-22s %d\n" "cron.{h,d,w,m}:" "$COUNT_CRON_DIRS"
|
|
printf " %-22s %d\n" "Systemd timers:" "$COUNT_SYSTEMD_TIMER"
|
|
printf " %-22s %d\n" "Anacron:" "$COUNT_ANACRON"
|
|
printf " %s\n" "$(printf '%.0s─' {1..30})"
|
|
printf " ${BOLD}%-22s %d${RESET}\n" "Total:" "$total"
|
|
|
|
echo ""
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# USAGE
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
${SCRIPT_NAME} — List all cron jobs across users, system cron, and systemd timers
|
|
|
|
USAGE:
|
|
${SCRIPT_NAME} [OPTIONS]
|
|
|
|
OPTIONS:
|
|
--format FORMAT Output format: table (default) or raw (tab-separated)
|
|
--verbose Enable debug output
|
|
--no-color Disable colored output
|
|
--help Show this help
|
|
|
|
SOURCES SCANNED:
|
|
/var/spool/cron/crontabs/ Per-user crontabs
|
|
/etc/crontab System crontab
|
|
/etc/cron.d/* Drop-in cron files
|
|
/etc/cron.{hourly,daily,weekly,monthly}/ Periodic scripts
|
|
systemd timers Active systemd timer units
|
|
/etc/anacrontab Anacron jobs
|
|
|
|
ENVIRONMENT VARIABLES:
|
|
FORMAT Output format (default: table)
|
|
COLOR Color mode: auto, always, never (default: auto)
|
|
|
|
EXAMPLES:
|
|
# List all cron jobs
|
|
sudo ./cron-lister.sh
|
|
|
|
# Raw output for parsing
|
|
sudo ./cron-lister.sh --format raw
|
|
|
|
# Pipe-friendly
|
|
sudo ./cron-lister.sh --no-color | tee /tmp/cron-audit.txt
|
|
EOF
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# ARGUMENT PARSING
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--format)
|
|
FORMAT="$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
|
|
|
|
if [[ "$FORMAT" != "table" && "$FORMAT" != "raw" ]]; then
|
|
echo "Invalid format: $FORMAT (must be 'table' or 'raw')" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# MAIN
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
setup_colors
|
|
|
|
if [[ "$FORMAT" != "raw" ]]; then
|
|
echo ""
|
|
echo -e "${BOLD}Cron Job Lister — $(hostname -f 2>/dev/null || hostname)${RESET}"
|
|
echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}"
|
|
fi
|
|
|
|
if [[ "$FORMAT" == "table" ]]; then
|
|
echo ""
|
|
print_table_header
|
|
fi
|
|
|
|
scan_user_crontabs
|
|
scan_system_crontab
|
|
scan_cron_d
|
|
scan_cron_dirs
|
|
scan_systemd_timers
|
|
scan_anacron
|
|
|
|
if [[ "$FORMAT" != "raw" ]]; then
|
|
print_summary
|
|
fi
|
|
}
|
|
|
|
main "$@"
|