#!/usr/bin/env bash # checkms - Analyse von MS-Fehlercodes im Exim-Log + Code-Management set -euo pipefail # --- Defaults --- DATE="$(date +%Y-%m-%d)" MINUTES=0 HOURS=0 CONF_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/checkms" ADD_CODES=() DEL_INDEXES=() SET_USER="" SHOW_CONF=0 # --- Funktionen --- usage() { cat <"$CONF_FILE" </dev/null 2>&1; then echo "Fehler: Für --add-code/--del-code/--set-user wird 'jq' benötigt." >&2 exit 1 fi } show_config() { if [[ ! -f "$CONF_FILE" ]]; then echo "Keine Config vorhanden. Sie wird beim nächsten Run erstellt." exit 0 fi # Roh-JSON if command -v jq >/dev/null 2>&1; then echo "RAW ($CONF_FILE):" jq . "$CONF_FILE" else echo "RAW ($CONF_FILE):" cat "$CONF_FILE" fi echo # Tabellarisch if command -v jq >/dev/null 2>&1; then SSH_USER_SHOW="$(jq -r '.ssh_user' "$CONF_FILE")" mapfile -t CODES_SHOW < <(jq -r '.codes[]' "$CONF_FILE") else SSH_USER_SHOW="$(grep -o '"ssh_user":[^,}]*' "$CONF_FILE" | cut -d'"' -f4)" CODES_LINE="$(sed -n 's/.*"codes":[[]\(.*\)[]].*/\1/p' "$CONF_FILE")" IFS=',' read -r -a CODES_SHOW <<<"$(echo "$CODES_LINE" | tr -d ' \t"')" fi echo "TABELLARISCHE ANSICHT:" printf "%-12s %s\n" "SSH_USER" "${SSH_USER_SHOW:-}" echo printf "%-6s %s\n" "ID" "CODE" printf "%-6s %s\n" "----" "-----------------------" id=0 for code in "${CODES_SHOW[@]}"; do printf "%-6s %s\n" "$id" "$code" id=$((id+1)) done exit 0 } apply_add_codes() { [[ ${#ADD_CODES[@]} -eq 0 ]] && return 0 require_jq_write local tmp; tmp="$(mktemp)" cp "$CONF_FILE" "$tmp" for code in "${ADD_CODES[@]}"; do jq --arg code "$code" ' (.codes | any(. == $code)) as $exists | if $exists then . else (.codes += [$code]) end ' "$tmp" > "${tmp}.new" mv "${tmp}.new" "$tmp" done mv "$tmp" "$CONF_FILE" } apply_del_indexes() { [[ ${#DEL_INDEXES[@]} -eq 0 ]] && return 0 require_jq_write local tmp; tmp="$(mktemp)" cp "$CONF_FILE" "$tmp" IFS=$'\n' read -r -d '' -a sorted <<<"$(printf "%s\n" "${DEL_INDEXES[@]}" | sort -rn; printf '\0')" for idx in "${sorted[@]}"; do [[ "$idx" =~ ^[0-9]+$ ]] || { echo "Warnung: ungültige ID '$idx'"; continue; } jq --argjson i "$idx" ' if ($i >= 0 and $i < (.codes|length)) then .codes |= del(.[$i]) else . end ' "$tmp" > "${tmp}.new" mv "${tmp}.new" "$tmp" done mv "$tmp" "$CONF_FILE" } apply_set_user() { [[ -z "$SET_USER" ]] && return 0 require_jq_write local tmp; tmp="$(mktemp)" jq --arg u "$SET_USER" '.ssh_user = $u' "$CONF_FILE" > "$tmp" mv "$tmp" "$CONF_FILE" } # --- Argumente parsen --- while [[ $# -gt 0 ]]; do case "$1" in --minutes) MINUTES="$2"; shift 2 ;; --hours) HOURS="$2"; shift 2 ;; --confdir) CONF_DIR="$2"; shift 2 ;; --show-config) SHOW_CONF=1; shift ;; --add-code) ADD_CODES+=("$2"); shift 2 ;; --del-code) DEL_INDEXES+=("$2"); shift 2 ;; --set-user) SET_USER="$2"; shift 2 ;; --help) usage; exit 0 ;; [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) DATE="$1"; shift ;; *) echo "Unbekannte Option: $1" >&2; usage; exit 1 ;; esac done # --- Configpfade --- CONF_FILE="${CONF_DIR}/checkms.conf" [[ -f "$CONF_FILE" ]] || init_conf # --- Reine Konfig-Operationen (frühzeitiger Exit!) --- if [[ $SHOW_CONF -eq 1 && ${#ADD_CODES[@]} -eq 0 && ${#DEL_INDEXES[@]} -eq 0 && -z "$SET_USER" ]]; then show_config fi # Wenn Codes/User geändert werden sollen, erst anwenden … if [[ ${#ADD_CODES[@]} -gt 0 || ${#DEL_INDEXES[@]} -gt 0 || -n "$SET_USER" ]]; then apply_add_codes apply_del_indexes apply_set_user # Wenn nur Konfig bearbeitet wurde (keine Analyse-Flags/Datum), hier enden if [[ $MINUTES -eq 0 && $HOURS -eq 0 && "$DATE" == "$(date +%Y-%m-%d)" ]]; then # Optional: danach anzeigen [[ $SHOW_CONF -eq 1 ]] && show_config exit 0 fi fi # --- Config laden für Analyse --- if command -v jq >/dev/null 2>&1; then SSH_USER="$(jq -r '.ssh_user' "$CONF_FILE")" CODES_REGEX="$(jq -r '.codes[]' "$CONF_FILE" | paste -sd'|' -)" else SSH_USER="$(grep -o '"ssh_user":[^,}]*' "$CONF_FILE" | cut -d'"' -f4)" CODES_REGEX="$( sed -n 's/.*"codes":[[]\(.*\)[]].*/\1/p' "$CONF_FILE" \ | tr -d ' \t"' | tr ',' '|' )" fi [[ -n "${CODES_REGEX// }" ]] || CODES_REGEX="S3150|S775|S7771[0-9]*" LOGFILE="${DATE}.log" # --- Remote-Kommando --- if type go >/dev/null 2>&1; then REMOTE="go logger" else REMOTE="ssh ${SSH_USER}@logger.in.nessus.at" fi # --- Analyse (remote) --- $REMOTE bash -s <&2 exit 1 fi echo "Analysiere $LOGFILE" echo printf "%-8s %-16s %-8s\n" "COUNT" "IP" "CODE" printf "%-8s %-16s %-8s\n" "-----" "----------------" "------" TF_CMD="" if [ $MINUTES -gt 0 ]; then START_TS=\$(date -d "-$MINUTES minutes" +"%b %_d %H:%M") echo "Zeitraum: letzte $MINUTES Minuten (ab \$START_TS)" TF_CMD="awk -v ts=\"\$START_TS\" '\$0 ~ ts,0'" elif [ $HOURS -gt 0 ]; then START_TS=\$(date -d "-$HOURS hours" +"%b %_d %H") echo "Zeitraum: letzte $HOURS Stunden (ab \$START_TS)" TF_CMD="awk -v ts=\"\$START_TS\" '\$0 ~ ts,0'" fi eval \$SRC \${TF_CMD:+| \$TF_CMD} \ | grep -E 'outlook|hotmail' \ | grep -E "$CODES_REGEX" \ | awk ' { code="NA"; ip="NA"; for (i=1;i<=NF;i++) { if (\$i ~ /\(S[0-9]+\)/) { t=\$i; gsub(/[()]/,"",t); code=t } else if (\$i ~ /^S[0-9]+/) { code=\$i } if (\$i ~ /^\[[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\]$/) { ip=\$i; gsub(/[\[\]]/,"",ip) } } print code, ip } ' \ | sort | uniq -c | sort -nr \ | while read count code ip; do printf "%-8s %-16s %-8s\n" "\$count" "\$ip" "\$code" done EOF