diff -u mailgraph-1.14/mailgraph.cgi mailgraph-1.14mod/mailgraph.cgi --- mailgraph-1.14/mailgraph.cgi Sun Dec 30 17:32:07 2007 +++ mailgraph-1.14mod/mailgraph.cgi Sun Dec 30 18:40:40 2007 @@ -5,10 +5,12 @@ # copyright (c) 2000-2007 David Schweikert # released under the GNU General Public License +# modified to include reject reasons by Martin Schuette + use RRDs; use POSIX qw(uname); -my $VERSION = "1.14"; +my $VERSION = "1.14mod"; my $host = (POSIX::uname())[1]; my $scriptname = 'mailgraph.cgi'; @@ -16,8 +18,9 @@ my $points_per_sample = 3; my $ypoints = 160; my $ypoints_err = 96; -my $rrd = 'mailgraph.rrd'; # path to where the RRD database is -my $rrd_virus = 'mailgraph_virus.rrd'; # path to where the Virus RRD database is +my $rrd = 'mailgraph.rrd'; # path to where the RRD database is +my $rrd_virus = 'mailgraph_virus.rrd'; # path to where the Virus RRD database is +my $rrd_rejects = "mailgraph_rejects.rrd"; my $tmp_dir = '/tmp/mailgraph'; # temporary directory where to store the images my @graphs = ( @@ -34,6 +37,15 @@ bounced => '000000', virus => 'DDBB00', spam => '999999', + rej_userunknown => 'AAFFFF', + rej_sender => 'FFAAAA', + rej_norelay => 'AAFFAA', + rej_policydw => '555555', + rej_helo => '55FF55', + rej_dnsbl => 'FF55FF', + rej_greylisted => 'FFFF00', + rej_greyblocked => '0000FF', + rej_other => 'CCCCCC', ); sub rrd_graph(@) @@ -96,6 +108,18 @@ 'GPRINT:srecv:MAX:total\: %8.0lf msgs', 'GPRINT:rrecv:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmrecv:MAX:max\: %4.0lf msgs/min\l', + + # include thin reject-line into sent/receive graph + "DEF:rejected=$rrd:rejected:AVERAGE", + "DEF:mrejected=$rrd:rejected:MAX", + "CDEF:rrejected=rejected,60,*", + "CDEF:drejected=rejected,UN,0,rejected,IF,$step,*", + "CDEF:srejected=PREV,UN,drejected,PREV,IF,drejected,+", + "CDEF:rmrejected=mrejected,60,*", + "LINE1:rrejected#$color{rejected}:Rejected", + 'GPRINT:srejected:MAX:total\: %8.0lf msgs', + 'GPRINT:rrejected:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrejected:MAX:max\: %4.0lf msgs/min\l', ); } @@ -103,14 +127,141 @@ { my ($range, $file) = @_; my $step = $range*$points_per_sample/$xpoints; - rrd_graph($range, $file, $ypoints_err, + # new graph layout: + # reject-reasons are stacked upward, + # virus/spam/bounce stacked negative and downwards, + rrd_graph($range, $file, $ypoints, + "DEF:rej_userunknown=$rrd_rejects:rej_userunknown:AVERAGE", + "DEF:mrej_userunknown=$rrd_rejects:rej_userunknown:MAX", + "CDEF:rrej_userunknown=rej_userunknown,60,*", + "CDEF:drej_userunknown=rej_userunknown,UN,0,rej_userunknown,IF,$step,*", + "CDEF:srej_userunknown=PREV,UN,drej_userunknown,PREV,IF,drej_userunknown,+", + "CDEF:rmrej_userunknown=mrej_userunknown,60,*", + "AREA:rrej_userunknown#$color{rej_userunknown}:user unknown ", + 'GPRINT:srej_userunknown:MAX:total\: %8.0lf msgs', + 'GPRINT:rrej_userunknown:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrej_userunknown:MAX:max\: %4.0lf msgs/min\l', + + "DEF:rej_sender=$rrd_rejects:rej_sender:AVERAGE", + "DEF:mrej_sender=$rrd_rejects:rej_sender:MAX", + "CDEF:rrej_sender=rej_sender,60,*", + "CDEF:drej_sender=rej_sender,UN,0,rej_sender,IF,$step,*", + "CDEF:srej_sender=PREV,UN,drej_sender,PREV,IF,drej_sender,+", + "CDEF:rmrej_sender=mrej_sender,60,*", + "STACK:rrej_sender#$color{rej_sender}:sender ", + 'GPRINT:srej_sender:MAX:total\: %8.0lf msgs', + 'GPRINT:rrej_sender:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrej_sender:MAX:max\: %4.0lf msgs/min\l', + + "DEF:rej_norelay=$rrd_rejects:rej_norelay:AVERAGE", + "DEF:mrej_norelay=$rrd_rejects:rej_norelay:MAX", + "CDEF:rrej_norelay=rej_norelay,60,*", + "CDEF:drej_norelay=rej_norelay,UN,0,rej_norelay,IF,$step,*", + "CDEF:srej_norelay=PREV,UN,drej_norelay,PREV,IF,drej_norelay,+", + "CDEF:rmrej_norelay=mrej_norelay,60,*", + "STACK:rrej_norelay#$color{rej_norelay}:no Relaying ", + 'GPRINT:srej_norelay:MAX:total\: %8.0lf msgs', + 'GPRINT:rrej_norelay:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrej_norelay:MAX:max\: %4.0lf msgs/min\l', + + "DEF:rej_dnsbl=$rrd_rejects:rej_dnsbl:AVERAGE", + "DEF:mrej_dnsbl=$rrd_rejects:rej_dnsbl:MAX", + "CDEF:rrej_dnsbl=rej_dnsbl,60,*", + "CDEF:drej_dnsbl=rej_dnsbl,UN,0,rej_dnsbl,IF,$step,*", + "CDEF:srej_dnsbl=PREV,UN,drej_dnsbl,PREV,IF,drej_dnsbl,+", + "CDEF:rmrej_dnsbl=mrej_dnsbl,60,*", + "STACK:rrej_dnsbl#$color{rej_dnsbl}:DNS RBL ", + 'GPRINT:srej_dnsbl:MAX:total\: %8.0lf msgs', + 'GPRINT:rrej_dnsbl:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrej_dnsbl:MAX:max\: %4.0lf msgs/min\l', + + "DEF:rej_policydw=$rrd_rejects:rej_policydw:AVERAGE", + "DEF:mrej_policydw=$rrd_rejects:rej_policydw:MAX", + "CDEF:rrej_policydw=rej_policydw,60,*", + "CDEF:drej_policydw=rej_policydw,UN,0,rej_policydw,IF,$step,*", + "CDEF:srej_policydw=PREV,UN,drej_policydw,PREV,IF,drej_policydw,+", + "CDEF:rmrej_policydw=mrej_policydw,60,*", + "STACK:rrej_policydw#$color{rej_policydw}:policyd-weight", + 'GPRINT:srej_policydw:MAX:total\: %8.0lf msgs', + 'GPRINT:rrej_policydw:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrej_policydw:MAX:max\: %4.0lf msgs/min\l', + + "DEF:rej_helo=$rrd_rejects:rej_helo:AVERAGE", + "DEF:mrej_helo=$rrd_rejects:rej_helo:MAX", + "CDEF:rrej_helo=rej_helo,60,*", + "CDEF:drej_helo=rej_helo,UN,0,rej_helo,IF,$step,*", + "CDEF:srej_helo=PREV,UN,drej_helo,PREV,IF,drej_helo,+", + "CDEF:rmrej_helo=mrej_helo,60,*", + "STACK:rrej_helo#$color{rej_helo}:HELO ", + 'GPRINT:srej_helo:MAX:total\: %8.0lf msgs', + 'GPRINT:rrej_helo:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrej_helo:MAX:max\: %4.0lf msgs/min\l', + + "DEF:rej_greylisted=$rrd_rejects:rej_greylisted:AVERAGE", + "DEF:mrej_greylisted=$rrd_rejects:rej_greylisted:MAX", + "CDEF:rrej_greylisted=rej_greylisted,60,*", + "CDEF:drej_greylisted=rej_greylisted,UN,0,rej_greylisted,IF,$step,*", + "CDEF:srej_greylisted=PREV,UN,drej_greylisted,PREV,IF,drej_greylisted,+", + "CDEF:rmrej_greylisted=mrej_greylisted,60,*", + "STACK:rrej_greylisted#$color{rej_greylisted}:Greylisted ", + 'GPRINT:srej_greylisted:MAX:total\: %8.0lf msgs', + 'GPRINT:rrej_greylisted:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrej_greylisted:MAX:max\: %4.0lf msgs/min\l', + + "DEF:rej_greyblocked=$rrd_rejects:rej_greyblocked:AVERAGE", + "DEF:mrej_greyblocked=$rrd_rejects:rej_greyblocked:MAX", + "CDEF:rrej_greyblocked=rej_greyblocked,60,*", + "CDEF:drej_greyblocked=rej_greyblocked,UN,0,rej_greyblocked,IF,$step,*", + "CDEF:srej_greyblocked=PREV,UN,drej_greyblocked,PREV,IF,drej_greyblocked,+", + "CDEF:rmrej_greyblocked=mrej_greyblocked,60,*", + "STACK:rrej_greyblocked#$color{rej_greyblocked}:retry too fast", + 'GPRINT:srej_greyblocked:MAX:total\: %8.0lf msgs', + 'GPRINT:rrej_greyblocked:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrej_greyblocked:MAX:max\: %4.0lf msgs/min\l', + + "DEF:rej_other=$rrd_rejects:rej_other:AVERAGE", + "DEF:mrej_other=$rrd_rejects:rej_other:MAX", + "CDEF:rrej_other=rej_other,60,*", + "CDEF:drej_other=rej_other,UN,0,rej_other,IF,$step,*", + "CDEF:srej_other=PREV,UN,drej_other,PREV,IF,drej_other,+", + "CDEF:rmrej_other=mrej_other,60,*", + "STACK:rrej_other#$color{rej_other}:Other rejects ", + 'GPRINT:srej_other:MAX:total\: %8.0lf msgs', + 'GPRINT:rrej_other:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrej_other:MAX:max\: %4.0lf msgs/min\l', + + "DEF:rejected=$rrd:rejected:AVERAGE", + "DEF:mrejected=$rrd:rejected:MAX", + "CDEF:rrejected=rejected,60,*", + "CDEF:drejected=rejected,UN,0,rejected,IF,$step,*", + "CDEF:srejected=PREV,UN,drejected,PREV,IF,drejected,+", + "CDEF:rmrejected=mrejected,60,*", + "LINE1:rrejected#$color{rejected}:Total rejected", + 'GPRINT:srejected:MAX:total\: %8.0lf msgs', + 'GPRINT:rrejected:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmrejected:MAX:max\: %4.0lf msgs/min\l', + + # move spam to bottom of stack + "DEF:spam=$rrd_virus:spam:AVERAGE", + "DEF:mspam=$rrd_virus:spam:MAX", + "CDEF:rspam=spam,60,*", + "CDEF:negrspam=rspam,-1,*", + "CDEF:dspam=spam,UN,0,spam,IF,$step,*", + "CDEF:sspam=PREV,UN,dspam,PREV,IF,dspam,+", + "CDEF:rmspam=mspam,60,*", + "AREA:negrspam#$color{spam}:Spam detected ", + 'GPRINT:sspam:MAX:total\: %8.0lf msgs', + 'GPRINT:rspam:AVERAGE:avg\: %5.2lf msgs/min', + 'GPRINT:rmspam:MAX:max\: %4.0lf msgs/min\l', + "DEF:bounced=$rrd:bounced:AVERAGE", "DEF:mbounced=$rrd:bounced:MAX", "CDEF:rbounced=bounced,60,*", + "CDEF:negrbounced=rbounced,-1,*", "CDEF:dbounced=bounced,UN,0,bounced,IF,$step,*", "CDEF:sbounced=PREV,UN,dbounced,PREV,IF,dbounced,+", "CDEF:rmbounced=mbounced,60,*", - "AREA:rbounced#$color{bounced}:Bounced ", + "STACK:negrbounced#$color{bounced}:Bounced ", 'GPRINT:sbounced:MAX:total\: %8.0lf msgs', 'GPRINT:rbounced:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmbounced:MAX:max\: %4.0lf msgs/min\l', @@ -118,36 +269,14 @@ "DEF:virus=$rrd_virus:virus:AVERAGE", "DEF:mvirus=$rrd_virus:virus:MAX", "CDEF:rvirus=virus,60,*", + "CDEF:negrvirus=rvirus,-1,*", "CDEF:dvirus=virus,UN,0,virus,IF,$step,*", "CDEF:svirus=PREV,UN,dvirus,PREV,IF,dvirus,+", "CDEF:rmvirus=mvirus,60,*", - "STACK:rvirus#$color{virus}:Viruses ", + "STACK:negrvirus#$color{virus}:Viruses ", 'GPRINT:svirus:MAX:total\: %8.0lf msgs', 'GPRINT:rvirus:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmvirus:MAX:max\: %4.0lf msgs/min\l', - - "DEF:spam=$rrd_virus:spam:AVERAGE", - "DEF:mspam=$rrd_virus:spam:MAX", - "CDEF:rspam=spam,60,*", - "CDEF:dspam=spam,UN,0,spam,IF,$step,*", - "CDEF:sspam=PREV,UN,dspam,PREV,IF,dspam,+", - "CDEF:rmspam=mspam,60,*", - "STACK:rspam#$color{spam}:Spam ", - 'GPRINT:sspam:MAX:total\: %8.0lf msgs', - 'GPRINT:rspam:AVERAGE:avg\: %5.2lf msgs/min', - 'GPRINT:rmspam:MAX:max\: %4.0lf msgs/min\l', - - "DEF:rejected=$rrd:rejected:AVERAGE", - "DEF:mrejected=$rrd:rejected:MAX", - "CDEF:rrejected=rejected,60,*", - "CDEF:drejected=rejected,UN,0,rejected,IF,$step,*", - "CDEF:srejected=PREV,UN,drejected,PREV,IF,drejected,+", - "CDEF:rmrejected=mrejected,60,*", - "LINE2:rrejected#$color{rejected}:Rejected", - 'GPRINT:srejected:MAX:total\: %8.0lf msgs', - 'GPRINT:rrejected:AVERAGE:avg\: %5.2lf msgs/min', - 'GPRINT:rmrejected:MAX:max\: %4.0lf msgs/min\l', - ); } @@ -163,7 +292,29 @@ Mail statistics for $host - + HEADER diff -u mailgraph-1.14/mailgraph.pl mailgraph-1.14mod/mailgraph.pl --- mailgraph-1.14/mailgraph.pl Sun Dec 30 17:34:17 2007 +++ mailgraph-1.14mod/mailgraph.pl Sun Dec 30 18:40:40 2007 @@ -5,6 +5,8 @@ # copyright (c) 2000-2007 David Schweikert # released under the GNU General Public License +# modified to include reject reasons by Martin Schuette + ######## Parse::Syslog 1.09 (automatically embedded) ######## package Parse::Syslog; use Carp; @@ -364,7 +366,7 @@ use Getopt::Long; use POSIX 'setsid'; -my $VERSION = "1.14"; +my $VERSION = "1.14mod"; # config my $rrdstep = 60; @@ -379,9 +381,13 @@ my $logfile; my $rrd = "mailgraph.rrd"; my $rrd_virus = "mailgraph_virus.rrd"; +my $rrd_rejects = "mailgraph_rejects.rrd"; my $year; my $this_minute; -my %sum = ( sent => 0, received => 0, bounced => 0, rejected => 0, virus => 0, spam => 0 ); +my %sum = ( sent => 0, received => 0, bounced => 0, rejected => 0, virus => 0, + spam => 0, rej_greylisted => 0, rej_greyblocked => 0, rej_helo => 0, + rej_policydw => 0, rej_dnsbl => 0, rej_userunknown => 0, rej_sender => 0, + rej_norelay => 0, rej_other => 0 ); my $rrd_inited=0; my %opt = (); @@ -562,6 +568,33 @@ $this_minute = RRDs::last($rrd_virus) + $rrdstep; } + # rejects rrd + if(! -f $rrd_rejects and ! $opt{'only-virus-rrd'}) { + RRDs::create($rrd_rejects, '--start', $m, '--step', $rrdstep, + 'DS:rej_greylisted:ABSOLUTE:'.($rrdstep*2).':0:U', + 'DS:rej_greyblocked:ABSOLUTE:'.($rrdstep*2).':0:U', + 'DS:rej_helo:ABSOLUTE:'.($rrdstep*2).':0:U', + 'DS:rej_policydw:ABSOLUTE:'.($rrdstep*2).':0:U', + 'DS:rej_dnsbl:ABSOLUTE:'.($rrdstep*2).':0:U', + 'DS:rej_userunknown:ABSOLUTE:'.($rrdstep*2).':0:U', + 'DS:rej_sender:ABSOLUTE:'.($rrdstep*2).':0:U', + 'DS:rej_norelay:ABSOLUTE:'.($rrdstep*2).':0:U', + 'DS:rej_other:ABSOLUTE:'.($rrdstep*2).':0:U', + "RRA:AVERAGE:0.5:$day_steps:$realrows", # day + "RRA:AVERAGE:0.5:$week_steps:$realrows", # week + "RRA:AVERAGE:0.5:$month_steps:$realrows", # month + "RRA:AVERAGE:0.5:$year_steps:$realrows", # year + "RRA:MAX:0.5:$day_steps:$realrows", # day + "RRA:MAX:0.5:$week_steps:$realrows", # week + "RRA:MAX:0.5:$month_steps:$realrows", # month + "RRA:MAX:0.5:$year_steps:$realrows", # year + ); + } + elsif(-f $rrd_rejects and ! defined $rrd_rejects) { + $this_minute = RRDs::last($rrd_rejects) + $rrdstep; + } + + $rrd_inited=1; } @@ -612,6 +645,42 @@ } elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: /) { event($time, 'rejected'); + if($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: RCPT from .*: Helo command rejected: /o) { + event($time, 'rej_helo'); + } + elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: RCPT from .*: Recipient address rejected: Mail appeared to be SPAM or forged\./o) { + event($time, 'rej_policydw'); + } + elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: RCPT from .*: Recipient address rejected: Diese Mailingliste wurde geloescht\.( Bei)? Nachfragen dazu bitte /o) { + event($time, 'rej_userunknown'); + } + elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: RCPT from .*: (Recipient address rejected: Your MTA is listed in too many DNSBLs|554 Service unavailable; Client host \[.*\] blocked using )/o) { + event($time, 'rej_dnsbl'); + } + elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: RCPT from .*: Recipient address rejected: Greylisted, see http:\/\/postgrey\.schweikert\.ch\/help\//o) { + event($time, 'rej_greylisted'); + } + elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: RCPT from .*: Recipient address rejected: temporarily blocked because of previous errors - /o) { + event($time, 'rej_greyblocked'); + } + elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: RCPT from .*: Recipient address rejected:.* User unknown /o) { + event($time, 'rej_userunknown'); + } + elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: RCPT from .*: Sender address rejected: /o) { + event($time, 'rej_sender'); + } + elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: RCPT from .*: Relay access denied; /o) { + event($time, 'rej_norelay'); + } + elsif($text =~ /^(NOQUEUE: )?reject: RCPT from.*: 450 4\.7\.1 Client host rejected: cannot find your hostname/o) { + # rej_other because not yet in RRD + event($time, 'rej_other'); + } + else { + # not recognized + print "unrecognized log line: $text\n" if $opt{verbose}; + event($time, 'rej_other'); + } } elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?milter-reject: /) { if($text =~ /Blocked by SpamAssassin/) { @@ -698,6 +767,7 @@ event($time, 'spam'); } else { event($time, 'rejected'); + event($time, 'rej_dnsbl'); } } elsif($text =~ / rejected RCPT \S+: (Sender verify failed|Unknown user)/) { @@ -873,11 +943,13 @@ print "update $this_minute:$sum{sent}:$sum{received}:$sum{bounced}:$sum{rejected}:$sum{virus}:$sum{spam}\n" if $opt{verbose}; RRDs::update $rrd, "$this_minute:$sum{sent}:$sum{received}:$sum{bounced}:$sum{rejected}" unless $opt{'only-virus-rrd'}; RRDs::update $rrd_virus, "$this_minute:$sum{virus}:$sum{spam}" unless $opt{'only-mail-rrd'}; + RRDs::update $rrd_rejects, "$this_minute:$sum{rej_greylisted}:$sum{rej_greyblocked}:$sum{rej_helo}:$sum{rej_policydw}:$sum{rej_dnsbl}:$sum{rej_userunknown}:$sum{rej_sender}:$sum{rej_norelay}:$sum{rej_other}" unless $opt{'only-virus-rrd'}; if($m > $this_minute+$rrdstep) { for(my $sm=$this_minute+$rrdstep;$sm<$m;$sm+=$rrdstep) { print "update $sm:0:0:0:0:0:0 (SKIP)\n" if $opt{verbose}; RRDs::update $rrd, "$sm:0:0:0:0" unless $opt{'only-virus-rrd'}; RRDs::update $rrd_virus, "$sm:0:0" unless $opt{'only-mail-rrd'}; + RRDs::update $rrd_rejects, "$sm:0:0:0:0:0:0:0:0:0" unless $opt{'only-virus-rrd'}; } } $this_minute = $m; @@ -887,6 +959,15 @@ $sum{rejected}=0; $sum{virus}=0; $sum{spam}=0; + $sum{rej_greylisted}=0; + $sum{rej_greyblocked}=0; + $sum{rej_helo}=0; + $sum{rej_policydw}=0; + $sum{rej_dnsbl}=0; + $sum{rej_userunknown}=0; + $sum{rej_sender}=0; + $sum{rej_norelay}=0; + $sum{rej_other}=0; return 1; }