#!/bin/bash
# name : mail_log_report.sh
# desciption : send logfile summaries via mail
# autor : speefak ( itoss@gmx.de )
# licence : (CC) BY-NC-SA
# version : 2.6
# notice : requires iptables-blacklist version 2.0 or later
#------------------------------------------------------------------------------------------------------------
############################################################################################################
########################################### define variables ###########################################
############################################################################################################
#------------------------------------------------------------------------------------------------------------
ScriptName=$(basename $0)
Version=$(cat $0 | grep "# version" | head -n1 | awk -F ":" '{print $2}' | sed 's/ //g')
MailAddress=root # mail address or systemuser ( requires active MTA )
#------------------------------------------------------------------------------------------------------------
############################################################################################################
######################################### adjustable functions #########################################
############################################################################################################
#------------------------------------------------------------------------------------------------------------------------------------------------
service.ssh () { # add any charater (e.g. space) at beginning line to deactivate service
# filter successfull ssh logins from /var/log/auth.log and send daily summary via mail to root
ServiceName=SSH # sevicename
ServiceFilter="sshd" # daemonname
SectionFilter="publickey password" # section expression filter
LogfileSource=$(journalctl --since "24 hour ago" -u ssh.service) # journald and timeframe ( 24 hour ago )
#LogfileSource="/var/log/auth.log /var/log/auth.log.1" # rsyslog path to log file(s) - ( /var/log/auth.log* for long check periods )
MailSubjectLine="Daily report from $(hostname) ( $(date '+%F %H:%M') ) => Accepted SSH Logins <=" # Email Subject line
SummaryMessage=" Accepted SSH Logins" # Email content summary line
FilterPeriod=86400 # filter past period in seconds (86400 = 1 Day)
SummaryFirstLastEntryOnTop=last # show <first|last> log entry on summary top
#TODO RecapColumn => sortierungs spalte für doppelte einträge
CountDuplicateLines=false # count duplicate entries
RecapDuplicateLines=false # sammurize duplicate entries
MailSubjectLineSubstitutions="password;PW publickey;PK" # set substitutions for mail subject line
# filter logfile for output expressions ( date string required )
# start line using date format : e.g. "Jan 18 07:00:00 " or "2018 01-01 07:00:00 " or "2019-01-18 21:34:34 " ; date divider: third space character
logfile_filter_service () {
echo "$LogfileSource" | grep Accepted | awk -F ": RSA" '{printf $1 "\n"}' # journald logging
# zgrep -ahwEi "$ServiceFilter" $LogfileSource | grep Accepted | awk -F ": RSA" '{printf $1 "\n"}' # rsyslog logging
}
}
#------------------------------------------------------------------------------------------------------------------------------------------------
service.fail2ban () {
# filter via fail2ban banned IPs from /var/log
ServiceName=Fail2Ban # sevicename
ServiceFilter=" Ban" # daemonname / logfile entry
SectionFilter="ssh wordpress nextcloud ftp http" # section expression filter
#LogfileSource=$(journalctl --since "24 hour ago" -u fail2ban.service) # journald unit and timeframe ( 24 hour ago ), f2ban does not write bans to journald
LogfileSource="/var/log/fail2ban.log*" #TODO set filter for last gz file # path to log file(s) - ( also gz compressed files)
MailSubjectLine="Daily report from $(hostname) ( $(date '+%F %H:%M') ) => Fail2Ban log <=" # Email Subject line
SummaryMessage=" Baned IPs / Attempts" # Email content summary line
FilterPeriod=86400 # filter past period in seconds (86400 = 1 Day)
SummaryFirstLastEntryOnTop=last # show <first|last> log entry on summary top
#TODO RecapColumn => sortierungs spalte für doppelte einträge
CountDuplicateLines=true # count duplicate entries
RecapDuplicateLines=true # sammurize duplicate entries
MailSubjectLineSubstitutions="" # set substitutions for mail subject line
# filter logfile for output expressions ( date string required )
# start line using date format : e.g. "Jan 18 07:00:00 " or "2018 01-01 07:00:00 " or "2019-01-18 21:34:34 " ; date divider: third space character
logfile_filter_service () {
# if [[ $(cat /etc/*release* | grep VERSION_ID | cut -d '"' -f2) -ge 12 ]]; then # debian 12 or newer / journald does not save bans to journald
# echo "$LogfileSource" | cut -d " " -f1,2,14,15,16 | sed 's/,.../ /'
# el
if [[ $(cat /etc/*release* | grep VERSION_ID | cut -d '"' -f2) -ge 9 ]]; then # debian 9 or newer
zgrep -ahwEi "$ServiceFilter" $LogfileSource | cut -d " " -f1,2,14,15,16 | sed 's/,.../ /'
elif [[ $(cat /etc/*release* | grep VERSION_ID | cut -d '"' -f2) == 8 ]]; then # debian 8
zgrep -ahwEi "$ServiceFilter" $LogfileSource | cut -d " " -f1,2,5,6,7 | sed 's/,.../ /'
fi
}
}
#------------------------------------------------------------------------------------------------------------------------------------------------
execute_post_report_command () {
if [[ $ServiceName == "Fail2Ban" ]]; then
# add multible bans to permanent blacklist using ipb command
MaxEntries=2
BlacklistedIPs=$(grep -w "\[sshd\] Ban" <<< $ReportContent | awk '{ if ($1 >= '$MaxEntries') print $0 }' | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
# check for non empty BlacklistedIPs var
if [[ -n "$BlacklistedIPs" ]]; then
printf "\r adding blacklisted IPs to blacklist ( ipb -a $BlacklistedIPs ) \n"
ipb -a $(echo "$BlacklistedIPs" | tr "\n" " ")
else
printf "\r no new blacklisted IPs detected \n"
fi
fi
}
#------------------------------------------------------------------------------------------------------------------------------------------------
############################################################################################################
######################################## set vars from options ##########################################
############################################################################################################
#------------------------------------------------------------------------------------------------------------
OptionVarList="
MailReport;-mr
PrintReport;-pr
ExecutePostReportCommands;-eprc
Monochrome;-m
ScriptInformation;-si
"
# set entered vars from optionvarlist
for InputOption in $(echo " $@" | tr " " "\n" ) ; do
for VarNameVarValue in $OptionVarList ; do
VarName=$(echo "$VarNameVarValue" | cut -d ";" -f1)
VarValue=$(echo "$VarNameVarValue" | cut -d ";" -f2)
if [[ $InputOption == $VarValue ]]; then
eval $(echo "$VarName"="$InputOption")
fi
done
done
#------------------------------------------------------------------------------------------------------------------------------------------------
############################################################################################################
########################################### fixed functions ############################################
############################################################################################################
#------------------------------------------------------------------------------------------------------------
load_color_codes () {
Black='\033[0;30m' && DGray='\033[1;30m'
LRed='\033[0;31m' && Red='\033[1;31m'
LGreen='\033[0;32m' && Green='\033[1;32m'
LYellow='\033[0;33m' && Yellow='\033[1;33m'
LBlue='\033[0;34m' && Blue='\033[1;34m'
LPurple='\033[0;35m' && Purple='\033[1;35m'
LCyan='\033[0;36m' && Cyan='\033[1;36m'
LLGrey='\033[0;37m' && White='\033[1;37m'
Reset='\033[0m'
# Use them to print in your required colours:
# printf "%s\n" "Text in ${Red}red${Reset}, white and ${Blue}blue${Reset}."
BG='\033[47m'
FG='\033[0;30m'
# reloard colored global vars
for i in $(cat $0 | sed '/load_color_codes/q' | grep '${Reset}'); do
eval "$i"
done
}
#------------------------------------------------------------------------------------------------------------------------------------------------
usage() {
printf " iptables-blacklist version: $Version | script location $basename $0\n"
clear
printf "\n"
printf " Usage: $(basename $0) <options> "
printf "\n"
printf " -h => help dialog \n"
printf " -stm => send test mail\n"
printf " -mr => mail report \n"
printf " -pr => print report \n"
printf " -eprc => execute post report commands \n"
printf " -m => monochrome output \n"
printf " -si => show script information \n"
printf "\n${Red} $1 ${Reset}\n"
printf "\n"
exit
}
#------------------------------------------------------------------------------------------------------------
script_information () {
printf "\n"
printf " Scriptname: $ScriptName\n"
printf " Version: $Version \n"
printf " Location: $(pwd)/$ScriptName\n"
printf " Filesize: $(ls -lh $0 | cut -d " " -f5)\n"
printf "\n"
exit 0
}
#------------------------------------------------------------------------------------------------------------------------------------------------
logfile_entry_filter () {
# set unixtime beginn past period
UnixtimePastValue=$(($(date +%s)-$FilterPeriod))
LogfileEntries=$(logfile_filter_service)
LogfileEntriesCount=$( wc -l <<< $LogfileEntries)
Counter=1 # ab Debian 10 => counter=1
# compare timestamp and print matched logfile line
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for LogEntry in $LogfileEntries ;do
# skip counter for cron execution
if [[ ! $CronExecution == true ]]; then
printf "\r processing entry $Counter/$LogfileEntriesCount" >&2
Counter=$((Counter+1))
fi
# skip Dec entries for turn of the year
if [[ $TurOfTheYear == true ]] || [[ -n $( grep "^Jan " <<< $LogEntry ) ]]; then
TurOfTheYear=true
if [[ $( grep "^Dec " <<< $LogEntry ) ]]; then
continue
fi
fi
if [[ $UnixtimePastValue -lt $(date -d "$(echo $LogEntry | cut -d " " -f1-3)" +"%s") ]]; then # divider : third space to get time string,faster
#if [[ $UnixtimePastValue -lt $(date -d "$(echo $LogEntry | sed 's/\([0-9]\{2\}:\)\{2\}[0-9]\{2\}.*$//')" +"%s") ]]; then # divider : "01:00:00" to get time string
LogfileFilteredDateContent=$(echo -e "$LogfileFilteredDateContent \n $LogEntry")
fi
done
IFS=$SAVEIFS
# print logfile content for each filter string
for SectionFilterLine in $SectionFilter ;do
# filter log entries and write to proccessing var
FinalLogfileContent=$(printf "$(echo "$LogfileFilteredDateContent" | grep $SectionFilterLine | sort -rnk1)")
FinalLogfileContentRecaped=$(printf "$(echo "$LogfileFilteredDateContent" | grep $SectionFilterLine | sort -k4 | uniq -f3 -c | sort -rnk1)") # TODO uniq -f3 -c => uniq -f$VAR -c
# set counter
FinalLogfileContentCounter=$(echo "$FinalLogfileContent" | grep $SectionFilterLine | sed '/^ $/d' | grep -c .)
FinalLogfileContentRecapedCounter=$(echo "$FinalLogfileContentRecaped" | grep $SectionFilterLine | sed '/^ $/d' | grep -c .)
# set output sequence
SummaryFirstLastEntryOnTop=$(echo $SummaryFirstLastEntryOnTop | sed 's/last/tee/' | sed 's/first/tac/')
# print counter and logentries for each section expression filter
if [[ $CountDuplicateLines == true ]] && [[ $RecapDuplicateLines == true ]]; then
printf "$SummaryMessage [$SectionFilterLine] => $FinalLogfileContentRecapedCounter / $FinalLogfileContentCounter \n\n"
printf "$FinalLogfileContentRecaped\n" | $SummaryFirstLastEntryOnTop | sed '/^$/d'
elif [[ $CountDuplicateLines == true ]] && [[ $RecapDuplicateLines == false ]]; then
printf "$SummaryMessage [$SectionFilterLine] => $FinalLogfileContentRecapedCounter / $FinalLogfileContentCounter \n\n"
printf "$FinalLogfileContent\n" | $SummaryFirstLastEntryOnTop | sed '/^$/d'
elif [[ $CountDuplicateLines == false ]] && [[ $RecapDuplicateLines == true ]]; then
printf "$SummaryMessage [$SectionFilterLine] => $FinalLogfileContentRecapedCounter \n\n"
printf "$FinalLogfileContentRecaped\n" | $SummaryFirstLastEntryOnTop | sed '/^$/d'
elif [[ $CountDuplicateLines == false ]] && [[ $RecapDuplicateLines == false ]]; then
printf "$SummaryMessage [$SectionFilterLine] => $FinalLogfileContentRecapedCounter \n\n"
printf "$FinalLogfileContent\n" | $SummaryFirstLastEntryOnTop | sed '/^$/d'
fi
if [[ -n $FinalLogfileContent ]]; then
printf "\n"
fi
done
printf "End of Report \n\n "
}
#------------------------------------------------------------------------------------------------------------------------------------------------
############################################################################################################
############################################# start script #############################################
############################################################################################################
#------------------------------------------------------------------------------------------------------------------------------------------------
# check for cronjob execution and cronjob options
CronExecution=
if [ -z $(grep "/" <<< "$(tty)") ]; then
CronExecution=true
Monochrome=true
TERM=xterm-256color
# TERM=linux
export TERM
fi
#------------------------------------------------------------------------------------------------------------------------------------------------
# check for help dialog
if [[ -z $1 ]] || [[ $1 == -h ]]; then usage ;fi
#------------------------------------------------------------------------------------------------------------------------------------------------
# check for monochrome output
if [[ -z $Monochrome ]]; then
load_color_codes
fi
#------------------------------------------------------------------------------------------------------------------------------------------------
# check for script information
if [[ -n $ScriptInformation ]]; then script_information ; fi
#------------------------------------------------------------------------------------------------------------------------------------------------
# check for root permission
if [ "$(whoami)" = "root" ]; then echo "";else echo "Are You Root ?";exit 1;fi
#------------------------------------------------------------------------------------------------------------------------------------------------
# check input options
if [[ -z $@ ]] || [[ $@ == -[hH] ]]; then usage "help dialog" ; fi
if [[ -n $(grep stm <<< $@) ]]; then
printf "execute: printf \"mail_log_report testmail on $(date) from $(hostname)\" | mail -s \"testmail\" $MailAddress \n"
printf "mail_log_report testmail on $(date) from $(hostname)" | mail -s "testmail" $MailAddress
exit
fi
if [[ $(grep -c . <<< $MailReport$PrintReport$ExecutePostReportCommands) == 0 ]]; then
usage " missing options "
fi
#------------------------------------------------------------------------------------------------------------------------------------------------
# processing services ( service.<name> functions )
for service in $(cat $0 | grep "() {" | grep "^service\." | cut -d " " -f1 ); do
# execute service section to set service vars
$service
printf "\n analyzing log files: $LogfileSource ($ServiceName) ... \n"
# create ReportContent from filtered log entries and calculate counter
ReportContent=$(logfile_entry_filter)
# set summary counter
CounterSummary=$(for SectionFilterLine in $SectionFilter ;do
Counter=$(echo "$ReportContent" | grep -w "\[$SectionFilterLine\]" | awk -F "=>" '{printf $2}' | sed 's/ //g')
printf "$SectionFilterLine $Counter, "
done
printf "\n\n" )
if [[ -n $MailReport ]]; then
# set subject line
MailSubjectLine="$MailSubjectLine $CounterSummary"
# set mail summary header
MailContentHeaderLine="$MailSubjectLine $(date -d "$FilterPeriod seconds ago" "+%F|%H:%M:%H") - $(date "+%F|%H:%M:%H")"
# parse mail subject for shortnames
for i in $MailSubjectLineSubstitutions ;do
BaseExpression=$(echo $i | cut -d ";" -f1)
OverwriteExpression=$(echo $i | cut -d ";" -f2)
MailSubjectLine=$(echo "$MailSubjectLine" | sed 's/'$BaseExpression'/'$OverwriteExpression'/' )
done
# send report via mail
printf "$MailContentHeaderLine \n\n$ReportContent" | mail -a "Content-Type: text/plain" -s "$MailSubjectLine" $MailAddress
fi
if [[ -n $PrintReport ]]; then
# print report only
printf "\n\n$ReportContent"
fi
if [[ -n $ExecutePostReportCommands ]]; then
# execute post report functions
execute_post_report_command
fi
done
#------------------------------------------------------------------------------------------------------------------------------------------------
exit 0
------------------------------------------------------------------------------------------------------------
############################################################################################################
############################################## changelog ###############################################
############################################################################################################
#------------------------------------------------------------------------------------------------------------
#changelog 2.5 => 2.6 : add journald logfile analyzing for ssh deamon