#!/usr/bin/perl -w # TODO: test if speedycgi works ? # ovs -- mail traffic statistics # copyright (c) 2007 Xavier Beaudouin # released under the GNU General Public License # # Based upon mailgraph, which # copyright (c) 2000-2007 ETH Zurich # copyright (c) 2000-2007 David Schweikert # released under the GNU General Public License # # And Based also upon couriergraph, which is # copyright (c) 2002-2006 Ralf Hildebrandt # relased under the GNU General Public License use RRDs; use POSIX qw(uname); my $VERSION = "1.01"; my $host = (POSIX::uname())[1]; my $scriptname = 'ovs.cgi'; my $xpoints = 540; my $points_per_sample = 3; my $ypoints = 160; my $ypoints_err = 260; my $rrd = '%%DATADIR%%/ovs.rrd'; # path to where the RRD database is my $rrd_virus = '%%DATADIR%%/ovs_virus.rrd'; # path to where the Virus RRD database is my $rrd_pop = '%%DATADIR%%/ovs_pop.rrd'; # path to where the Virus RRD database is my $tmp_dir = '/tmp/ovs'; # temporary directory where to store the images my @graphs = ( { title => 'Last Day', seconds => 3600*24, }, { title => 'Last Week', seconds => 3600*24*7, }, { title => 'Last Month', seconds => 3600*24*31, }, { title => 'Last Year', seconds => 3600*24*365, }, ); my %color = ( sent => '000099', # rrggbb in hex received => '009900', rejected => 'AA0000', bounced => '000000', virus => 'CCFF99', spam => 'DDBB00', greylist => 'CCCCCC', helo => '009999', spf => '990099', dnf => '00FFFF', policydbl => 'FF0000', vrfytmp => 'FFF000', vrfyrjt => 'FF00F0', ); sub rrd_graph(@) { my ($range, $file, $ypoints, @rrdargs) = @_; my $step = $range*$points_per_sample/$xpoints; # choose carefully the end otherwise rrd will maybe pick the wrong RRA: my $end = time; $end -= $end % $step; my $date = localtime(time); $date =~ s|:|\\:|g unless $RRDs::VERSION < 1.199908; my ($graphret,$xs,$ys) = RRDs::graph($file, '--imgformat', 'PNG', '--width', $xpoints, '--height', $ypoints, '--start', "-$range", '--end', $end, '--vertical-label', 'msgs/min', '--lower-limit', 0, '--units-exponent', 0, # don't show milli-messages/s '--lazy', '--color', 'SHADEA#ffffff', '--color', 'SHADEB#ffffff', '--color', 'BACK#ffffff', $RRDs::VERSION < 1.2002 ? () : ( '--slope-mode'), @rrdargs, 'COMMENT:['.$date.']\r', ); my $ERR=RRDs::error; die "ERROR: $ERR\n" if $ERR; } sub graph($$) { my ($range, $file) = @_; my $step = $range*$points_per_sample/$xpoints; rrd_graph($range, $file, $ypoints, "DEF:sent=$rrd:sent:AVERAGE", "DEF:msent=$rrd:sent:MAX", "CDEF:rsent=sent,60,*", "CDEF:rmsent=msent,60,*", "CDEF:dsent=sent,UN,0,sent,IF,$step,*", "CDEF:ssent=PREV,UN,dsent,PREV,IF,dsent,+", "AREA:rsent#$color{sent}:Sent ", 'GPRINT:ssent:MAX:total\: %8.0lf msgs', 'GPRINT:rsent:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmsent:MAX:max\: %4.0lf msgs/min\l', "DEF:recv=$rrd:recv:AVERAGE", "DEF:mrecv=$rrd:recv:MAX", "CDEF:rrecv=recv,60,*", "CDEF:rmrecv=mrecv,60,*", "CDEF:drecv=recv,UN,0,recv,IF,$step,*", "CDEF:srecv=PREV,UN,drecv,PREV,IF,drecv,+", "LINE2:rrecv#$color{received}:Received", 'GPRINT:srecv:MAX:total\: %8.0lf msgs', 'GPRINT:rrecv:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmrecv: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', ); } sub graph_err($$) { my ($range, $file) = @_; my $step = $range*$points_per_sample/$xpoints; rrd_graph($range, $file, $ypoints_err, "DEF:bounced=$rrd:bounced:AVERAGE", "DEF:mbounced=$rrd:bounced:MAX", "CDEF:rbounced=bounced,60,*", "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 ", 'GPRINT:sbounced:MAX:total\: %8.0lf msgs', 'GPRINT:rbounced:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmbounced:MAX:max\: %4.0lf msgs/min\l', "DEF:virus=$rrd_virus:virus:AVERAGE", "DEF:mvirus=$rrd_virus:virus:MAX", "CDEF:rvirus=virus,60,*", "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 ", '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,*", "LINE2: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:greylist=$rrd_virus:greylist:AVERAGE", "DEF:mgreylist=$rrd_virus:greylist:MAX", "CDEF:rgreylist=greylist,60,*", "CDEF:dgreylist=greylist,UN,0,greylist,IF,$step,*", "CDEF:sgreylist=PREV,UN,dgreylist,PREV,IF,dgreylist,+", "CDEF:rmgreylist=mgreylist,60,*", "STACK:rgreylist#$color{greylist}:Greylist", 'GPRINT:sgreylist:MAX:total\: %8.0lf msgs', 'GPRINT:rgreylist:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmgreylist:MAX:max\: %4.0lf msgs/min\l', "DEF:helo=$rrd_virus:helo:AVERAGE", "DEF:mhelo=$rrd_virus:helo:MAX", "CDEF:rhelo=helo,60,*", "CDEF:dhelo=helo,UN,0,helo,IF,$step,*", "CDEF:shelo=PREV,UN,dhelo,PREV,IF,dhelo,+", "CDEF:rmhelo=mhelo,60,*", "STACK:rhelo#$color{helo}:Helo rej", 'GPRINT:shelo:MAX:total\: %8.0lf msgs', 'GPRINT:rhelo:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmhelo:MAX:max\: %4.0lf msgs/min\l', "DEF:spf=$rrd_virus:spf:AVERAGE", "DEF:mspf=$rrd_virus:spf:MAX", "CDEF:rspf=spf,60,*", "CDEF:dspf=spf,UN,0,spf,IF,$step,*", "CDEF:sspf=PREV,UN,dspf,PREV,IF,dspf,+", "CDEF:rmspf=mspf,60,*", "STACK:rspf#$color{spf}:Spf ", 'GPRINT:sspf:MAX:total\: %8.0lf msgs', 'GPRINT:rspf:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmspf:MAX:max\: %4.0lf msgs/min\l', "DEF:dnf=$rrd_virus:dnf:AVERAGE", "DEF:mdnf=$rrd_virus:dnf:MAX", "CDEF:rdnf=dnf,60,*", "CDEF:ddnf=dnf,UN,0,dnf,IF,$step,*", "CDEF:sdnf=PREV,UN,ddnf,PREV,IF,ddnf,+", "CDEF:rmdnf=mdnf,60,*", "STACK:rdnf#$color{dnf}:DomainNF", 'GPRINT:sdnf:MAX:total\: %8.0lf msgs', 'GPRINT:rdnf:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmdnf:MAX:max\: %4.0lf msgs/min\l', "DEF:policydbl=$rrd_virus:policydbl:AVERAGE", "DEF:mpolicydbl=$rrd_virus:policydbl:MAX", "CDEF:rpolicydbl=policydbl,60,*", "CDEF:dpolicydbl=policydbl,UN,0,policydbl,IF,$step,*", "CDEF:spolicydbl=PREV,UN,dpolicydbl,PREV,IF,dpolicydbl,+", "CDEF:rmpolicydbl=mpolicydbl,60,*", "STACK:rpolicydbl#$color{policydbl}:PolicyBL", 'GPRINT:spolicydbl:MAX:total\: %8.0lf msgs', 'GPRINT:rpolicydbl:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmpolicydbl:MAX:max\: %4.0lf msgs/min\l', "DEF:vrfytmp=$rrd_virus:vrfytmp:AVERAGE", "DEF:mvrfytmp=$rrd_virus:vrfytmp:MAX", "CDEF:rvrfytmp=vrfytmp,60,*", "CDEF:dvrfytmp=vrfytmp,UN,0,vrfytmp,IF,$step,*", "CDEF:svrfytmp=PREV,UN,dvrfytmp,PREV,IF,dvrfytmp,+", "CDEF:rmvrfytmp=mvrfytmp,60,*", "STACK:rvrfytmp#$color{vrfytmp}:Vrfy Tmp", 'GPRINT:svrfytmp:MAX:total\: %8.0lf msgs', 'GPRINT:rvrfytmp:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmvrfytmp:MAX:max\: %4.0lf msgs/min\l', "DEF:vrfyrjt=$rrd_virus:vrfyrjt:AVERAGE", "DEF:mvrfyrjt=$rrd_virus:vrfyrjt:MAX", "CDEF:rvrfyrjt=vrfyrjt,60,*", "CDEF:dvrfyrjt=vrfyrjt,UN,0,vrfyrjt,IF,$step,*", "CDEF:svrfyrjt=PREV,UN,dvrfyrjt,PREV,IF,dvrfyrjt,+", "CDEF:rmvrfyrjt=mvrfyrjt,60,*", "STACK:rvrfyrjt#$color{vrfyrjt}:Vrfy rej", 'GPRINT:svrfyrjt:MAX:total\: %8.0lf msgs', 'GPRINT:rvrfyrjt:AVERAGE:avg\: %5.2lf msgs/min', 'GPRINT:rmvrfyrjt:MAX:max\: %4.0lf msgs/min\l', ); } sub graph_pop($$) { my ($range, $file) = @_; my $step = $range*$points_per_sample/$xpoints; rrd_graph($range, $file, $ypoints_err, "DEF:pop3d_login=$rrd_pop:pop3d_login:AVERAGE", "DEF:mpop3d_login=$rrd_pop:pop3d_login:MAX", "DEF:pop3d_ssl_login=$rrd_pop:pop3d_ssl_login:AVERAGE", "DEF:mpop3d_ssl_login=$rrd_pop:pop3d_ssl_login:MAX", "CDEF:rpop3d_login=pop3d_login,60,*", "CDEF:vpop3d_login=pop3d_login,UN,0,pop3d_login,IF,$step,*", "CDEF:rmpop3d_login=mpop3d_login,60,*", "CDEF:rpop3d_ssl_login=pop3d_ssl_login,60,*", "CDEF:vpop3d_ssl_login=pop3d_ssl_login,UN,0,pop3d_ssl_login,IF,$step,*", "CDEF:rmpop3d_ssl_login=mpop3d_ssl_login,60,*", "DEF:imapd_login=$rrd_pop:imapd_login:AVERAGE", "DEF:mimapd_login=$rrd_pop:imapd_login:MAX", "DEF:imapd_ssl_login=$rrd_pop:imapd_ssl_login:AVERAGE", "DEF:mimapd_ssl_login=$rrd_pop:imapd_ssl_login:MAX", "CDEF:rimapd_login=imapd_login,60,*", "CDEF:vimapd_login=imapd_login,UN,0,imapd_login,IF,$step,*", "CDEF:rmimapd_login=mimapd_login,60,*", "CDEF:rimapd_ssl_login=imapd_ssl_login,60,*", "CDEF:rmimapd_ssl_login=mimapd_ssl_login,60,*", "CDEF:vimapd_ssl_login=imapd_ssl_login,UN,0,imapd_ssl_login,IF,$step,*", 'LINE:rpop3d_login#DD0000:pop3', 'GPRINT:vpop3d_login:AVERAGE:total\: %.0lf logins', 'GPRINT:rmpop3d_login:MAX:max\: %.0lf logins/min\l', 'HRULE:0#000000', 'AREA:rpop3d_ssl_login#770000:pop3/ssl:STACK', 'GPRINT:vpop3d_ssl_login:AVERAGE:total\: %.0lf logins', 'GPRINT:rmpop3d_ssl_login:MAX:max\: %.0lf logins/min\l', 'HRULE:0#000000', 'LINE:rimapd_login#00DD00:imap', 'GPRINT:vimapd_login:AVERAGE:total\: %.0lf logins', 'GPRINT:rmimapd_login:MAX:max\: %.0lf logins/min\l', 'HRULE:0#000000', 'AREA:rimapd_ssl_login#007700:imap/ssl:STACK', 'GPRINT:vimapd_ssl_login:AVERAGE:total\: %.0lf logins', 'GPRINT:rmimapd_ssl_login:MAX:max\: %.0lf logins/min\l', ); } sub print_html() { print "Content-Type: text/html\n\n"; print < Mail statistics for $host HEADER print "

