Add script 'dovecot-logreport.sh'.
This commit is contained in:
667
dovecot-logreport.sh
Executable file
667
dovecot-logreport.sh
Executable file
@@ -0,0 +1,667 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# dovecot-logreport.sh
|
||||
#
|
||||
# Zweck:
|
||||
# - Dovecot Logs parsen (imap-login/imap/pop3-login/lmtp/auth/...)
|
||||
# - Lesbare Key/Value-Ausgabe pro Event
|
||||
# - Filtermöglichkeiten:
|
||||
# * Ergebnis: --success / --fail / --error
|
||||
# * Zeit: --since / --until (ISO8601 empfohlen)
|
||||
# --last (bequem: "jetzt - Dauer", z.B. 10m/2h/7d)
|
||||
# * Inhalt: --addr REGEX (match gegen "user ip message")
|
||||
# --email STR (case-insensitive substring auf user)
|
||||
# * Typen: --only-login / --only-lmtp / --only-imap / --only-pop3
|
||||
# --type REGEX (match gegen Dovecot type/service Token)
|
||||
# - Input:
|
||||
# * Standard-Log: /var/log/dovecot/dovecot.log
|
||||
# * --all: nimmt zusätzlich .0 und .*.gz
|
||||
# * --file: eigene Logdateien (mehrfach möglich)
|
||||
# Wichtig: Wenn kein absoluter Pfad angegeben ist,
|
||||
# wird automatisch /var/log/dovecot/ vorangestellt.
|
||||
# - Ausgabe:
|
||||
# * Default: Key/Value Block + Leerzeile als Trenner
|
||||
# * --oneline: 1 Zeile TSV (kein Leerzeilentrenner)
|
||||
# * --stats: Statistik statt Events (inkl. Matrix/Toplisten)
|
||||
# - Live-Modus:
|
||||
# * --follow: tail -F (gz-Dateien werden dafür übersprungen)
|
||||
#
|
||||
# Anforderungen/Annahmen:
|
||||
# - Timestamp steht als erstes Feld in ISO-Format (z.B. 2026-01-17T12:34:56+01:00)
|
||||
# => since/until/last funktionieren als Stringvergleich.
|
||||
# - Dovecot-Zeilen enthalten "dovecot:"
|
||||
#
|
||||
# Beispiele:
|
||||
# - Nur Login-Fails (Default logfile):
|
||||
# ./dovecot-logreport.sh --only-login --fail
|
||||
# - Stats für letzte 6 Stunden:
|
||||
# ./dovecot-logreport.sh --all --last 6h --stats
|
||||
# - Live follow nur IMAP domain:
|
||||
# ./dovecot-logreport.sh --follow --only-imap --email '@example.org'
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
PROG="$(basename "$0")"
|
||||
|
||||
# --- Standard-Logverzeichnis / Default-Datei ---
|
||||
LOGDIR="/var/log/dovecot"
|
||||
DEFAULT_LOG="${LOGDIR}/dovecot.log"
|
||||
|
||||
# --- Optionen ---
|
||||
FOLLOW=0
|
||||
ALL=0
|
||||
ONELINE=0
|
||||
STATS=0
|
||||
|
||||
# Service-/Event-Filter (OR-verknüpft, wenn mehrere gesetzt sind)
|
||||
ONLY_LOGIN=0
|
||||
ONLY_LMTP=0
|
||||
ONLY_IMAP=0
|
||||
ONLY_POP3=0
|
||||
|
||||
# Regex-Filter für type/service
|
||||
TYPE_RE=""
|
||||
|
||||
STATUS_FILTER="all" # all|success|fail|error
|
||||
SINCE=""
|
||||
UNTIL=""
|
||||
LAST_DUR=""
|
||||
ADDR_RE=""
|
||||
EMAIL_NEEDLE=""
|
||||
|
||||
# --file kann mehrfach gesetzt werden
|
||||
declare -a FILES=()
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
$PROG - Dovecot Log Analyzer
|
||||
|
||||
SYNOPSIS
|
||||
$PROG [options]
|
||||
|
||||
DEFAULTS
|
||||
- Default logfile: ${DEFAULT_LOG}
|
||||
- Default logdir : ${LOGDIR}
|
||||
- If you pass --file with a RELATIVE path (no leading "/"),
|
||||
it will be interpreted as: ${LOGDIR}/<yourfile>
|
||||
|
||||
INPUT OPTIONS
|
||||
--file PATH
|
||||
Add a log file to read (can be used multiple times).
|
||||
|
||||
PATH may be:
|
||||
- Absolute: /somewhere/dovecot.log
|
||||
- Relative: dovecot.log.0
|
||||
=> becomes ${LOGDIR}/dovecot.log.0
|
||||
|
||||
Examples:
|
||||
--file dovecot.log
|
||||
--file dovecot.log.0
|
||||
--file dovecot.log.1.gz
|
||||
--file /tmp/test.log
|
||||
|
||||
--all
|
||||
Include rotated logs based on the default logfile:
|
||||
${DEFAULT_LOG}
|
||||
${DEFAULT_LOG}.0
|
||||
${DEFAULT_LOG}.*.gz
|
||||
|
||||
Note:
|
||||
You can combine --all and --file to add extra files.
|
||||
|
||||
--follow
|
||||
Follow the active logfile(s) (tail -F).
|
||||
.gz files cannot be followed and will be skipped in follow mode.
|
||||
|
||||
TIME FILTERS (ISO8601 recommended)
|
||||
--since TS
|
||||
Only show entries with timestamp >= TS
|
||||
Example:
|
||||
--since 2026-01-17T00:00:00
|
||||
|
||||
--until TS
|
||||
Only show entries with timestamp <= TS
|
||||
Example:
|
||||
--until 2026-01-18T23:59:59
|
||||
|
||||
--last DUR
|
||||
Convenience: sets --since to "now - DUR" (only if --since is not set).
|
||||
DUR format:
|
||||
Nm (minutes) e.g. 10m
|
||||
Nh (hours) e.g. 2h
|
||||
Nd (days) e.g. 7d
|
||||
Examples:
|
||||
--last 30m
|
||||
--last 6h
|
||||
--last 1d
|
||||
|
||||
CONTENT FILTERS
|
||||
--addr REGEX
|
||||
Regex match against combined "user ip message".
|
||||
Useful for filtering by IP, username, or keywords.
|
||||
Example:
|
||||
--addr 'rip=188\\.194\\.126\\.134|^188\\.194\\.126\\.134$'
|
||||
--addr 'imap-login'
|
||||
|
||||
--email STR
|
||||
Case-insensitive substring match against extracted user.
|
||||
Works with full mail address, partial, or domain.
|
||||
Examples:
|
||||
--email 'sales@koma-elektronik.com'
|
||||
--email 'sales'
|
||||
--email '@koma-elektronik.com'
|
||||
|
||||
TYPE / SERVICE FILTERS
|
||||
--type REGEX
|
||||
Regex match against the extracted Dovecot service token (type).
|
||||
Examples:
|
||||
--type 'imap-login'
|
||||
--type '^(imap|imap-login)$'
|
||||
--type 'lmtp|auth'
|
||||
|
||||
--only-login
|
||||
Show only login-related events:
|
||||
- services ending in "-login" (imap-login, pop3-login)
|
||||
- typical login/auth messages (logged in, auth failed, login aborted, ...)
|
||||
|
||||
--only-imap
|
||||
Show only IMAP related services (imap + imap-login)
|
||||
|
||||
--only-pop3
|
||||
Show only POP3 related services (pop3 + pop3-login)
|
||||
|
||||
--only-lmtp
|
||||
Show only LMTP service
|
||||
|
||||
RESULT FILTERS
|
||||
--success
|
||||
Only show classified success events
|
||||
|
||||
--fail
|
||||
Only show classified fail events
|
||||
|
||||
--error
|
||||
Only show classified error events
|
||||
|
||||
OUTPUT OPTIONS
|
||||
--oneline
|
||||
One line per event (tab separated):
|
||||
TS<TAB>TYPE<TAB>RESULT<TAB>IP<TAB>USER<TAB>MESSAGE
|
||||
|
||||
--stats
|
||||
Print statistics instead of individual events:
|
||||
- By result (success/fail/error/info)
|
||||
- By type (Top 20)
|
||||
- Top IPs overall + fail-only
|
||||
- Top users overall + fail-only
|
||||
- Matrix: result@type (Top 30)
|
||||
- Fail combos: IP -> user (Top 20)
|
||||
|
||||
HELP
|
||||
-h, --help
|
||||
Show this help
|
||||
|
||||
EXAMPLES
|
||||
# Default logfile, show all failed login attempts
|
||||
$PROG --only-login --fail
|
||||
|
||||
# All rotated logs, stats for brute-force overview (last 6 hours)
|
||||
$PROG --all --only-login --last 6h --stats
|
||||
|
||||
# Follow current log, only IMAP, only fail, only a domain
|
||||
$PROG --follow --only-imap --fail --email '@example.org'
|
||||
|
||||
# Use relative --file paths (auto-prefixed with ${LOGDIR})
|
||||
$PROG --file dovecot.log --file dovecot.log.0 --file dovecot.log.1.gz --stats
|
||||
USAGE
|
||||
}
|
||||
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# normalize_path():
|
||||
# - If PATH starts with "/" -> keep it (absolute path)
|
||||
# - Otherwise -> prefix with LOGDIR
|
||||
# -------------------------------------------------------------------
|
||||
normalize_path() {
|
||||
local p="$1"
|
||||
if [[ "$p" == /* ]]; then
|
||||
printf "%s" "$p"
|
||||
else
|
||||
printf "%s/%s" "$LOGDIR" "$p"
|
||||
fi
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Argumente parsen
|
||||
# -------------------------------------------------------------------
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--file)
|
||||
[[ $# -ge 2 ]] || die "--file needs PATH"
|
||||
FILES+=("$(normalize_path "$2")")
|
||||
shift 2
|
||||
;;
|
||||
--all) ALL=1; shift;;
|
||||
--follow) FOLLOW=1; shift;;
|
||||
|
||||
--since) [[ $# -ge 2 ]] || die "--since needs TS"; SINCE="$2"; shift 2;;
|
||||
--until) [[ $# -ge 2 ]] || die "--until needs TS"; UNTIL="$2"; shift 2;;
|
||||
--last) [[ $# -ge 2 ]] || die "--last needs DUR (e.g. 10m, 2h, 7d)"; LAST_DUR="$2"; shift 2;;
|
||||
|
||||
--addr) [[ $# -ge 2 ]] || die "--addr needs REGEX"; ADDR_RE="$2"; shift 2;;
|
||||
--email) [[ $# -ge 2 ]] || die "--email needs STR"; EMAIL_NEEDLE="$2"; shift 2;;
|
||||
--type) [[ $# -ge 2 ]] || die "--type needs REGEX"; TYPE_RE="$2"; shift 2;;
|
||||
|
||||
--success) STATUS_FILTER="success"; shift;;
|
||||
--fail) STATUS_FILTER="fail"; shift;;
|
||||
--error) STATUS_FILTER="error"; shift;;
|
||||
|
||||
--only-login) ONLY_LOGIN=1; shift;;
|
||||
--only-lmtp) ONLY_LMTP=1; shift;;
|
||||
--only-imap) ONLY_IMAP=1; shift;;
|
||||
--only-pop3) ONLY_POP3=1; shift;;
|
||||
|
||||
--oneline) ONELINE=1; shift;;
|
||||
--stats) STATS=1; shift;;
|
||||
|
||||
-h|--help) usage; exit 0;;
|
||||
*) die "Unknown option: $1";;
|
||||
esac
|
||||
done
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# --last DUR:
|
||||
# If --since is NOT set, compute SINCE as ISO-like timestamp using GNU date.
|
||||
# Accepts: Nm, Nh, Nd (minutes/hours/days)
|
||||
# Example: 10m, 2h, 7d
|
||||
# -------------------------------------------------------------------
|
||||
if [[ -n "${LAST_DUR}" && -z "${SINCE}" ]]; then
|
||||
if [[ "${LAST_DUR}" =~ ^([0-9]+)([mhd])$ ]]; then
|
||||
n="${BASH_REMATCH[1]}"
|
||||
unit="${BASH_REMATCH[2]}"
|
||||
case "$unit" in
|
||||
m) SINCE="$(date -Is -d "${n} minutes ago")" ;;
|
||||
h) SINCE="$(date -Is -d "${n} hours ago")" ;;
|
||||
d) SINCE="$(date -Is -d "${n} days ago")" ;;
|
||||
esac
|
||||
else
|
||||
die "--last expects DUR like 10m, 2h, 7d (got: ${LAST_DUR})"
|
||||
fi
|
||||
fi
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# resolve_files():
|
||||
# Legt fest, welche Dateien gelesen werden:
|
||||
# - explizite --file(s)
|
||||
# - plus optional --all Rotationslogs
|
||||
# - sonst default
|
||||
# -------------------------------------------------------------------
|
||||
resolve_files() {
|
||||
local -a out=()
|
||||
|
||||
# 1) explizit angegebene Dateien
|
||||
if [[ ${#FILES[@]} -gt 0 ]]; then
|
||||
out+=("${FILES[@]}")
|
||||
fi
|
||||
|
||||
# 2) Rotationslogs (basierend auf DEFAULT_LOG)
|
||||
if [[ $ALL -eq 1 ]]; then
|
||||
local base="$DEFAULT_LOG"
|
||||
[[ -f "$base" ]] && out+=("$base")
|
||||
[[ -f "${base}.0" ]] && out+=("${base}.0")
|
||||
for gz in "${base}".*.gz; do
|
||||
[[ -f "$gz" ]] && out+=("$gz")
|
||||
done
|
||||
fi
|
||||
|
||||
# 3) Fallback: Default
|
||||
if [[ ${#out[@]} -eq 0 ]]; then
|
||||
[[ -f "$DEFAULT_LOG" ]] || die "Default logfile $DEFAULT_LOG not found. Use --file or --all."
|
||||
out+=("$DEFAULT_LOG")
|
||||
fi
|
||||
|
||||
# Duplikate entfernen (Reihenfolge bleibt erhalten)
|
||||
printf "%s\n" "${out[@]}" | awk '{ if(!seen[$0]++) print $0 }'
|
||||
}
|
||||
|
||||
mapfile -t LOGFILES < <(resolve_files)
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Follow: nur nicht-gz Dateien (tail -F kann gz nicht live verfolgen)
|
||||
# -------------------------------------------------------------------
|
||||
if [[ $FOLLOW -eq 1 ]]; then
|
||||
mapfile -t FOLLOW_FILES < <(printf "%s\n" "${LOGFILES[@]}" | awk '!/\.gz$/')
|
||||
[[ ${#FOLLOW_FILES[@]} -gt 0 ]] || die "--follow requested, but only .gz files selected. Add an active log with --file."
|
||||
fi
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# POSIX-AWK Parser
|
||||
# - kompatibel zu mawk/nawk/busybox awk
|
||||
# -------------------------------------------------------------------
|
||||
AWK_SCRIPT='
|
||||
function tolow(s, i,c,r) {
|
||||
r=""
|
||||
for (i=1; i<=length(s); i++) {
|
||||
c=substr(s,i,1)
|
||||
r=r "" tolower(c)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
# classify():
|
||||
# Versucht einen Dovecot-Logeintrag grob zu klassifizieren:
|
||||
# success / fail / error / info
|
||||
function classify(msg, m) {
|
||||
m=tolower(msg)
|
||||
|
||||
# typische Fail-Muster (Auth/Login)
|
||||
if (m ~ /(auth failed|authentication failed|login aborted|password mismatch|unknown user|invalid password|disconnected: auth failed)/) return "fail"
|
||||
|
||||
# typische Error-Muster
|
||||
if (m ~ /(fatal|panic|error|critical|internal error|temporary failure)/) return "error"
|
||||
|
||||
# typische Success-Muster
|
||||
if (m ~ /(logged in:|stored mail into mailbox|sieve:.*stored mail|connect from)/) return "success"
|
||||
if (m ~ /(disconnected: logged out|disconnect .*logged out)/) return "success"
|
||||
|
||||
return "info"
|
||||
}
|
||||
|
||||
# extract_type():
|
||||
# Extracts the dovecot service token after "dovecot: ".
|
||||
# Examples:
|
||||
# "dovecot: imap-login: ..." -> imap-login
|
||||
# "dovecot: imap(user@dom):" -> imap
|
||||
# "dovecot: lmtp(123):" -> lmtp
|
||||
function extract_type(line, arr, t) {
|
||||
t="-"
|
||||
if (match(line, /dovecot: [^: ]+/, arr)) {
|
||||
t=substr(arr[0], length("dovecot: ")+1)
|
||||
sub(/\(.*/, "", t)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
# extract_user():
|
||||
# Tries to extract a user/mailbox from common patterns.
|
||||
function extract_user(line, arr, u) {
|
||||
if (match(line, /user=<[^>]+>/, arr)) {
|
||||
return substr(arr[0], 7, length(arr[0])-7-1)
|
||||
}
|
||||
|
||||
if (match(line, /: (imap|pop3|lmtp)\([^)]*\)/, arr)) {
|
||||
u=arr[0]
|
||||
sub(/^: (imap|pop3|lmtp)\(/, "", u)
|
||||
sub(/\)$/, "", u)
|
||||
return u
|
||||
}
|
||||
|
||||
if (match(line, /: lmtp\([^)]*\)</, arr)) {
|
||||
u=arr[0]
|
||||
sub(/^: lmtp\(/, "", u)
|
||||
sub(/<$/, "", u)
|
||||
return u
|
||||
}
|
||||
|
||||
if (match(line, /: auth\([^,]+,/, arr)) {
|
||||
u=arr[0]
|
||||
sub(/^: auth\(/, "", u)
|
||||
sub(/,$/, "", u)
|
||||
return u
|
||||
}
|
||||
|
||||
return "-"
|
||||
}
|
||||
|
||||
# extract_ip():
|
||||
# Extracts remote IP from patterns like "rip=1.2.3.4"
|
||||
# or auth(user,IP,sasl:...)
|
||||
function extract_ip(line, arr, s) {
|
||||
if (match(line, /rip=[^ ,]+/, arr)) {
|
||||
return substr(arr[0], 5)
|
||||
}
|
||||
|
||||
if (match(line, /: auth\([^,]+,[^,]+,/, arr)) {
|
||||
s=arr[0]
|
||||
sub(/^: auth\([^,]+,/, "", s)
|
||||
sub(/,.*/, "", s)
|
||||
return s
|
||||
}
|
||||
|
||||
return "-"
|
||||
}
|
||||
|
||||
# is_login_event():
|
||||
# Heuristik: identifiziert Login/Auth Events
|
||||
function is_login_event(type, msg, m) {
|
||||
if (type ~ /-login$/) return 1
|
||||
m=tolower(msg)
|
||||
if (m ~ /(logged in:|auth failed|authentication failed|login aborted|invalid password|unknown user)/) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# service_match():
|
||||
# only_* Filter: Wenn keiner gesetzt -> alles ok.
|
||||
# Wenn mind. einer gesetzt -> OR-Verknüpfung:
|
||||
# - only_lmtp: type == lmtp
|
||||
# - only_imap: imap oder imap-login
|
||||
# - only_pop3: pop3 oder pop3-login
|
||||
# - only_login: is_login_event() true
|
||||
function service_match(type, msg, only_login, only_lmtp, only_imap, only_pop3) {
|
||||
if (only_login==0 && only_lmtp==0 && only_imap==0 && only_pop3==0) return 1
|
||||
|
||||
if (only_lmtp==1 && type=="lmtp") return 1
|
||||
if (only_imap==1 && (type=="imap" || type=="imap-login")) return 1
|
||||
if (only_pop3==1 && (type=="pop3" || type=="pop3-login")) return 1
|
||||
if (only_login==1 && is_login_event(type, msg)==1) return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# type_regex_match():
|
||||
# Optionaler --type Filter (regex gegen type token)
|
||||
function type_regex_match(type, typere) {
|
||||
if (typere == "") return 1
|
||||
if (type ~ typere) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---- STATS helper: sort and print top N ----
|
||||
function print_top_sorted(title, arr, n, k,i,j,keys,tmp) {
|
||||
print title
|
||||
i=0
|
||||
for (k in arr) { i++; keys[i]=k }
|
||||
|
||||
for (i=1; i<=length(keys); i++) {
|
||||
for (j=i+1; j<=length(keys); j++) {
|
||||
if (arr[keys[j]] > arr[keys[i]]) {
|
||||
tmp=keys[i]; keys[i]=keys[j]; keys[j]=tmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i=1; i<=length(keys) && i<=n; i++) {
|
||||
printf " %7d %s\n", arr[keys[i]], keys[i]
|
||||
}
|
||||
print ""
|
||||
}
|
||||
|
||||
# ---- STATS helper: sort and print top N for matrix-like keys ----
|
||||
function print_matrix(title, arr, n, k,i,j,keys,tmp) {
|
||||
print title
|
||||
i=0
|
||||
for (k in arr) { i++; keys[i]=k }
|
||||
|
||||
for (i=1; i<=length(keys); i++) {
|
||||
for (j=i+1; j<=length(keys); j++) {
|
||||
if (arr[keys[j]] > arr[keys[i]]) {
|
||||
tmp=keys[i]; keys[i]=keys[j]; keys[j]=tmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i=1; i<=length(keys) && i<=n; i++) {
|
||||
printf " %7d %s\n", arr[keys[i]], keys[i]
|
||||
}
|
||||
print ""
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
since = ENVIRON["SINCE"]
|
||||
until = ENVIRON["UNTIL"]
|
||||
addrre = ENVIRON["ADDR_RE"]
|
||||
want = ENVIRON["STATUS_FILTER"]
|
||||
oneline = ENVIRON["ONELINE"] + 0
|
||||
stats = ENVIRON["STATS"] + 0
|
||||
|
||||
emailneedle = tolower(ENVIRON["EMAIL_NEEDLE"])
|
||||
|
||||
onlylogin = ENVIRON["ONLY_LOGIN"] + 0
|
||||
onlylmtp = ENVIRON["ONLY_LMTP"] + 0
|
||||
onlyimap = ENVIRON["ONLY_IMAP"] + 0
|
||||
onlypop3 = ENVIRON["ONLY_POP3"] + 0
|
||||
|
||||
typere = ENVIRON["TYPE_RE"]
|
||||
total=0
|
||||
}
|
||||
|
||||
{
|
||||
line=$0
|
||||
if (line !~ /dovecot:/) next
|
||||
|
||||
# Timestamp (ISO) steht am Anfang
|
||||
ts=$1
|
||||
if (since != "" && ts < since) next
|
||||
if (until != "" && ts > until) next
|
||||
|
||||
type = extract_type(line)
|
||||
|
||||
# --type Filter
|
||||
if (type_regex_match(type, typere) == 0) next
|
||||
|
||||
# Message: alles nach "dovecot: "
|
||||
msg=line
|
||||
sub(/^.*dovecot: /, "", msg)
|
||||
|
||||
# only_* Filter
|
||||
if (service_match(type, msg, onlylogin, onlylmtp, onlyimap, onlypop3) == 0) next
|
||||
|
||||
user = extract_user(line)
|
||||
ip = extract_ip(line)
|
||||
|
||||
res = classify(msg)
|
||||
|
||||
# Ergebnisfilter
|
||||
if (want == "success" && res != "success") next
|
||||
if (want == "fail" && res != "fail") next
|
||||
if (want == "error" && res != "error") next
|
||||
|
||||
# addr filter: regex gegen "user ip msg"
|
||||
hay = user " " ip " " msg
|
||||
if (addrre != "" && hay !~ addrre) next
|
||||
|
||||
# email filter: substring nur gegen user
|
||||
if (emailneedle != "" && index(tolower(user), emailneedle) == 0) next
|
||||
|
||||
total++
|
||||
|
||||
# Stats sammeln
|
||||
if (stats == 1) {
|
||||
c_res[res]++
|
||||
c_type[type]++
|
||||
c_res_type[res "@" type]++
|
||||
|
||||
if (ip != "-") {
|
||||
c_ip[ip]++
|
||||
if (res == "fail") c_ip_fail[ip]++
|
||||
}
|
||||
|
||||
if (user != "-") {
|
||||
c_user[user]++
|
||||
if (res == "fail") c_user_fail[user]++
|
||||
}
|
||||
|
||||
if (res == "fail" && ip != "-" && user != "-") {
|
||||
c_fail_ip_user[ip " -> " user]++
|
||||
}
|
||||
|
||||
next
|
||||
}
|
||||
|
||||
# Eventausgabe
|
||||
if (oneline == 1) {
|
||||
printf "%s\t%s\t%s\t%s\t%s\t%s\n", ts, type, res, ip, user, msg
|
||||
next
|
||||
}
|
||||
|
||||
print "time: " ts
|
||||
print "type: " type
|
||||
print "client-ip: " ip
|
||||
print "user: " user
|
||||
print "result: " res
|
||||
print "message: " msg
|
||||
print ""
|
||||
}
|
||||
|
||||
END {
|
||||
if (stats != 1) exit
|
||||
|
||||
print "Events: " total
|
||||
print ""
|
||||
|
||||
print "By result"
|
||||
printf " %7d success\n", c_res["success"]+0
|
||||
printf " %7d fail\n", c_res["fail"]+0
|
||||
printf " %7d error\n", c_res["error"]+0
|
||||
printf " %7d info\n", c_res["info"]+0
|
||||
print ""
|
||||
|
||||
print_top_sorted("By type (Top 20)", c_type, 20)
|
||||
|
||||
print_top_sorted("Top client IPs (overall, Top 10)", c_ip, 10)
|
||||
print_top_sorted("Top client IPs (fail only, Top 10)", c_ip_fail, 10)
|
||||
|
||||
print_top_sorted("Top users (overall, Top 10)", c_user, 10)
|
||||
print_top_sorted("Top users (fail only, Top 10)", c_user_fail, 10)
|
||||
|
||||
print_matrix("Matrix result@type (Top 30)", c_res_type, 30)
|
||||
|
||||
print_top_sorted("Fail combos IP -> user (Top 20)", c_fail_ip_user, 20)
|
||||
}
|
||||
'
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# run_pipeline():
|
||||
# Liest die ausgewählten Files (inkl. .gz über zcat -f) und pipe't
|
||||
# in awk. In --follow Mode: tail -F auf nicht-gz Dateien.
|
||||
# -------------------------------------------------------------------
|
||||
run_pipeline() {
|
||||
local f
|
||||
|
||||
if [[ $FOLLOW -eq 1 ]]; then
|
||||
tail -F "${FOLLOW_FILES[@]}" | \
|
||||
env SINCE="$SINCE" UNTIL="$UNTIL" ADDR_RE="$ADDR_RE" EMAIL_NEEDLE="$EMAIL_NEEDLE" TYPE_RE="$TYPE_RE" \
|
||||
ONLY_LOGIN="$ONLY_LOGIN" ONLY_LMTP="$ONLY_LMTP" ONLY_IMAP="$ONLY_IMAP" ONLY_POP3="$ONLY_POP3" \
|
||||
STATUS_FILTER="$STATUS_FILTER" ONELINE="$ONELINE" STATS="$STATS" \
|
||||
awk "$AWK_SCRIPT"
|
||||
return
|
||||
fi
|
||||
|
||||
{
|
||||
for f in "${LOGFILES[@]}"; do
|
||||
if [[ "$f" =~ \.gz$ ]]; then
|
||||
zcat -f -- "$f" 2>/dev/null || true
|
||||
else
|
||||
cat -- "$f" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
} | env SINCE="$SINCE" UNTIL="$UNTIL" ADDR_RE="$ADDR_RE" EMAIL_NEEDLE="$EMAIL_NEEDLE" TYPE_RE="$TYPE_RE" \
|
||||
ONLY_LOGIN="$ONLY_LOGIN" ONLY_LMTP="$ONLY_LMTP" ONLY_IMAP="$ONLY_IMAP" ONLY_POP3="$ONLY_POP3" \
|
||||
STATUS_FILTER="$STATUS_FILTER" ONELINE="$ONELINE" STATS="$STATS" \
|
||||
awk "$AWK_SCRIPT"
|
||||
}
|
||||
|
||||
run_pipeline
|
||||
|
||||
Reference in New Issue
Block a user