#!/bin/sh - set -a PATH=/usr/ucb:/bin:/usr/bin:/usr/sbin:/sbin:/usr/lib #### reports sent to email addresses listed, one per line, in this file USERLIST=/etc/postfix/reportrecipient #### MAILFROM abuse by default MAILFROM=abuse #### filter out garbage in IGNORE='(broken.example.com|:.hold:|Policy Rejection|Greylisted| warning| postfix.smtp\[|status=bounced|postfix/cleanup.*relay=none|DISCARD|Address verification in progress|connect from|DISCARD)' #### maximum number of lines per section #### don't make this too large or mailed reports could bounce (postconf message_size_limit) SECTIONMAX=5000 #### maximum archive levels (i.e, maillog.25) #### tune this to 2 x max# mail log files created daily MAXARCHIVES=9 #### where to look for logs LOGDIRS="/var/log /var/adm /var/log/OLD" LOGFILES="maillog messages mail.log mail mail.messages" #### customizable end of report SIGNATURE="" #### use: MAILCMD="sendmail -oem -oi -t -f $MAILFROM" with sendmail >V8.8 #### "-t" should work with Postfix versions Snapshot-20030611 or newer MAILCMD="sendmail -oem -oi -f $MAILFROM" #### redefine $host only if localhost is not the mailhost host=`uname -n|awk -F. '{print $1}'` DEBUG=NO #[Y/y/else N] DEBUG_MAILTO=${MAILFROM} ###################################################################### # spamrep_today_byuser spamrep_yesterday_byuser (Postfix version) # # # # License terms at: # HTML version available with PostConf ###################################################################### # Usage A: spamrep_today_byuser # (prints report to screen) # Usage B: spamrep_today_byuser mail # (mails report to users listed in $USERLIST) # NOTE1: May fail under Redhat 9 due to a bug in its 'sort' command. # To fix install GNU coreutils-5.0 or later. # NOTE2: If the server's header_checks and body_checks are blocking # reports try this modifyication to /etc/postfix/master.cf: # from: # pickup fifo n - n 60 1 pickup # to: # pickup fifo n - n 60 1 pickup -o receive_override_options=no_header_body_checks # ###################################################################### # $Id: spamrep_today_byuser,v 1.127 2007/05/15 18:08:44 marquis Exp marquis $ ###################################################################### LANG=C _POSIX2_VERSION=199209 TMP="/tmp/.spamrepu.$$" REPDAYS=3 umask 077 LESS=-ce #### test minlines when changing the report MINLINES=7 cleantmp () { rm -f $TMP $TMP.spam $TMP.rep $TMP.sum $TMP.rbl $TMP.pre } cleanexit () { cleantmp rm -f $TMP.today exit } trap "cleanexit" 0 1 2 3 15 printSecHead () { SS="`wc -l $TMP.spam 2>/dev/null | awk '{print $1}'`" TOTALFILTERED=`expr ${SS} + ${TOTALFILTERED}` echo "" >> $TMP if [ $SS -eq 1 ]; then barheader "$SS $sectionTitleS" >> $TMP echo " $SS $sectionTitleS" >> $TMP.sum elif [ $SS -gt $SECTIONMAX ]; then barheader "$SS ${sectionTitleM}, ${SECTIONMAX} shown" >> $TMP echo " $SS ${sectionTitleM}, ${SECTIONMAX} shown" >> $TMP.sum else barheader "$SS $sectionTitleM" >> $TMP echo " $SS $sectionTitleM" >> $TMP.sum fi } printSecFoot () { echo "" >> $TMP head -${SECTIONMAX} $TMP.spam >> $TMP rm -f $TMP.spam } printRBLdetail () { RBLS=`sed -e 's/^.*blocked using //' -e 's/, reason.*$//' -e 's/;.*$//' $TMP.spam | sort -u` if [ "`echo $RBLS | wc -w`" -gt 1 ]; then rblCalc () { for rbl in $RBLS ; do rblhits="`grep $rbl $TMP.spam 2>/dev/null | wc -l 2>/dev/null | sed 's/ *//g'`" #if [ $rblhits -gt 1 ]; then echo " $rblhits - $rbl [`expr 100 \* $rblhits / $SS`%]" #fi done } rblCalc | sort -grn | \ $AWK '{ printf "%15i %s %s %s %s %s %s %s \n", $1, $2, $3, $4, $5, $6, $7, $8 }' > $TMP.rbl fi } barheader () { echo "------[ $1 ]----------------------------------------------------------------" | \ awk -F"\/\/" '{ printf "%-.75s", $1 }' 2>/dev/null } parselog () { #### customize sed flags only in case of non-standard syslog format grep "^$LOGDATE" 2>/dev/null | grep postfix | egrep -v "$IGNORE" | sort -Mr | \ sed -e 's/ NOQUEUE://' \ -e 's/ proto=ESMTP / /' \ -e 's/ proto=SMTP / /' \ -e 's/ postfix.* reject:/ reject:/' \ -e 's/ postfix.* discard:/ discard:/' \ -e 's/ postfix.* error:/ error:/' \ -e 's/ postfix.* fatal:/ fatal:/' \ -e 's/ postfix.* panic:/ panic:/' \ -e 's/ postfix.* reject_warning:/ reject_warning:/' \ -e 's/ postfix.* warning:/ warning:/' \ -e 's/ postfix.*smtp.*mail.info]//' \ -e 's,'" $host "'postfix\/,'" $TIMEZONE "',' \ -e 's,'" $host "'postfix.\/,'" $TIMEZONE "',' \ -e 's/'" $host "'/ '$TIMEZONE' /' \ >> $TMP.today } catlog () { if [ ! -r ${1} ]; then continue ## probably not running as root elif [ "`echo ${1} | grep '.gz'$`" != "" ]; then zcat ${1} | parselog else cat ${1} | parselog fi #logger " DEBUG: `basename $0` parsing ${1}" } findlogs () { if [ -s ${1} ]; then # current log catlog ${1} elif [ -s ${1}.gz ]; then # compressed archive catlog ${1}.gz fi i=0 while [ $i -lt $MAXARCHIVES ]; do j=${i} i=`expr $i + 1` presize=1 if [ -s ${1}.${j} ] && [ "`find ${1}.${j} -mtime -${REPDAYS}`" != "" ]; then # numbered archive presize=`wc -c $TMP.today 2>/dev/null | awk '{ print $1 }'` catlog ${1}.${j} elif [ -s ${1}.${j}.gz ] && [ "`find ${1}.${j}.gz -mtime -${REPDAYS}`" != "" ]; then # numbered and compressed archive presize=`wc -c $TMP.today 2>/dev/null | awk '{ print $1 }'` catlog ${1}.${j}.gz fi postsize=`wc -c $TMP.today 2>/dev/null | awk '{ print $1 }'` if [ $REPDAYS = 1 ] && [ $presize = $postsize ]; then ## don't search older logs break fi done } report_on_account () { account=$1 #################### list by filter type ############################# grep -i "to=.${account}" $TMP.today 2>/dev/null | \ grep -v " reject_warning: " | grep 'from=<' | \ sed -e 's/^.* from= to=<.*$/>/' | sort -t\< +1f | \ sort -u 2>/dev/null > $TMP.spam UNIQUE="`wc -l $TMP.spam 2>/dev/null | awk '{print $1}'`" if [ -s $TMP.spam ]; then sectionTitleS='unique sender' sectionTitleM="$sectionTitleS"s printSecHead printSecFoot fi #### correct double counting TOTALFILTERED=0 grep -i "to=.${account}" $TMP.today 2>/dev/null | \ grep -i "blocked.using" | grep -v " reject_warning: " | \ sed -e 's/ Service unavailable//' \ -e 's/ Client host .* blocked using / blocked using /' > $TMP.spam if [ -s $TMP.spam ]; then sectionTitleS='rejected by subscription' sectionTitleM="$sectionTitleS" printSecHead printRBLdetail printSecFoot fi grep -i "to=.${account}" $TMP.today 2>/dev/null | \ egrep -i '(reject: header|discard:|essage.size.exceeds|access.denied|Recipient.address.rejected|Message.content.rejected)' | \ egrep -iv '(user.unknown|discard: header X-Amavis.*: INFECTED|discard: header X-Spam| reject_warning: |helo.command.rejected|domain.not.found|need.fully-qualified)' | \ sed 's/: Access denied//' > $TMP.spam if [ -s $TMP.spam ]; then sectionTitleS='rejected by localhost' sectionTitleM="$sectionTitleS" printSecHead printSecFoot fi grep -i "to=.${account}" $TMP.today 2>/dev/null | \ egrep -i '(helo.command.rejected|domain.not.found|need.fully-qualified)' | \ egrep -iv '( reject_warning: |reject: header|discard:|message.size.exceeds|access.denied|Recipient.address.rejected|Message.content.rejected)' > $TMP.spam if [ -s $TMP.spam ]; then sectionTitleS='protocol error' sectionTitleM="$sectionTitleS"s printSecHead printSecFoot fi grep -i "to=.${account}" $TMP.today 2>/dev/null | \ grep " reject_warning: " | \ sed -e 's/blocked using/listed by/' -e 's/reject_warning: /warn_only: /' \ -e 's/ ... Service unavailable; //' > $TMP.spam if [ -s $TMP.spam ]; then sectionTitleS='warn-only notice' sectionTitleM="$sectionTitleS"s printSecHead printSecFoot fi #### assumes "/^X-Spam-Level: \*\*\*\*\*\*/ DISCARD" or equivalent header_checks grep -i "to=.${account}" $TMP.today 2>/dev/null | \ grep -i "discard: header X-Spam" | \ grep -i " to=.${account}" | \ sed -e 's/ proto=ESMTP helo=.localhost.//' -e 's/from localhost.*127.0.0.1.; //' \ -e 's/ from local;//' > $TMP.spam if [ -s $TMP.spam ]; then sectionTitleS='discarded by spamassassin' sectionTitleM="$sectionTitleS" printSecHead printSecFoot fi ####################################################################################### ## For optimal AV logging A) configure amavis to "$final_virus_destiny = D_PASS;", ## and "$policy_bank{'MYNETS'} = { bypass_banned_checks_maps => [1], };", then ## configure Postfix to both B) send XFORWARD "smtp_send_xforward_command = yes", ## C) and receive it "smtpd_authorized_xforward_hosts = localhost", finally ## D) discarding the results via header_checks "/^X-Amavis-Alert: INFECTED/ DISCARD". ####################################################################################### #### assumes clam-av, amavis discarded by header_check #### grep -i "to=.${account}" $TMP.today 2>/dev/null | \ egrep '(discarded, .* - VIRUS|discard: header X-Amavis.*: INFECTED)' | \ sed -e 's/'$host' .*: to/'$TIMEZONE' virus: to/' \ -e 's/ from localhos.*\[127.0.0.1\]//' \ -e 's/, relay=127.0.0.1\[127.0.0.1\].*status=sent//' > $TMP.spam if [ -s $TMP.spam ]; then sectionTitleS='discarded virus' sectionTitleM="$sectionTitleS"es printSecHead printSecFoot fi #################### finish header and view or mail ################## echo "" >> $TMP if [ "`wc -l $TMP 2>/dev/null | awk '{print $1 }'`" -lt $MINLINES ]; then cleantmp continue else #### build header if [ $MAIL = y ]; then #### "To: ${account}" is required but may be ignored if recipient is #### specified on the command line. echo "From: ${MAILFROM} To: ${account} Reply-To: ${MAILFROM} Subject: $DAY mailstats for $account " >> $TMP.pre fi echo "" >> $TMP.pre barheader "$DAY mailstats for $account" >> $TMP.pre echo "" >> $TMP.pre echo "" >> $TMP.pre SENDERS="sender" if [ "$UNIQUE" -ne 1 ]; then SENDERS="${SENDERS}s" fi echo "$TOTALFILTERED total filtered from $UNIQUE $SENDERS" | \ $AWK '{ printf "%9i %s %s %s %s %s %s %s %s \n", $1, $2, $3, $4, $5, $6, $7, $8, $9 }' >> $TMP.pre sort -nr < $TMP.sum | \ $AWK '{ printf "%12i %s %s %s %s %s %s %s %s \n", $1, $2, $3, $4, $5, $6, $7, $8, $9 }' >> $TMP.pre #### add rbl detail if [ -s $TMP.rbl ] && [ "`grep 'rejected by subscription' $TMP.pre`" != "" ]; then REPLINE=`grep -n 'rejected by subscription' $TMP.pre | awk -F: '{print $1}'` head -${REPLINE} $TMP.pre | grep -v "unique sender" > $TMP.rep cat $TMP.rbl >> $TMP.rep tail +`expr $REPLINE + 1` $TMP.pre | grep -v "unique sender" >> $TMP.rep else grep -v "unique sender" $TMP.pre > $TMP.rep fi #### add note echo " " >> $TMP.rep echo " Reply to this email or forward to $MAILFROM if" >> $TMP.rep echo " you find any false-positives or have questions about" >> $TMP.rep echo " spam filtering." >> $TMP.rep echo " " >> $TMP.rep echo " Important reminder: please do not reply to unsolicited " >> $TMP.rep echo " email." >> $TMP.rep #### add report body cat $TMP >> $TMP.rep #### add footer if [ "$SIGNATURE" ]; then barheader "end of report - ${SIGNATURE}" >> $TMP.rep else barheader "end of report - www.postconf.com/docs/spamrep" >> $TMP.rep fi echo "" >> $TMP.rep if [ $MAIL = y ]; then if [ "$DEBUG" = y ] || [ "$DEBUG" = Y ]; then $MAILCMD $DEBUG_MAILTO < $TMP.rep else $MAILCMD $account < $TMP.rep fi elif [ -s $TMP.rep ]; then clear more $TMP.rep echo "Press [RETURN] to continue" read x echo "processing, please wait..." fi cleantmp fi } #################### main #################### if [ "`uname -s`" = SunOS ]; then AWK=nawk else AWK=awk fi TIMESTAMP="`date`" TIMEZONE="`echo $TIMESTAMP | awk '{print $5}'`" if [ "`basename $0 | grep -i yesterday`" != "" ]; then #### basename = *yesterday* #### REPDAYS=2 if [ "`echo $TIMESTAMP | awk '{ print $3 }'`" = 1 ]; then #### unreliable cross-platform hack for 1st of the month TZ="`echo $TIMEZONE`+31" TIMESTAMP="`date`" LOGDATE="`echo $TIMESTAMP | $AWK '{printf "%3s%3s", $2, $3}'`" DAY="`echo $TIMESTAMP | awk '{print $2, $3, $NF}'`" else LOGDATE="`echo $TIMESTAMP | $AWK '{printf "%3s%3s", $2, $3-1}'`" DAY="`echo $TIMESTAMP | awk '{print $2, $3-1, $NF}'`" fi else #### basename = *today* #### REPDAYS=1 LOGDATE="`echo $TIMESTAMP | $AWK '{printf "%3s%3s", $2, $3}'`" DAY="`echo $TIMESTAMP | awk '{print $1, $2, $3, $NF}'`" fi cleantmp cp -f /dev/null $TMP.today for dir in $LOGDIRS ; do if [ -d $dir ]; then for log in $LOGFILES ; do #### parse logs into $TMP.today findlogs ${dir}/${log} done fi done ############### parse the command line ##################### case $1 in h|-h|--h|help|-help|--help) echo " USAGE: `basename $0` [ mail ] [ user1 ... ]" exit 0 ;; mail) if [ "$2" = "" ]; then #### list -> mail #### MAIL=y LIST=y else #### cmnd line userlist -> mail #### MAIL=y LIST=n shift fi ;; "") #### list -> display #### MAIL=n LIST=y ;; *) #### cmnd line userlist -> display #### MAIL=n LIST=n ;; esac ############### run the reports ############################ if [ $LIST = n ]; then for account in $* ; do report_on_account $account done else if [ ! -s $USERLIST ]; then report_on_account `whoami` else for account in `grep -v '^#' $USERLIST | awk '{ print $1 }'` ; do report_on_account $account done fi fi cleanexit