Mail statistics for $host

\n"; print "\n"; for my $n (0..$#graphs) { print "

$graphs[$n]{title}

\n"; print "

\"ovs\"/
\n"; print "\"ovs\"/


\n"; print "\"ovs\"/

\n"; } print <
OpenVISP Stats $VERSION by Xavier Beaudouin
Based on Mailgraph & CourierGraph
FOOTER } sub send_image($) { my ($file)= @_; -r $file or do { print "Content-type: text/plain\n\nERROR: can't find $file\n"; exit 1; }; print "Content-type: image/png\n"; print "Content-length: ".((stat($file))[7])."\n"; print "\n"; open(IMG, $file) or die; my $data; print $data while read(IMG, $data, 16384)>0; } sub main() { my $uri = $ENV{REQUEST_URI} || ''; $uri =~ s/\/[^\/]+$//; $uri =~ s/\//,/g; $uri =~ s/(\~|\%7E)/tilde,/g; mkdir $tmp_dir, 0777 unless -d $tmp_dir; mkdir "$tmp_dir/$uri", 0777 unless -d "$tmp_dir/$uri"; my $img = $ENV{QUERY_STRING}; if(defined $img and $img =~ /\S/) { if($img =~ /^(\d+)-n$/) { my $file = "$tmp_dir/$uri/ovs_$1.png"; graph($graphs[$1]{seconds}, $file); send_image($file); } elsif($img =~ /^(\d+)-e$/) { my $file = "$tmp_dir/$uri/ovs_$1_err.png"; graph_err($graphs[$1]{seconds}, $file); send_image($file); } elsif($img =~ /^(\d+)-p$/) { my $file = "$tmp_dir/$uri/ovs_$1_pop.png"; graph_pop($graphs[$1]{seconds}, $file); send_image($file); } else { die "ERROR: invalid argument\n"; } } else { print_html; } } main;