#!/usr/bin/perl # # Original Version # ---------------- # http://www.Linux-Sec.net/Mail/Logger/sendmail.spam.pl # # # Extract Mail Statistics from the logs --> display mailstats in webpage format # ------------------------------------- # - time/date/msgID info # - From/To info # - Relay info # - why they were rejected # # # Usage: # ------- # sendmail.log.from.pl -skip [ -day 9 ] # for a specific date # # # crontab -e # # # # Run hourly mail stats via cron # 59 * * * * sendmail.log.from.pl -skip # # # To View the spams and status # ---------------------------- # http://www.your_domain.com/MailStat # # # # 05-Jun-02 amo Date-of-Birth Generate Web-based MailStats # 11-Jun-02 amo Added output directly into $html # 01-Jul-02 amo Fix Debug option for url, and use "grep $dd" instead for "Jul 1" # 02-Aug-02 amo Fix $Day option # 06-Sep-02 amo Fix $ok to create in mk_dir # # # # local ( $NM ) = "sendmail.spam.pl"; # local ( $WebMaster) = "WebMaster"; # owner of web pages # # # # User Defined mailstats directory # --------------------------------- # local ( $HTML ) = "/home/httpd/html"; $HTML = "/home/httpd/htdocs.fef"; # local ( $MAILSTAT ) = "MailStat"; # local ( $MAILLOGS ) = "/var/log/mail.log"; # # make sure we can read the mail log file # chgrp www $MAILLOGS # # local ( $HOST ) = `hostname -f`; chomp ( $HOST ); # # ----- end user defined variables ---------------------------------- # # local ( $PID ) = $$; # pid # local ( $DEBUG ) = 0; local ( $Skip_Stdin ) = 0; local ( $Content_type ) = 0; # local ( @p_v ) = (); # web variables # local ( $SearchMon ) = 0; local ( $SearchDay ) = 0; local ( $SearchMTA ) = -1; # search for this string instead # # local ( $Date ) = ` date `; # Wed Apr 19 22:40:27 PDT 2002 chomp ( $Date ); # local ( $Dow, $Mon, $Day, $Time, $Zone, $Year ) = split ( /\s+/, $Date ); chomp ( $Year ); # # Day can be manually defined via command line options # local ( $DD ) = sprintf ( "%02d", $Day ); # local ( $REV ) = ""; # grep -v # # # # Convert String into number # local ( @MonID ) = (); $MonID{ "Jan" } = 1; $MonID{ "Feb" } = 2; $MonID{ "Mar" } = 3; $MonID{ "Apr" } = 4; $MonID{ "May" } = 5; $MonID{ "Jun" } = 6; $MonID{ "Jul" } = 7; $MonID{ "Aug" } = 8; $MonID{ "Sep" } = 9; $MonID{ "Oct" } = 10; $MonID{ "Nov" } = 11; $MonID{ "Dec" } = 12; # # # local ( $MM ) = sprintf ( "%02d", $MonID{ $Mon } ); # local ( $Yr_Mon ) = "${Year}_$MM"; # # local ( @RES ) = (); # raw mail log data local ( %DATA ) = (); # processed data # # # # # Process the commandline arguments # ---------------------------------- # &cmd_args ; # # # # Process the Web page forms # -------------------------- # if ( $Skip_Stdin == 0) { # $Content_type = printf "Content-type: text/html\n\n" if ( $Content_type == 0 ); # # Process the options passed by the web page forms # @p_v = &proc_form ; # # Extract and define DIR used by require below # &proc_data ( @p_v ) ; # } # web page data # # # Manually Deine a Specific Date to Process if needed # # $Day = 1; # local ( $day ) = $Day; # "Jul 1" $day = " $Day" if ( $Day <= 9 ); # right justified date # $DD = sprintf ( "%02d", $Day ); # # # Get todays mail statistics # --------------------------- # printf "$NM: ERROR: Cannot read $MAILLOGS..
\n" if ( ! -r "$MAILLOGS" ); # # if ( $SearchMTA != -1 ) { # # Find a particular entry # Exclude a particular entry like exclude all root's emails # @RES = ` grep '$Mon $day' $MAILLOGS | grep $REV $SearchMTA `; # # } else { # @RES = ` grep "$Mon $day" $MAILLOGS `; # } # # # Read the log info # &read_maillog ; # # # Print the Results in HTML page # -------------------------------- # &pr_html ; # # # all done # exit 0 ; # # # =========================================================================== # # # Read the entries from the maillog # --------------------------------- # sub read_maillog { # my ( $id ) = 0; my ( $jd ) = 0; # my ( @f ) = ""; my ( @first ) = ""; # my ( $line ) = ""; my ( $mon, $dat, $tim, $hst, $mta, $msg ) = ""; # my ( $hh, $mm, $ss ) = ""; my ( $key ) = ""; # my ( $p, $v ) = ""; # # # Each from= entry # ---------------- # while ( $id <= $#RES ) { # # Mon dd hh:mm:ss host sm-mta[pid]: id: from=somebody, .... size=xx, ... relay=xx # # no comma in this error message # Mon dd hh:mm:ss host sm-mta[pid]: id: SYSERR(root): Macro/class {bodytype}: too many long names # $line = $RES[$id]; # # # things like will not show properly in html debug mode # # printf "
..ID=$id..line=$line..
\n" ; # # # brute force fix (remove) "illegal" characters -- screws up the associated array # ---------------------------------------------- # $line =~ s/\ $line =~ s/\>//; # $line =~ s/\[//; # sendmail[pid] $line =~ s/\]//; # $line =~ s/-//; # sm-mta[pid] # $line =~ s/\ $line =~ s/\>//; #line =~ s/\@/-at-/; # $line =~ s/\'//; # error messages # # # $DEBUG = 1 if ( $id > 260 ); # # Each field is comma separated # ------------------------------ # @f = split ( /, /, $line ); # to=user1,user2,usr2, mailer=foo, ... # # ( $mon, $dat, $tim, $hst, $mta, $msg, @first ) = split ( /\s+/, $f[0] ); # ( $hh, $mm, $ss ) = split ( /:/, $tim ); # # # # # Info about the same email could occupy multiple lines and mta is not monotonously increasing # $key = "$hh:$mta"; #key = "$hh:$msg"; # printf "..line[$id]=f[$#f]..key=$key....$line..
\n" if ( $DEBUG ); # # # initialize new email status # --------------------------- # if ( $DATA{ "$key" }{ "td" } eq "" ) { # $DATA{ "$key" }{ "size" } = "-"; $DATA{ "$key" }{ "to" } = "-"; $DATA{ "$key" }{ "from" } = "-"; $DATA{ "$key" }{ "relay" } = "-"; $DATA{ "$key" }{ "ruleset" } = "-"; $DATA{ "$key" }{ "reject" } = "-"; $DATA{ "$key" }{ "stat" } = "-"; # # initialize other parameters, even if not displayed in html output # $DATA{ "$key" }{ "arg1" } = "-"; $DATA{ "$key" }{ "ctladdr" } = "-"; $DATA{ "$key" }{ "delay" } = "-"; $DATA{ "$key" }{ "xdelay" } = "-"; $DATA{ "$key" }{ "mailer" } = "-"; $DATA{ "$key" }{ "pri" } = "-"; $DATA{ "$key" }{ "dsn" } = "-"; # } # new entry # # # Set time stamp # $DATA{ "$key" }{ "td" } = "$mon $dat $tim"; # time-date # # # # have other comma separated options # ---------------------------------- # if ( $#f == 0 ) { # $err = join ( " ", @first ); # error like MAIL/EXPN/VRFY/ETRN d # &save_data ( $key, 0.10, "stat", $err ); # $x = $err ; # if ( $x =~ s/SMTP\+queueing/SMTP+queueing/ ) { &save_data ( $key, 0.01, "ruleset", "Restart" ); } elsif ( $x =~ s/SYSERR/SYSERR/ || $x =~ s/Authentication-Warning/Authentication-Warning/ ) { &save_data ( $key, 0.12, "ruleset", "Sendmail.cf-Broken" ); } else { &save_data ( $key, 0.12, "ruleset", "Rejected" ); } # # printf "....first[$#first]=$err..
\n" if ( $DEBUG ); # } else { # ( $p, $v) = split ( /=/, $first[0] ); # last field of "," separated parameters=value, p=val # # &save_data ( $key, 0.0, $p, $v ); # # # each field in "from= entry" # -------------------------- # $jd = 1; # while ( $jd <= $#f ) { # ( $p, $v ) = split ( /=/, $f[$jd] ) ; # $p =~ s/^\s+//; # remove leading spaces chomp ( $v ); # # # save data # &save_data ( $key, $jd, $p, $v ); # $jd += 1; # } # each field # } # # $DEBUG = 0; # done with that debug # $id += 1; # } # Each entry # # } # read_maillog # # # # Select the fields we want to use # -------------------------------- # sub save_data { # my ( $key, $jd, $p, $v ) = ( $_[0], $_[1], $_[2], $_[3] ); # my ( $len ) = length( $v ); # my ( $d ) = $DATA{ "$key" }{ "$p" }; # my ( $err, @msg ) = ""; # # # printf "....$key..j[$#f]=$jd...p=$p..v[$len]=$v..d=$d..
\n" if ( $DEBUG == 666 ); # # Count the Spams and colorize it # --------------- # if ( $p eq "ruleset" ) { # $v = " $v " # } # ruleset # if ( $p eq "reject" ) { # ( $err, @msg ) = split ( /\s+/, $v ); # $v = " $err " . join ( " ", @msg ); # } # rejects # # # Have new data to save # if ( $len > 0 ) { # # printf "..v=$v...
\n" if ( $p eq "to" && $DEBUG == 666 ); # if ( "$d" eq "-" ) { # $DATA{ "$key" }{ "$p" } = "$v"; # first time # } else { # # skip duplicate or partially the same as already saved data # $DATA{ "$key" }{ "$p" } .= "
$v" if ( $d ne $v ); # append if not duplicate info # } # save data # } # have data # # printf "..to=$DATA{ $key }{ $p }...
\n" if ( $p eq "to" && $DEBUG == 666 ); # } # add_data # # # ========================================================================== # # -------------------------------- # Print the Start of the HTML page # -------------------------------- # sub pr_html { # my ( $id ) = 0; # my ( $size, $td, $to, $from, $relay, $ruleset, $reject, $stat ) = ""; # my ( $dd ) = sprintf "%d", $DD; # snip of "0" in 01, 02, 03 # my ( $html ) = "${HTML}/${MAILSTAT}/${Yr_Mon}/${DD}.html"; # # # Make sure we have a place to save the resulting html file # --------------------------------------------------------- # &mk_dir ( 0, "$HTML" ); &mk_dir ( 0, "$HTML/$MAILSTAT" ); &mk_dir ( 0, "$HTML/$MAILSTAT/Temp" ); &mk_dir ( 1, "$HTML/$MAILSTAT/$Yr_Mon" ); # exit if directory doesn't exist # # # Output the mail stats to a temp file # ------------------------------------- # $html = "$HTML/$MAILSTAT/Temp/$PID.html" if ( $SearchMTA != -1 ); # # my ( $status ) = open ( FWR, "> $html" ); # # # # Start html stuff # ---------------- # &pr_html_head ; # # # Looking for specifics vs summary -- NOT used for now # -------------------------------- # if ( $SearchMTA == -1 && $SearchMTA eq "DO_NO_USE_for_NOW" ) { # # Daily, Monthly, Yearly Summary # printf FWR "\n"; printf FWR "\n"; # &pr_html_dmy ( "Daily" ); # &pr_html_dmy ( "Monthly" ); # &pr_html_dmy ( "Year-to-Date" ); # printf FWR "
\n"; # printf FWR "\n"; # } # search # # # spacer printf FWR "

\n"; # # # Search for tha edetails of specific entries # ------------------------------------------- # # printf FWR "..day=$DD..dd=$dd...SearchDay=$SearchDay..
\n"; # printf FWR "
\n"; #rintf FWR "&day=$DD" if ( $dd ne "$SearchDay" ); # not today #rintf FWR "\">\n"; printf FWR " DebugMode -- show original line
\n"; printf FWR " grep -v ( eg: skip root's emails )
\n"; printf FWR "\n"; printf FWR "\n"; printf FWR "
\n"; # # if ( $SearchMTA != -1 ) { # printf FWR "\n"; printf FWR " Found %d matches for search= $REV $SearchMTA
\n", $#RES +1; printf FWR "\n"; # } # # Now Print the per-email results # --------------------------------- # printf FWR "\n\n"; # printf FWR "\n"; # foreach $key ( sort keys %DATA ) { # $size = $DATA{ "$key" }{ "size" }; $td = $DATA{ "$key" }{ "td" }; $to = $DATA{ "$key" }{ "to" }; $from = $DATA{ "$key" }{ "from" }; $relay = $DATA{ "$key" }{ "relay" }; $ruleset = $DATA{ "$key" }{ "ruleset" }; $reject = $DATA{ "$key" }{ "reject" }; $stat = $DATA{ "$key" }{ "stat" }; # $relay =~ s/\[/
[/; # # user1,user2,user3 # $to =~ s/,/
/g if ( $to =~ s/,/,/ ); # # if ( $id % 25 == 0 ) { # printf FWR "\n"; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR "\n"; # } # header # # ( $hh, $m ) = split ( /:/, $key ); # strip off the hours... hh:mta # $mid = $m; $mid =~ s/sendmail//; # sendmail[$id] $mid =~ s/sm-mta//; # sm-mta[$id] # # $mta = "$mm" . "[$mid]"; # printf FWR "\n"; printf FWR "\n"; printf FWR ""; printf FWR ""; #rintf FWR ""; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR ""; printf FWR "\n"; # $id += 1; # } # each data # printf FWR "\n"; printf FWR "
id Date MTA Size From:
To:
Relay RuleSet Reject
Status
$id $td $mta $m $size F: $from
T: $to
$relay $ruleset $reject
$stat
\n"; # # if ( $SearchMTA != -1 ) { # printf FWR "All Done for search=$REV $SearchMTA..
\n"; printf FWR "\n"; } # # # Done with html stuff # -------------------- # &pr_html_end ; # # # printf "..Outputed mailstats to $html\n\n"; # close ( FWR ); # # # # Display search results to the web browser # ---------------------------------------- # if ( $Skip_Stdin == 0 ) { # $status = open ( FRD, "< $html" ); # while ( ) { printf "$_"; } # close ( FRD ); # # get rid of the temporary file unlink $html ; # } # # } # pr_html # # # Daily, Monthly Yearly Summary # ----------------------------- # sub pr_html_dmy { # my ( $title ) = $_[0]; # printf FWR "\n"; printf FWR "\n"; # printf FWR "\n"; printf FWR " \n"; # printf FWR "\n"; printf FWR "\n"; printf FWR "\n"; # printf FWR "\n"; printf FWR "\n"; printf FWR "\n"; # printf FWR "\n"; printf FWR "\n"; # printf FWR "
$title Summary
Outgoing
From To


Incoming
From To


Spam Types
RuleSet Reject Status
\n"; printf FWR "\n"; # } # pr_html_ymd # # ------------------------------------------------------------------ # sub pr_html_head { # printf FWR "\n"; printf FWR "\n"; printf FWR "\n"; printf FWR " MailLog Statistics for $HOST \n"; printf FWR "\n"; # printf FWR "\n"; printf FWR "

\n"; printf FWR "

MailLog Statistics for $HOST

\n"; printf FWR "
\n"; # } # pr_html_head # # # sub pr_html_end { # printf FWR "\n"; # } # pr_html_end # # # Make sure the directory exists # ------------------------------ # sub mk_dir { # my ( $ok ) = $_[0]; my ( $dir ) = $_[1]; # # if ( ! -d $dir ) { # # if ( $ok eq 0 ) { if ( $ok eq 1 ) { # # Make sure the permissions are set right if we create a dir # printf "Creating Missing dir=$dir..\n"; mkdir ( $dir, "0775" ); chown ( $WebMaster, $dir ) ; chmod 0775, $dir ; # } else { # printf "Aborting: Missing dir=$dir..\n"; exit 1; # } # } # no such dir # } # mk_dir # # # # Process the WebPage form data # ----------------------------- # sub proc_data { my ( @p_v ) = @_; # my ( $id ) = 0 ; my ( $p, $v ) = ""; my ( $foo ) = 0; # my ( @res ) = (); # # # Needed for Error Messages # # $Content_type = printf "Content-type: text/html\n\n" if ( $Content_type == 0 ); # # QUERY_STRING=Login=alvin&Param=Value.. # # printf "..p_v[$#p_v]=$p_v[0]..
\n"; # while ( $id <= $#p_v ) { # ( $p, $v ) = split ( /=/, $p_v[$id] ); # # # 24-Nov-01 amo Disallow bad options # $v =~ s/\/$//g; # trailing http://foo.com/ $v =~ s/^\s+//g; # leading spaces $v =~ s/\s+$//g; # traling spaces $v =~ s/\n//g; $v =~ s/\`//g; $v =~ s/\'//g; $v =~ s/\|//g; $v =~ s/\"//g; $v =~ s/\//g; $v =~ s/\;//g; $v =~ s/\?//g; $v =~ s/\*//g; #v =~ s/\///g; # allow ip#/24 $v =~ s/\+/ /g; # convert -v+-sS to -v -sS #v =~ s/\ //g; # allow spaces $v =~ s/\\//g; # skip \ #v =~ s/^\-//g; # allow -v options $v =~ s/\$//g; $v =~ s/\%//g; # $v =~ s/-m//g; # prevent nmap logging $v =~ s/-o//g; # $v =~ s/^\s+//; # take out leading spaces # # # printf "..p=$p..v=$v..
\n"; # if ( $p eq Help ) { # $Help = 1; # } elsif ( $p eq Debug || $p eq debug ) { # $DEBUG = 99; # form button # } elsif ( $p eq Skip ) { # $Skip_Stdin = 1; # } elsif ( $p eq "month" ) { # $SearchMon = "$v" if ( "$v" ne "" ); # } elsif ( $p eq "day" ) { # if ( "$v" ne "" ) { # $SearchDay = "$v" ; $DD = sprintf ( "%2s", $v ); # } # } elsif ( $p eq "mta" ) { # $SearchMTA = "$v" if ( "$v" ne "" ); # } elsif ( $p eq "Search" ) { # generic search box request # $SearchMTA = "$v" if ( "$v" ne "" ); # } elsif ( $p eq "rev" ) { # grep -v SearchMTA # $REV = "-v"; # } # $id += 1; # } # All form data # # my ( @srch ) = split ( /\;/, $SEARCH ); # $p:$val;$p:$val... # # printf "..pv_SearchDay=$SearchDay..
\n"; # } # proc_data # # # # Process the variables from the WebPage forms # -------------------------------------------- # sub proc_form { # # Prevent waiting for ^D if script run from command line # my ( $postinput ) = if ( $Skip_Stdin == 0); # # Define this to be able to debug # # $Content_type = printf "Content-type: text/html\n\n" if ( $Content_type == 0) ; # # print "Posted input content-length = $len..GN=$ENV{'GN_QUERY'}..
\n"; # print "Posted input content-length = $len..QS=$ENV{'QUERY_STRING'}..
\n"; # # if ( $ENV{REQUEST_METHOD} eq "POST") { # # print "Undecoded posted input:
..$postinput

\n\n"; # $postinput =~ s/&/&
\n/g; $postinput =~ s/\+/ /g; $postinput =~ s/%([\da-f]{1,2})/pack(C,hex($1))/eig; # # print "Decoded posted input:
..$postinput
\n"; # Content-type required # } # post # # # append: monitor.pl?Login=USR¶m=value # $postinput .= "&" . "$ENV{'QUERY_STRING'}"; # # $Visitor = $ENV{REMOTE_ADDR}; # who are they # # printf "..postinput=$postinput..
\n"; # Content-type required # return ( split ( /&/, $postinput ) ); # } # proc_form # # # # Parse the command lines # ----------------------- # sub cmd_args { # my ( $arg ) = ""; my ( $id ) = 0; # # Does NOT work if from web page # # $Help = 1 if ( $#ARGV == -1 ); # no options given... # # # Check the Command line options # while ( $id <= $#ARGV ) { # $arg = $ARGV[$id]; # # $Content_type = printf "Content-type: text/html\n\n" if ( $Content_type == 0) ; # printf "..checking $arg..
\n"; # if ( $arg eq "-h" ) { $Help = 1; # } elsif ( $arg eq "-d" || $arg eq "-debug" ) { $DEBUG = 1; # } elsif ( $arg eq "-skip" ) { # $Skip_Stdin = 1; # Skip stdin check in proc_form # } elsif ( $arg eq "-day" ) { # $id += 1; $arg = $ARGV[$id]; # $DD = sprintf ( "%2s", $arg); # Which date to process # } # $id += 1; # } # args # return ( 0 ); # } # cmd_args # # # # end of file