update mail-status rework
rework script
This commit is contained in:
+246
-144
@@ -1,111 +1,111 @@
|
||||
#!/bin/bash
|
||||
# Postfix Mail Summary Tool - Hardened & Domain-Focused Version (v3.0.1)
|
||||
|
||||
# 1. RESOURCE LIMITS & ENVIRONMENT
|
||||
ulimit -t 15
|
||||
ulimit -v 500000
|
||||
ulimit -f 102400
|
||||
# Postfix Mail Summary Tool - PRODUCTION READY EDITION (v4.0.7)
|
||||
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
# 2. LOCKFILE & PRE-FLIGHT CHECKS
|
||||
LOCKFILE="/var/lock/postfix_summary.lock"
|
||||
[ -w /var/lock ] || LOCKFILE="/tmp/postfix_summary.lock"
|
||||
|
||||
exec 9>>"$LOCKFILE"
|
||||
if ! flock -n 9; then
|
||||
echo "Error: Another instance is already running." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_DATA=$(mktemp /tmp/postfix_log.XXXXXX)
|
||||
trap 'rm -f "$TMP_DATA" 2>/dev/null' EXIT
|
||||
|
||||
export USE_COLOR=0
|
||||
[[ -t 1 ]] && USE_COLOR=1
|
||||
|
||||
# 3. HELP MENU
|
||||
# ==============================================================================
|
||||
# 1. HELP MENU (EXTENDED & DETAILED)
|
||||
# ==============================================================================
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Usage: mail-stats [OPTIONS]
|
||||
|
||||
FILTERING OPTIONS:
|
||||
-d, --date "MMM DD" Filter by date (e.g., "Sep 4" or "Sep 24"). Default: today.
|
||||
Use "" (empty string) to search all dates in available logs.
|
||||
--time "HH:MM" Filter by specific time/hour (e.g., "14:30" or "14:").
|
||||
-f, --from EMAIL Filter by sender email or domain (e.g., "admin@" or "domain.com").
|
||||
-t, --to EMAIL Filter by recipient email or domain.
|
||||
-a, --ip IP_ADDRESS Filter by source/relay IP address (e.g., "192.168.1.").
|
||||
-s, --status STATUS Filter by delivery status (e.g., sent, deferred, reject, bounced). Case-insensitive.
|
||||
FILTERING OPTIONS (Combine multiple filters to narrow down results):
|
||||
-d, --date "MMM DD" Filter by exact date. Default is today.
|
||||
Examples: -d "Sep 4" | -d "Oct 12"
|
||||
Special: -d "" (Empty string searches ALL available dates)
|
||||
--time "HH:MM" Filter by exact time or whole hour.
|
||||
Examples: --time "14:30" (exact minute) | --time "14:" (entire hour)
|
||||
-f, --from STRING Filter by sender email address or domain.
|
||||
Examples: -f "admin@domain.com" | -f "paypal"
|
||||
-t, --to STRING Filter by recipient email address or domain.
|
||||
Examples: -t "user@local.sk" | -t "gmail.com"
|
||||
-a, --ip STRING Filter by source/relay IP address (full or partial).
|
||||
Examples: -a "192.168.1.50" | -a "10.0."
|
||||
-s, --status STATUS Filter by delivery status (case-insensitive).
|
||||
Examples: -s sent | -s deferred | -s reject | -s bounced | -s greylist
|
||||
-i, --id QUEUE_ID Search for a specific Postfix Queue ID (Enables Detail View).
|
||||
Example: -i 4T3bV22xLnz1t
|
||||
--min-size BYTES Show only emails larger than specified BYTES.
|
||||
Example: --min-size 10485760 (Find emails larger than ~10MB)
|
||||
-R, --regex Treat filters (-f, -t, -a, -s) as Regular Expressions.
|
||||
Example: -R -f "^admin.*@.*\.sk$"
|
||||
|
||||
OUTPUT OPTIONS:
|
||||
-l, --list NUMBER Number of records to show (1-1000, default: 10).
|
||||
-S, --summary Show aggregate statistics (Top IPs, Senders, Recipients, Statuses) instead of list.
|
||||
-c, --csv Output data in CSV format (disables colors).
|
||||
OUTPUT & RUN MODE OPTIONS:
|
||||
-l, --list NUMBER Number of latest records to display (1-1000). Default: 10.
|
||||
Example: -l 50
|
||||
-S, --summary Show aggregate statistics (Top IPs, Senders, Data usage) instead of list.
|
||||
-c, --csv Output data in CSV format (disables colors for easy Excel import).
|
||||
-w, --watch Live Watch Mode (tail -f). Runs continuously and shows new emails in real-time.
|
||||
|
||||
MISC OPTIONS:
|
||||
--log-dir PATH Custom path to log files (default: /var/log).
|
||||
-h, --help Display this help message.
|
||||
-h, --help Display this detailed help message.
|
||||
|
||||
EXAMPLES:
|
||||
# Search for rejected emails today and show summary stats
|
||||
mail-stats -s reject -S
|
||||
==============================================================================
|
||||
PRACTICAL EXAMPLES (Copy & Paste):
|
||||
|
||||
# Find all emails sent from a specific IP around 14:30 on Sep 4
|
||||
mail-stats -d "Sep 4" --time "14:30" -a "192.168.1.50"
|
||||
1. Live monitor all incoming REJECTED emails in real-time:
|
||||
mail-stats -w -s reject
|
||||
|
||||
# Export 50 latest records for a specific target domain to a CSV file
|
||||
mail-stats -t targetdomain.com -l 50 -c > report.csv
|
||||
2. Show statistical summary of all emails sent to 'gmail.com' today:
|
||||
mail-stats -t gmail.com -S
|
||||
|
||||
# Check logs in a custom backup directory
|
||||
mail-stats --log-dir /mnt/backups/mail_logs/ -d "Aug 15"
|
||||
3. Export the last 100 emails from a specific IP to a CSV file:
|
||||
mail-stats -a 192.168.1.100 -l 100 -c > ip_report.csv
|
||||
|
||||
4. Find all large emails (over 10MB) across all old logs (ignoring date):
|
||||
mail-stats -d "" --min-size 10485760
|
||||
|
||||
5. Find what happened around 14:30 today for a specific sender domain:
|
||||
mail-stats --time "14:30" -f "@mydomain.sk" -l 50
|
||||
|
||||
6. Use REGEX to find senders starting with 'info' or 'admin':
|
||||
mail-stats -R -f "^(info|admin)@"
|
||||
==============================================================================
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 4. ARGUMENT PARSING
|
||||
LIMIT=10; MAX_LIMIT=1000; MAX_INPUT_LEN=100
|
||||
# ==============================================================================
|
||||
# 2. ARGUMENT PARSING
|
||||
# ==============================================================================
|
||||
LIMIT=10; MAX_LIMIT=1000
|
||||
FROM_FILTER=""; TO_FILTER=""; ID_FILTER=""; DATE_FILTER=""; IP_FILTER=""
|
||||
STATUS_FILTER=""; TIME_FILTER=""; LOG_DIR="/var/log"
|
||||
DATE_SET=0; CSV_MODE=0; SUMMARY_MODE=0
|
||||
STATUS_FILTER=""; TIME_FILTER=""; LOG_DIR="/var/log"; MIN_SIZE=0
|
||||
DATE_SET=0; CSV_MODE=0; SUMMARY_MODE=0; WATCH_MODE=0; REGEX_MODE=0
|
||||
|
||||
check_arg() {
|
||||
local opt="$1"
|
||||
shift
|
||||
if [[ "$#" -lt 1 ]]; then
|
||||
echo "Error: Option '$opt' requires an argument." >&2
|
||||
exit 1
|
||||
if [[ "$2" -lt 1 ]]; then
|
||||
echo "Error: Option '$1' requires an argument." >&2; exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sanitize() { echo "${1:0:$MAX_INPUT_LEN}" | tr -cd '[:alnum:]@._ -:'; }
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help) show_help ;;
|
||||
-l|--list) check_arg "$1" "${2:-}"; if [[ "$2" =~ ^[0-9]+$ ]]; then LIMIT="$2"; fi; shift ;;
|
||||
-f|--from) check_arg "$1" "${2:-}"; FROM_FILTER=$(sanitize "${2:-}"); shift ;;
|
||||
-t|--to) check_arg "$1" "${2:-}"; TO_FILTER=$(sanitize "${2:-}"); shift ;;
|
||||
-i|--id) check_arg "$1" "${2:-}"; ID_FILTER=$(sanitize "${2:-}"); shift ;;
|
||||
-a|--ip) check_arg "$1" "${2:-}"; IP_FILTER=$(sanitize "${2:-}"); shift ;;
|
||||
-s|--status) check_arg "$1" "${2:-}"; STATUS_FILTER=$(sanitize "${2:-}"); shift ;;
|
||||
--time) check_arg "$1" "${2:-}"; TIME_FILTER=$(sanitize "${2:-}"); shift ;;
|
||||
--log-dir) check_arg "$1" "${2:-}"; LOG_DIR="${2:-}"; shift ;;
|
||||
-l|--list) check_arg "$1" $#; if [[ "$2" =~ ^[0-9]+$ ]]; then LIMIT="$2"; fi; shift ;;
|
||||
-f|--from) check_arg "$1" $#; FROM_FILTER="$2"; shift ;;
|
||||
-t|--to) check_arg "$1" $#; TO_FILTER="$2"; shift ;;
|
||||
-i|--id) check_arg "$1" $#; ID_FILTER="$2"; shift ;;
|
||||
-a|--ip) check_arg "$1" $#; IP_FILTER="$2"; shift ;;
|
||||
-s|--status) check_arg "$1" $#; STATUS_FILTER="$2"; shift ;;
|
||||
--time) check_arg "$1" $#; TIME_FILTER="$2"; shift ;;
|
||||
--log-dir) check_arg "$1" $#; LOG_DIR="$2"; shift ;;
|
||||
--min-size) check_arg "$1" $#; if [[ "$2" =~ ^[0-9]+$ ]]; then MIN_SIZE="$2"; fi; shift ;;
|
||||
-S|--summary) SUMMARY_MODE=1 ;;
|
||||
-c|--csv) CSV_MODE=1 ;;
|
||||
-d|--date)
|
||||
if [[ "$#" -lt 2 ]]; then echo "Error: Option '$1' requires an argument."; exit 1; fi
|
||||
DATE_FILTER=$(sanitize "${2:-}"); DATE_SET=1; shift ;;
|
||||
-w|--watch) WATCH_MODE=1 ;;
|
||||
-R|--regex) REGEX_MODE=1 ;;
|
||||
-d|--date) check_arg "$1" $#; DATE_FILTER="$2"; DATE_SET=1; shift ;;
|
||||
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
(( LIMIT < 1 )) && LIMIT=1
|
||||
(( LIMIT > MAX_LIMIT )) && LIMIT=$MAX_LIMIT
|
||||
if (( LIMIT < 1 )); then LIMIT=1; fi
|
||||
if (( LIMIT > MAX_LIMIT )); then LIMIT=$MAX_LIMIT; fi
|
||||
|
||||
if [ "$DATE_SET" -eq 0 ]; then
|
||||
DATE_FILTER=$(date '+%b %e')
|
||||
@@ -114,49 +114,131 @@ else
|
||||
fi
|
||||
readonly DATE_FILTER
|
||||
|
||||
DETAIL_VIEW=0; [[ -n "$ID_FILTER" && "$ID_FILTER" != "NOQUEUE" ]] && DETAIL_VIEW=1
|
||||
DETAIL_VIEW=0
|
||||
if [[ -n "$ID_FILTER" && "$ID_FILTER" != "NOQUEUE" ]]; then
|
||||
DETAIL_VIEW=1
|
||||
fi
|
||||
|
||||
# 5. LOG FILE DISCOVERY
|
||||
export USE_COLOR=0
|
||||
if [[ -t 1 ]]; then
|
||||
USE_COLOR=1
|
||||
fi
|
||||
if [[ "$CSV_MODE" -eq 1 ]]; then
|
||||
USE_COLOR=0
|
||||
fi
|
||||
export CSV_MODE
|
||||
|
||||
# SECURITY SAFEGUARD: Prevent ReDoS in Watch Mode
|
||||
if [[ "$WATCH_MODE" -eq 1 && "$REGEX_MODE" -eq 1 ]]; then
|
||||
echo "Security Warning: Regular Expressions (-R) are disabled in Watch Mode (-w) to prevent CPU locking (ReDoS)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ==============================================================================
|
||||
# 3. RESOURCE LIMITS & LOCKFILES (PRODUCTION SAFEGUARDS)
|
||||
# ==============================================================================
|
||||
if [[ "$WATCH_MODE" -eq 1 ]]; then
|
||||
LOCKFILE="/tmp/postfix_watch.lock"
|
||||
ulimit -v 500000
|
||||
else
|
||||
LOCKFILE="/tmp/postfix_summary.lock"
|
||||
ulimit -t 15
|
||||
ulimit -v 500000
|
||||
ulimit -f 102400
|
||||
fi
|
||||
|
||||
exec 9>>"$LOCKFILE"
|
||||
if ! flock -n 9; then
|
||||
echo "Error: Another instance of this mode is already running." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_DATA=$(mktemp /tmp/postfix_log.XXXXXX)
|
||||
trap 'rm -f "$TMP_DATA" 2>/dev/null' EXIT
|
||||
|
||||
# ==============================================================================
|
||||
# 4. LOG FILE DISCOVERY & PROCESSING ENGINE
|
||||
# ==============================================================================
|
||||
if [ ! -d "$LOG_DIR" ]; then
|
||||
echo "Error: Directory $LOG_DIR does not exist." >&2; exit 1
|
||||
fi
|
||||
|
||||
LOG_FILES=()
|
||||
while IFS= read -r -d '' file; do LOG_FILES+=("$file"); done < <(find "$LOG_DIR" -maxdepth 1 \( -name 'maillog*' -o -name 'mail.log*' \) -type f -print0 2>/dev/null | sort -z -r)
|
||||
[ ${#LOG_FILES[@]} -eq 0 ] && { echo "Error: No Postfix logs found in $LOG_DIR." >&2; exit 1; }
|
||||
get_log_stream() {
|
||||
if [[ "$WATCH_MODE" -eq 1 ]]; then
|
||||
ACTIVE_LOG=""
|
||||
if [[ -f "$LOG_DIR/maillog" ]]; then ACTIVE_LOG="$LOG_DIR/maillog"; fi
|
||||
if [[ -z "$ACTIVE_LOG" && -f "$LOG_DIR/mail.log" ]]; then ACTIVE_LOG="$LOG_DIR/mail.log"; fi
|
||||
|
||||
# 6. LOG PROCESSING
|
||||
process_logs() {
|
||||
for log in "${LOG_FILES[@]}"; do
|
||||
[ ! -r "$log" ] && continue
|
||||
if [[ "$log" =~ \.gz$ ]]; then
|
||||
[[ -n "$DATE_FILTER" ]] && ! zgrep -m 1 -q "$DATE_FILTER" "$log" 2>/dev/null && continue
|
||||
zcat -- "$log" 2>/dev/null
|
||||
else
|
||||
[[ -n "$DATE_FILTER" ]] && ! grep -m 1 -q "$DATE_FILTER" "$log" 2>/dev/null && continue
|
||||
cat -- "$log" 2>/dev/null
|
||||
if [[ -z "$ACTIVE_LOG" ]]; then
|
||||
echo "Error: No active log found to watch." >&2; exit 1
|
||||
fi
|
||||
done
|
||||
echo "Starting Live Watch Mode on: $ACTIVE_LOG (Ctrl+C to stop)..." >&2
|
||||
tail -n 100 -F "$ACTIVE_LOG"
|
||||
else
|
||||
LOG_FILES=()
|
||||
while IFS= read -r -d '' file; do
|
||||
LOG_FILES+=("$file")
|
||||
done < <(find "$LOG_DIR" -maxdepth 1 \( -name 'maillog*' -o -name 'mail.log*' \) -type f -print0 2>/dev/null | sort -z -r)
|
||||
|
||||
if [ ${#LOG_FILES[@]} -eq 0 ]; then
|
||||
echo "Error: No Postfix logs found." >&2; exit 1
|
||||
fi
|
||||
|
||||
for log in "${LOG_FILES[@]}"; do
|
||||
if [ ! -r "$log" ]; then continue; fi
|
||||
|
||||
if [[ "$log" =~ \.gz$ ]]; then
|
||||
if [[ -n "$DATE_FILTER" ]]; then
|
||||
if ! zgrep -m 1 -q -e "$DATE_FILTER" -- "$log" 2>/dev/null; then continue; fi
|
||||
fi
|
||||
zcat -- "$log" 2>/dev/null
|
||||
else
|
||||
if [[ -n "$DATE_FILTER" ]]; then
|
||||
if ! grep -m 1 -q -e "$DATE_FILTER" -- "$log" 2>/dev/null; then continue; fi
|
||||
fi
|
||||
cat -- "$log" 2>/dev/null
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
process_logs | awk -v d_filt="$DATE_FILTER" -v tm_filt="$TIME_FILTER" -v f_filt="$FROM_FILTER" -v t_filt="$TO_FILTER" -v i_filt="$ID_FILTER" -v ip_filt="$IP_FILTER" -v st_filt="$STATUS_FILTER" '
|
||||
AWK_SCRIPT='
|
||||
BEGIN {
|
||||
split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", n);
|
||||
for(i in n) { m[n[i]]=sprintf("%02d",i); months[n[i]]=1; }
|
||||
db_count = 0;
|
||||
}
|
||||
function chk(val, filt, is_regex) {
|
||||
if (filt == "") return 1;
|
||||
if (is_regex == 1 || is_regex == "1") {
|
||||
return (tolower(val) ~ tolower(filt));
|
||||
}
|
||||
return (index(tolower(val), tolower(filt)) > 0);
|
||||
}
|
||||
function sanitize(str) { gsub(/[^[:print:]]/, "", str); return str; }
|
||||
!($1 in months) { next }
|
||||
{ if (length($0) < 20 || length($0) > 2048) next; }
|
||||
(d_filt != "" && substr($0, 1, length(d_filt)) != d_filt) { next }
|
||||
{ if (length($0) < 20 || length($0) > 4096) next; }
|
||||
(w_mode == 0 && d_filt != "" && substr($0, 1, length(d_filt)) != d_filt) { next }
|
||||
(tm_filt != "" && index($3, tm_filt) != 1) { next }
|
||||
{
|
||||
if (db_count > 100000) { split("", db_from); split("", db_time); split("", db_in_ip); db_count = 0; }
|
||||
if (db_count > 100000) { split("", db_from); split("", db_time); split("", db_in_ip); split("", db_subj); split("", db_size); db_count = 0; }
|
||||
qid = ""; for(i=1; i<=9; i++) if($i ~ /^[A-F0-9]+:$/) { qid=$i; gsub(/:/,"",qid); break; }
|
||||
curr_t = $1"-"$2"_"$3;
|
||||
if (qid != "" && !(qid in db_from)) { db_count++; }
|
||||
|
||||
if (qid != "" && index($0, "warning: header Subject:") > 0) {
|
||||
if (match($0, /Subject: .* from /)) {
|
||||
subj = substr($0, RSTART+9, RLENGTH-15); db_subj[qid] = sanitize(subj);
|
||||
}
|
||||
}
|
||||
if (qid != "" && index($0, "size=") > 0) {
|
||||
if (match($0, /size=[0-9]+/)) db_size[qid] = substr($0, RSTART+5, RLENGTH-5);
|
||||
}
|
||||
if (index($0, "amavis") > 0 && index($0, "queued_as:") > 0) {
|
||||
match($0, /queued_as: [A-F0-9]+/); aqid = substr($0, RSTART+11, RLENGTH-11);
|
||||
if (match($0, /\[[0-9]{1,3}\.[0-9.]+/)) db_in_ip[aqid] = substr($0, RSTART+1, RLENGTH-1);
|
||||
if(match($0, /queued_as: [A-F0-9]+/)) {
|
||||
aqid = substr($0, RSTART+11, RLENGTH-11);
|
||||
if (match($0, /\[[0-9]{1,3}\.[0-9.]+/)) db_in_ip[aqid] = substr($0, RSTART+1, RLENGTH-1);
|
||||
}
|
||||
}
|
||||
if (qid != "" && index($0, "client=") > 0) {
|
||||
if (match($0, /\[[0-9]{1,3}\.[0-9.]+/)) {
|
||||
@@ -166,7 +248,7 @@ BEGIN {
|
||||
db_time[qid] = curr_t;
|
||||
}
|
||||
if (qid != "" && index($0, "from=<") > 0) {
|
||||
match($0, /from=<[^>]*>/); if (RLENGTH > 0) db_from[qid] = substr($0, RSTART+6, RLENGTH-7);
|
||||
if (match($0, /from=<[^>]*>/)) db_from[qid] = substr($0, RSTART+6, RLENGTH-7);
|
||||
}
|
||||
if (qid != "" && index($0, "to=<") > 0 && index($0, "status=") > 0) {
|
||||
if (index($0, "relay=127.0.0.1") > 0 || index($0, "10025") > 0) next;
|
||||
@@ -179,10 +261,13 @@ BEGIN {
|
||||
s_re = "OK"; if (match($0, /\([^)]*\)/)) { s_re = substr($0, RSTART+1, RLENGTH-2); gsub(/ /,"_",s_re); }
|
||||
s_f = (db_from[qid] != "") ? db_from[qid] : "-";
|
||||
s_tm = (db_time[qid] != "") ? db_time[qid] : curr_t;
|
||||
s_sz = (db_size[qid] != "") ? db_size[qid] : 0;
|
||||
s_sub = (db_subj[qid] != "") ? db_subj[qid] : "-"; gsub(/ /,"_",s_sub);
|
||||
|
||||
if ((f_filt == "" || index(s_f, f_filt) > 0) && (t_filt == "" || index(s_to, t_filt) > 0) && (i_filt == "" || index(qid, i_filt) > 0) && (ip_filt == "" || index(final_ip, ip_filt) > 0) && (st_filt == "" || index(tolower(s_st), tolower(st_filt)) > 0)) {
|
||||
if (s_sz >= m_sz && chk(s_f, f_filt, r_mode) && chk(s_to, t_filt, r_mode) && chk(qid, i_filt, r_mode) && chk(final_ip, ip_filt, r_mode) && chk(s_st, st_filt, r_mode)) {
|
||||
sort_k = m[$1] sprintf("%02d", $2); tm_tmp=$3; gsub(/:/,"",tm_tmp); sort_k = sort_k tm_tmp;
|
||||
print sort_k " " s_tm " " qid " " final_ip " " s_f " " s_to " " s_st " " s_re;
|
||||
out = sort_k " " s_tm " " qid " " final_ip " " s_f " " s_to " " s_st " " s_sz " " s_sub " " s_re;
|
||||
if (w_mode == 1) { print out; fflush(); } else { print out > tmp_file; }
|
||||
}
|
||||
}
|
||||
if (index($0, "NOQUEUE: reject:") > 0 && (i_filt == "" || i_filt == "NOQUEUE")) {
|
||||
@@ -193,23 +278,62 @@ BEGIN {
|
||||
s_re = "Rejected"; if (match($0, /: [^;]+; /)) s_re = substr($0, RSTART+2, RLENGTH-4); gsub(/ /,"_",s_re);
|
||||
sort_k = m[$1] sprintf("%02d", $2); tm_tmp=$3; gsub(/:/,"",tm_tmp); sort_k = sort_k tm_tmp;
|
||||
|
||||
if ((f_filt == "" || index(f, f_filt) > 0) && (t_filt == "" || index(t, t_filt) > 0) && (ip_filt == "" || index(nip, ip_filt) > 0) && (st_filt == "" || index(tolower(s_st), tolower(st_filt)) > 0)) {
|
||||
print sort_k " " curr_t " NOQUEUE " nip " " f " " t " " s_st " " s_re;
|
||||
if (chk(f, f_filt, r_mode) && chk(t, t_filt, r_mode) && chk(nip, ip_filt, r_mode) && chk(s_st, st_filt, r_mode)) {
|
||||
out = sort_k " " curr_t " NOQUEUE " nip " " f " " t " " s_st " 0 - " s_re;
|
||||
if (w_mode == 1) { print out; fflush(); } else { print out > tmp_file; }
|
||||
}
|
||||
}
|
||||
}' > "$TMP_DATA"
|
||||
}'
|
||||
|
||||
# 7. FINAL OUTPUT RENDERING
|
||||
[[ "$CSV_MODE" -eq 1 ]] && USE_COLOR=0
|
||||
# ==============================================================================
|
||||
# 5. EXECUTION & OUTPUT RENDERING
|
||||
# ==============================================================================
|
||||
format_output() {
|
||||
awk -v u_col="$USE_COLOR" -v csv="$CSV_MODE" -v d_view="$DETAIL_VIEW" '
|
||||
function csv_safe(s) { if(s ~ /^[=+\-@]/) { s=" "s }; gsub(/"/,"\"\"",s); return s; }
|
||||
function d_cut(e, m) { if(length(e)<=m)return e; return ".." substr(e, length(e)-m+2); }
|
||||
BEGIN { b="\033[1;34m"; r="\033[0m"; g="\033[1;32m"; rd="\033[1;31m"; y="\033[1;33m"; gr="\033[0;90m"; c="\033[0;36m"; if(u_col!="1")b=r=g=rd=y=gr=c="";
|
||||
if(csv==1 && d_view==0) print "DATE-TIME,QUEUE_ID,EXT_IP,SENDER,RECIPIENT,STATUS,SIZE_BYTES,SUBJECT,REASON"; }
|
||||
{
|
||||
tm=$2; gsub(/_/," ",tm); id=$3; ip=$4;
|
||||
from=$5; to=$6; st=$7; sz=$8;
|
||||
subj=$9; gsub(/_/," ",subj);
|
||||
re=$10; gsub(/_/," ",re);
|
||||
|
||||
if (d_view == 1) {
|
||||
printf "\n%s--- Detail for ID: %s ---%s\nDate: %s\nIP: %s\nFrom: %s\nTo: %s\nStatus: %s\nSize: %s Bytes\nSubject: %s\nReason: %s\n", b,id,r,tm,ip,from,to,st,sz,subj,re;
|
||||
fflush();
|
||||
} else if (csv == 1) {
|
||||
printf "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n", csv_safe(tm), csv_safe(id), csv_safe(ip), csv_safe(from), csv_safe(to), csv_safe(st), csv_safe(sz), csv_safe(subj), csv_safe(re);
|
||||
fflush();
|
||||
} else {
|
||||
scol = (st == "sent") ? g : (st == "deferred" || st == "GREYLIST" ? y : rd);
|
||||
s_re = (match(re, /[0-9]{3} [0-9]\.[0-9]\.[0-9]/)) ? substr(re, RSTART, RLENGTH) : substr(re, 1, 15);
|
||||
printf "%-16s %s%-12s%s %s%-16s%s %-32s %-28s %s%-10s%s %s[%s]%s\n", tm, (id=="NOQUEUE"?rd:b), id, r, gr, sprintf("%.15s", ip), r, d_cut(from, 31), d_cut(to, 27), scol, st, r, (st=="sent"?c:scol), s_re, r;
|
||||
fflush();
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
if [[ "$WATCH_MODE" -eq 1 ]]; then
|
||||
if [[ "$USE_COLOR" -eq 1 ]]; then
|
||||
printf "\033[1m%-16s %-12s %-16s %-32s %-28s %-10s %s\033[0m\n" "DATE-TIME" "QUEUE_ID" "EXT_IP" "SENDER" "RECIPIENT" "STATUS" "REASON"
|
||||
fi
|
||||
get_log_stream | awk -v d_filt="$DATE_FILTER" -v tm_filt="$TIME_FILTER" -v f_filt="$FROM_FILTER" -v t_filt="$TO_FILTER" -v i_filt="$ID_FILTER" -v ip_filt="$IP_FILTER" -v st_filt="$STATUS_FILTER" -v m_sz="$MIN_SIZE" -v w_mode=1 -v r_mode="$REGEX_MODE" -v tmp_file="$TMP_DATA" "$AWK_SCRIPT" | format_output
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Run normal processing
|
||||
get_log_stream | awk -v d_filt="$DATE_FILTER" -v tm_filt="$TIME_FILTER" -v f_filt="$FROM_FILTER" -v t_filt="$TO_FILTER" -v i_filt="$ID_FILTER" -v ip_filt="$IP_FILTER" -v st_filt="$STATUS_FILTER" -v m_sz="$MIN_SIZE" -v w_mode=0 -v r_mode="$REGEX_MODE" -v tmp_file="$TMP_DATA" "$AWK_SCRIPT"
|
||||
|
||||
# ================= SUMMARY MODE =================
|
||||
if [[ "$SUMMARY_MODE" -eq 1 ]]; then
|
||||
total_records=$(wc -l < "$TMP_DATA")
|
||||
[[ "$USE_COLOR" -eq 1 ]] && printf "\033[1;36m"
|
||||
|
||||
if [[ "$USE_COLOR" -eq 1 ]]; then printf "\033[1;36m"; fi
|
||||
echo "=========================================================="
|
||||
echo " SUMMARY STATISTICS (Total records: ${total_records})"
|
||||
echo "=========================================================="
|
||||
[[ "$USE_COLOR" -eq 1 ]] && printf "\033[0m"
|
||||
if [[ "$USE_COLOR" -eq 1 ]]; then printf "\033[0m"; fi
|
||||
|
||||
if [[ "$total_records" -eq 0 ]]; then
|
||||
echo "No data matched your filters."
|
||||
@@ -218,57 +342,35 @@ if [[ "$SUMMARY_MODE" -eq 1 ]]; then
|
||||
|
||||
print_top() {
|
||||
local col=$1; local title=$2; local max=$3
|
||||
[[ "$USE_COLOR" -eq 1 ]] && printf "\n\033[1;33m%s\033[0m\n" "$title" || printf "\n%s\n" "$title"
|
||||
if [[ "$USE_COLOR" -eq 1 ]]; then
|
||||
printf "\n\033[1;33m%s\033[0m\n" "$title"
|
||||
else
|
||||
printf "\n%s\n" "$title"
|
||||
fi
|
||||
awk "{print \$$col}" "$TMP_DATA" | sort | uniq -c | sort -nr | head -n "$max" | awk '{printf " %6d %s\n", $1, $2}'
|
||||
}
|
||||
|
||||
print_top 7 "STATUS BREAKDOWN:" 10
|
||||
print_top 4 "TOP 10 SOURCE IPs:" 10
|
||||
print_top 5 "TOP 10 SENDERS:" 10
|
||||
print_top 6 "TOP 10 RECIPIENTS:" 10
|
||||
|
||||
if [[ "$USE_COLOR" -eq 1 ]]; then
|
||||
printf "\n\033[1;33mBANDWIDTH CONSUMPTION:\033[0m\n"
|
||||
else
|
||||
printf "\nBANDWIDTH CONSUMPTION:\n"
|
||||
fi
|
||||
awk '{sum+=$8} END {printf " Total Processed Size: %.2f MB\n", sum/1048576}' "$TMP_DATA"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ================= DETAIL / LIST MODE =================
|
||||
if [[ "$DETAIL_VIEW" -eq 1 ]]; then
|
||||
if [ ! -s "$TMP_DATA" ]; then echo "No records found for ID: $ID_FILTER"; exit 0; fi
|
||||
sort -n "$TMP_DATA" | awk -v u_col="$USE_COLOR" '{
|
||||
blue=(u_col=="1"?"\033[1;34m":""); reset=(u_col=="1"?"\033[0m":"");
|
||||
tm=$2; gsub(/_/," ",tm); qid=$3; ip=$4;
|
||||
f=$5; gsub(/[^[:print:]]/, "", f);
|
||||
t=$6; gsub(/[^[:print:]]/, "", t);
|
||||
st=$7;
|
||||
r=$8; gsub(/[^[:print:]]/, "", r); gsub(/_/," ",r);
|
||||
printf "\n%s--- Detail for ID: %s ---%s\nDate: %s\nExternal IP: %s\nFrom: %s\nTo: %s\nStatus: %s\nReason: %s\n", blue,qid,reset,tm,ip,f,t,st,r
|
||||
}'
|
||||
else
|
||||
if [[ "$CSV_MODE" -eq 1 ]]; then
|
||||
echo "DATE-TIME,QUEUE_ID,EXT_IP,SENDER,RECIPIENT,STATUS,REASON"
|
||||
sort -n "$TMP_DATA" | tail -n "$LIMIT" | awk '{
|
||||
tm=$2; gsub(/_/," ",tm); id=$3; ip=$4;
|
||||
from=$5; gsub(/[^[:print:]]/, "", from);
|
||||
to=$6; gsub(/[^[:print:]]/, "", to);
|
||||
st=$7;
|
||||
re=$8; gsub(/[^[:print:]]/, "", re); gsub(/_/," ",re);
|
||||
printf "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n", tm, id, ip, from, to, st, re
|
||||
}'
|
||||
else
|
||||
[[ "$USE_COLOR" -eq 1 ]] && printf "\033[1m"
|
||||
printf "%-16s %-12s %-16s %-36s %-31s %-12s %s\n" "DATE-TIME" "QUEUE_ID" "EXT_IP" "SENDER" "RECIPIENT" "STATUS" "REASON"
|
||||
[[ "$USE_COLOR" -eq 1 ]] && printf "\033[0m"
|
||||
|
||||
sort -n "$TMP_DATA" | tail -n "$LIMIT" | awk -v u_col="$USE_COLOR" '
|
||||
function d_cut(e, m) { if(length(e)<=m)return e; return ".." substr(e, length(e)-m+2); }
|
||||
BEGIN { b="\033[1;34m"; r="\033[0m"; g="\033[1;32m"; rd="\033[1;31m"; y="\033[1;33m"; gr="\033[0;90m"; c="\033[0;36m"; if(u_col!="1")b=r=g=rd=y=gr=c=""; }
|
||||
{
|
||||
tm=$2; gsub(/_/," ",tm); id=$3; ip=$4;
|
||||
from=$5; gsub(/[^[:print:]]/, "", from);
|
||||
to=$6; gsub(/[^[:print:]]/, "", to);
|
||||
st=$7;
|
||||
re_f=$8; gsub(/[^[:print:]]/, "", re_f); gsub(/_/," ",re_f);
|
||||
re = (match(re_f, /[0-9]{3} [0-9]\.[0-9]\.[0-9]/)) ? substr(re_f, RSTART, RLENGTH) : substr(re_f, 1, 15);
|
||||
scol = (st == "sent") ? g : (st == "deferred" || st == "GREYLIST" ? y : rd);
|
||||
printf "%-16s %s%-12s%s %s%-16s%s %-36s %-31s %s%-12s%s %s[%s]%s\n", tm, (id=="NOQUEUE"?rd:b), id, r, gr, sprintf("%.15s", ip), r, d_cut(from, 35), d_cut(to, 30), scol, st, r, (st=="sent"?c:scol), re, r;
|
||||
}'
|
||||
if [ ! -s "$TMP_DATA" ]; then
|
||||
echo "No records found for ID: $ID_FILTER"; exit 0
|
||||
fi
|
||||
sort -n "$TMP_DATA" | format_output
|
||||
else
|
||||
if [[ "$USE_COLOR" -eq 1 && "$CSV_MODE" -eq 0 ]]; then
|
||||
printf "\033[1m%-16s %-12s %-16s %-32s %-28s %-10s %s\033[0m\n" "DATE-TIME" "QUEUE_ID" "EXT_IP" "SENDER" "RECIPIENT" "STATUS" "REASON"
|
||||
fi
|
||||
sort -n "$TMP_DATA" | tail -n "$LIMIT" | format_output
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user