Overview
Configuration Crontab Environment
Written: Jan-2025

maillog-summary.bsh script

So I needed/wanted to investigate what was going on in my /var/log/maillog file on plesk, so this is my bash script, running on ubuntu via cron

bash
#!/bin/bash

logfile="/var/log/maillog"

# Extract date range
earliest=$(awk 'NR==1 {print $1, $2, $3}' "$logfile")
latest=$(awk 'END {print $1, $2, $3}' "$logfile")

echo "# maillog dates from $earliest - $latest"

echo
echo "# Time Period Summary of Failed Attempts #"
echo


# Process logs for Plesk's smtpd and extract unique IPs and login IDs with counts
awk '
function parse_timestamp(month, day, time) {
    split(time, t, ":");
    months["Jan"]=1; months["Feb"]=2; months["Mar"]=3; months["Apr"]=4;
    months["May"]=5; months["Jun"]=6; months["Jul"]=7; months["Aug"]=8;
    months["Sep"]=9; months["Oct"]=10; months["Nov"]=11; months["Dec"]=12;
    return mktime("2025 " months[month] " " day " " t[1] " " t[2] " 0");
}



function format_interval(timestamp, minutes) {

Default to 10 minutes if no minutes argument is provided

if (minutes == "") { minutes = 10; } split(strftime("%Y-%m-%d %H:%M", timestamp), time_parts, ":"); min_block = int(time_parts[2] / minutes) * minutes; return time_parts[1] ":" (min_block < 10 ? "0" min_block : min_block); } function sort_and_print(arr, label, min) { n = asorti(arr, sorted_arr, "@val_num_desc"); if (n > 0) { seen=0; for (i = 1; i <= n; i++) { if (arr[sorted_arr[i]] >= min) { if ( seen==0 ) print " " label ":"; seen=1; print " " arr[sorted_arr[i]] " attempts : " sorted_arr[i]; } } print ""; return 1; } return 0; } function reset_block() { ips=0; logins=0; # ip_attempts[ip]=count # login_attempts[login]=count for (i in ip_attempts) { if (ip_attempts[i] >= min_count) ips++; } for (i in login_attempts) { if (login_attempts[i] >= min_count) logins++; } if(current_interval != "" && (ips >= 1 || logins >= 1)) { print ""; print ""; print "Summary for " current_interval; print ""; ip_reported = sort_and_print(ip_attempts, "Unique suspect IP Addresses ", min_count_ip); login_reported = sort_and_print(login_attempts, "Unique failed Login IDs ", min_count_login); ip_reported = login_reported = 0; seen_matches = 0; if (length(seen_entries) > 0) { for (i in seen_entries) { # Loop through ip_attempts to check for the existence of seen_entries[i] found_in_ip = 0; for (ip in ip_attempts) { if (ip_attempts[ip] >= min_count_ip && index(seen_entries[i], ip) > 0) { found_in_ip = 1; break; # Exit the loop if a match is found } } # Loop through login_attempts to check for the existence of seen_entries[i] found_in_login = 0; for (login in login_attempts) { if (login_attempts[login]>=min_count_login && index(seen_entries[i], login) > 0) { found_in_login = 1; break; # Exit the loop if a match is found } } # If found in either ip_attempts or login_attempts if (found_in_ip || found_in_login) { if (seen_matches == 0) { print " Referenced Entries:"; } print " > " seen_entries[i]; seen_matches = 1; } } print ""; } } delete ip_attempts; delete login_attempts; delete seen_entries; for (i in seen_entries) { delete seen_entries[i]; } } BEGIN { current_interval = ""; min_count_ip = 2; min_count_login = 1; gap_minutes = 30; delete ip_attempts; delete login_attempts; delete seen_entries; } { timestamp = parse_timestamp($1, $2, $3); timestamp_str = $1 " " $2 " " $3; interval = format_interval(timestamp, gap_minutes); if ( current_interval == "" ) current_interval = interval; if (interval != current_interval) { reset_block(); current_interval = interval; start_datestr = interval ":00"; } # Extract IPs from "warning: unknown" if ($0 ~ /warning: unknown[([0-9]+.[0-9]+.[0-9]+.[0-9]+)]/) { match($0, /warning: unknown[([0-9]+.[0-9]+.[0-9]+.[0-9]+)]/, ip_match); if (ip_match[1] != "") ip_attempts[ip_match[1]]++; if (ip_match[1] != "") seen_entries[NR] = $0; } # Extract login IDs from "No such user" if ($0 ~ /failed mail authentication attempt for user/) { match($0, / authentication attempt for user x27([^x27]+)x27/, login_match); if (login_match[1] != "") login_attempts[login_match[1]]++; if (login_match[1] != "") seen_entries[NR] = $0; } } END { reset_block(); print ""; print "End of log summary."; }' "$logfile"

The output for it will be a summary of the number of failed logins or IPs that have occured during a given period within the log.

stdout
# maillog dates from Jan 25 10:41:52 - Jan 25 16:49:52

# Time Period Summary of Failed Attempts #



Summary for 2025-01-25 10:30

    Unique suspect IP Addresses :
     2 attempts : 87.120.121.102
     2 attempts : 45.151.99.245
     2 attempts : 194.0.234.135
     2 attempts : 193.32.162.89

    Unique failed Login IDs :
     2 attempts : test@abc.com
     1 attempts : test3@zzz.com
     1 attempts : test3@fff.com
     1 attempts : soporte@xyz.com

   Referenced Entries:0 1
     > Jan 25 10:42:19 xxx plesk_saslauthd[156912]: failed mail authentication attempt for user 'test@xyz.com' (password len=10)
     > Jan 25 10:42:19 xxx postfix/smtpd[143596]: warning: unknown[87.120.121.102]: SASL LOGIN authentication failed: authentication failure
     > Jan 25 10:43:22 xxx plesk_saslauthd[170949]: failed mail authentication attempt for user 'iva' (password len=4)
     > Jan 25 10:43:37 xxx plesk_saslauthd[170949]: failed mail authentication attempt for user 'nataliag' (password len=9)
     > Jan 25 10:44:23 xxx plesk_saslauthd[184481]: failed mail authentication attempt for user 'news@xyz.com' (password len=13)
     > Jan 25 10:44:23 xxx postfix/smtpd[143596]: warning: unknown[45.151.99.245]: SASL LOGIN authentication failed: authentication failure
     > Jan 25 10:44:32 xxx plesk_saslauthd[184481]: failed mail authentication attempt for user 'mail@xyz.com' (password len=13)
     > Jan 25 10:44:32 xxx postfix/smtpd[143596]: warning: unknown[194.0.234.135]: SASL LOGIN authentication failed: authentication failure
     > Jan 25 10:50:05 xxx plesk_saslauthd[262997]: failed mail authentication attempt for user 'rachel' (password len=7)



Summary for 2025-01-25 11:00

    Unique suspect IP Addresses :
     2 attempts : 193.32.162.23

    Unique failed Login IDs :
     1 attempts : user


And so on.

Configuration

Adjust the size of the time periods being chunked out of the logfile, then change gap_minutes in the script.To change the number of repeats in the log for an IP or a login-name during the period, then change min_count_ip and/or min_count_login

Crontab

To put this into crontab to email yourself, use the command line:

echo -e "Subject:.....nFrom:noreply@......comnTo:someone@.....comnn$(/path-name/maillog-summary.bsh)" | sendmail -t -f noreply@....com

(Requires the script to be saved as maillog-summary.bsh, and the noreply@... com email address to exist).

Environment

NOTE - this script will not protect you from login attempts - you should consider installing fail2ban on plesk to provide that.

square