Peter Siegrist vor 7 Jahren
Commit
709b061ab4
6 geänderte Dateien mit 2040 neuen und 0 gelöschten Zeilen
  1. 144 0
      README
  2. 144 0
      README.md
  3. 350 0
      lib/Parse/Syslog/Mail.pm
  4. 351 0
      lib/Parse/Syslog/alt_si_Mail.pm
  5. 43 0
      yasma.conf
  6. 1008 0
      yasma.pl

+ 144 - 0
README

@@ -0,0 +1,144 @@
+# yasma.pl
+# (Yet Another Sendmail Log Analyzer)
+#
+# Copyright (c) 2006 by Peter_Siegrist(SystemLoesungen)  (PSS@ZweierNet.ch)
+# 
+# All Rights reserved.
+# This program is free software; you can redistribute it and/or 
+# modify it under the terms of the GNU General Public License as 
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
+# GNU General Public License for more details.    
+#
+
+README
+------
+
+BE CAREFUL BEFORE YOU USE THIS SOTWARE !
+In some Countries it might be illegal to read mail logs and produce mail log summaries.
+Or, at least, it is illegal to view names of users you run a mail service for it.
+If so, make sure that you run Yasma always with command line option (-u) or configuration file 
+parameter "HIDE_USERS = TRUE".
+
+
+Thanks to Sébastien Aperghis-Tramoni
+====================================
+
+Yasma uses a slightly modified Parse::Syslog::Mail perl module 
+from Sébastien Aperghis-Tramoni <sebastien@aperghis.net>.
+
+Therefore you dont have to install this module, its delivered within the Yasma package.
+
+
+Installation:
+=============
+
+Download the archive, unpack it to a directory of your choice (ie. /usr/local/) with
+
+    - tar xvfz yasma-0.91.tar.gz
+
+tar will create a new directory (ie. yasma-0.91/) with all necessary files. 
+You can rename this directory if you like so, 
+or, for updates just copy the perl script to your old yasma location and check the 
+differences of the config file.
+
+
+Go to this directory an edit the 'yasma.conf' file suited to your environment and fancy.
+The configuration variables are explained within this file.
+
+
+Run it:
+=======
+
+The 'yasma.pl' programm have to run within the directory where yasma.conf resides 
+and must have read access to logfiles and write access to the folder 
+where the html output file will be written.
+
+To run the program, just type:
+
+	#> yasma.pl [-c config-file] [-f output-file] [-u] /path_to_logfiles/logfile[*]
+
+Yasma can read single or multiple logfiles who can be plain, gzipped (.gz), compressed (.Z) or mixed.
+
+! Be sure the program cannot be started from an insecure place or user !
+
+
+Command line options:
+---------------------
+-c config	full name of yasma.conf config-file
+
+-u			dont show user names of mail addresses
+
+-f file		print the report to this file. Else output will sent to file defined in configfile.
+
+
+Examples:
+=========
+
+	#> yasma.pl -f /opt/httpd/htdocs/report.html /var/log/mail*
+
+this will read all logs beginning with mail* and puts the html report to the file specified by the -f option.
+
+	#> yasma.pl /var/log/mail
+
+this will read the logfile /var/log/mail and puts the html report to file you have defined in 'yasma.conf'
+
+	#> yasma.pl -u /var/log/mail
+
+the same as above but do not show user part of addresses.
+
+
+GeoIP Database:
+===============
+
+For installing and upgrading GeoIP-database you should do the following:
+1. Download GeoIP-database 'GeoIP.dat.gz' from http://www.maxmind.com/download/geoip/database/GeoLiteCountry/
+2. unpack the GeoIP.dat to the place you defined in yasma.conf (param "geoip_dbase")
+
+
+System Requirements:
+====================
+
+
+Software:
+
+    * Linux/Unix System mit Perl >= 5.6 und Web-Server
+    * Perlmodule CGI, IO::Handle, File::Basename, Parse::Syslog and POSIX from CPAN
+    * Optional Geo::IP::PurePerl and its GeoIP Database
+
+
+Changes
+=======
+0.94: 	new comandline parameter -c configfile
+		allow timestamps in the future in mailfile
+0.95: 	fix a senseless isa() call when no logfile is given.	
+
+0.96:	give Parse::Syslog::Mail a year attribute if we can grep it from the filename. 
+		Useful for leap years.
+
+		
+ToDo:
+=====
+
+Yasma is very unfinished.
+Yasma is tested just for sendmail logfiles. Other mailsystems should be added in future.
+Therefor I'm looking for logfiles from other mailsystems as well as strange sendmail logfiles.
+
+
+Other:
+======
+I would be pleased about suggestions, experience or problems.
+
+Please send any mails regarding yasma to yasma@ZweierNet.ch
+
+
+Copyright und Lizenz:
+=====================
+
+Copyright © 2006 by Peter_Siegrist(SystemLoesungen) <pss@zweiernet.ch>.
+
+This program is free software under the terms of the GNU General Public License.
+

+ 144 - 0
README.md

@@ -0,0 +1,144 @@
+# yasma.pl
+# (Yet Another Sendmail Log Analyzer)
+#
+# Copyright (c) 2006 by Peter_Siegrist(SystemLoesungen)  (PSS@ZweierNet.ch)
+# 
+# All Rights reserved.
+# This program is free software; you can redistribute it and/or 
+# modify it under the terms of the GNU General Public License as 
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
+# GNU General Public License for more details.    
+#
+
+README
+------
+
+BE CAREFUL BEFORE YOU USE THIS SOTWARE !
+In some Countries it might be illegal to read mail logs and produce mail log summaries.
+Or, at least, it is illegal to view names of users you run a mail service for it.
+If so, make sure that you run Yasma always with command line option (-u) or configuration file 
+parameter "HIDE_USERS = TRUE".
+
+
+Thanks to Sébastien Aperghis-Tramoni
+====================================
+
+Yasma uses a slightly modified Parse::Syslog::Mail perl module 
+from Sébastien Aperghis-Tramoni <sebastien@aperghis.net>.
+
+Therefore you dont have to install this module, its delivered within the Yasma package.
+
+
+Installation:
+=============
+
+Download the archive, unpack it to a directory of your choice (ie. /usr/local/) with
+
+    - tar xvfz yasma-0.91.tar.gz
+
+tar will create a new directory (ie. yasma-0.91/) with all necessary files. 
+You can rename this directory if you like so, 
+or, for updates just copy the perl script to your old yasma location and check the 
+differences of the config file.
+
+
+Go to this directory an edit the 'yasma.conf' file suited to your environment and fancy.
+The configuration variables are explained within this file.
+
+
+Run it:
+=======
+
+The 'yasma.pl' programm have to run within the directory where yasma.conf resides 
+and must have read access to logfiles and write access to the folder 
+where the html output file will be written.
+
+To run the program, just type:
+
+	#> yasma.pl [-c config-file] [-f output-file] [-u] /path_to_logfiles/logfile[*]
+
+Yasma can read single or multiple logfiles who can be plain, gzipped (.gz), compressed (.Z) or mixed.
+
+! Be sure the program cannot be started from an insecure place or user !
+
+
+Command line options:
+---------------------
+-c config	full name of yasma.conf config-file
+
+-u			dont show user names of mail addresses
+
+-f file		print the report to this file. Else output will sent to file defined in configfile.
+
+
+Examples:
+=========
+
+	#> yasma.pl -f /opt/httpd/htdocs/report.html /var/log/mail*
+
+this will read all logs beginning with mail* and puts the html report to the file specified by the -f option.
+
+	#> yasma.pl /var/log/mail
+
+this will read the logfile /var/log/mail and puts the html report to file you have defined in 'yasma.conf'
+
+	#> yasma.pl -u /var/log/mail
+
+the same as above but do not show user part of addresses.
+
+
+GeoIP Database:
+===============
+
+For installing and upgrading GeoIP-database you should do the following:
+1. Download GeoIP-database 'GeoIP.dat.gz' from http://www.maxmind.com/download/geoip/database/GeoLiteCountry/
+2. unpack the GeoIP.dat to the place you defined in yasma.conf (param "geoip_dbase")
+
+
+System Requirements:
+====================
+
+
+Software:
+
+    * Linux/Unix System mit Perl >= 5.6 und Web-Server
+    * Perlmodule CGI, IO::Handle, File::Basename, Parse::Syslog and POSIX from CPAN
+    * Optional Geo::IP::PurePerl and its GeoIP Database
+
+
+Changes
+=======
+0.94: 	new comandline parameter -c configfile
+		allow timestamps in the future in mailfile
+0.95: 	fix a senseless isa() call when no logfile is given.	
+
+0.96:	give Parse::Syslog::Mail a year attribute if we can grep it from the filename. 
+		Useful for leap years.
+
+		
+ToDo:
+=====
+
+Yasma is very unfinished.
+Yasma is tested just for sendmail logfiles. Other mailsystems should be added in future.
+Therefor I'm looking for logfiles from other mailsystems as well as strange sendmail logfiles.
+
+
+Other:
+======
+I would be pleased about suggestions, experience or problems.
+
+Please send any mails regarding yasma to yasma@ZweierNet.ch
+
+
+Copyright und Lizenz:
+=====================
+
+Copyright © 2006 by Peter_Siegrist(SystemLoesungen) <pss@zweiernet.ch>.
+
+This program is free software under the terms of the GNU General Public License.
+

+ 350 - 0
lib/Parse/Syslog/Mail.pm

@@ -0,0 +1,350 @@
+##- Package modified by sigi <pss@zweiernet.ch>
+##- for usage with Yasma
+##- sigi, 2006
+
+
+package Parse::Syslog::Mail;
+use strict;
+use Carp;
+use Parse::Syslog;
+
+{ no strict;
+  $VERSION = '0.09';
+}
+
+=head1 NAME
+
+Parse::Syslog::Mail - Parse mailer logs from syslog
+
+=head1 VERSION
+
+Version 0.09
+
+=head1 SYNOPSIS
+
+    use Parse::Syslog::Mail;
+
+    my $maillog = Parse::Syslog::Mail->new('/var/log/syslog');
+    
+    while(my $log = $maillog->next) {
+	# do something with $log
+        # ...
+    }
+
+=head1 DESCRIPTION
+
+As its names implies, C<Parse::Syslog::Mail> presents a simple interface 
+to gather mail information from a syslog file. It uses C<Parse::Syslog> for 
+reading the syslog, and offer the same simple interface. Currently supported 
+log formats are: Sendmail, Postfix, Qmail.
+
+
+=head1 METHODS
+
+=over 4
+
+=item B<new()>
+
+Creates and returns a new C<Parse::Syslog::Mail> object. 
+A file path or a C<File::Tail> object is expected as first argument. 
+Options can follow as a hash. Most are the same as for C<Parse::Syslog->new()>. 
+
+B<Options>
+
+=over 4
+
+=item *
+
+C<year> - Syslog files usually do store the time of the event without 
+year. With this option you can specify the start-year of this log. If 
+not specified, it will be set to the current year.
+
+=item *
+
+C<GMT> - If this option is set, the time in the syslog will be converted 
+assuming it is GMT time instead of local time.
+
+=item *
+
+C<repeat> - C<Parse::Syslog> will by default repeat xx times events that 
+are followed by messages like C<"last message repeated xx times">. If you 
+set this option to false, it won't do that.
+
+=item *
+
+C<locale> - Specifies an additional locale name or the array of locale 
+names for the parsing of log files with national characters.
+
+=item *
+
+C<allow_future> - If true will allow for timestamps in the future. 
+Otherwise timestamps of one day in the future and more will not be returned 
+(as a safety measure against wrong configurations, bogus --year arguments, 
+etc.)
+
+=back
+
+B<Example>
+
+    my $syslog = new Parse::Syslog::Mail '/var/log/syslog', allow_future => 1;
+
+=cut
+
+sub new {
+    my $self = {
+        syslog => undef, 
+    };
+    my $class = ref $_[0] ? ref shift : shift;
+    bless $self, $class;
+
+    my $file = shift;
+    my %args = @_;
+
+    croak "fatal: Expected an argument"
+      unless defined $file;
+    
+    croak "fatal: First argument of new() must be a file path of a File::Tail object"
+      unless -f $file or $file->isa('File::Tail') or $file->isa('IO::Handle');
+    
+    eval { $self->{syslog} = new Parse::Syslog $file, %args };
+    if($@) {
+        $@ =~ s/ at .*$//;
+        croak "fatal: Can't create new Parse::Syslog object: $@";
+    }
+
+    return $self
+}
+
+=item B<next()>
+
+Returns the next line of the syslog as a hashref, or C<undef> when there 
+is no more lines. The hashref contains at least the following keys: 
+
+=over 4
+
+=item *
+
+C<host> - hostname of the machine.
+
+=item *
+
+C<program> - name of the program. 
+
+=item *
+
+C<timestamp> - Unix timestamp for the event.
+
+=item *
+
+C<id> - Local transient mail identifier. 
+
+=item *
+
+C<text> - text description.
+
+=back
+
+Other available keys:
+
+=over 4
+
+=item *
+
+C<from> - Email address of the sender.
+
+=item *
+
+C<to> - Email addresses of the recipients, coma-separated.
+
+=item *
+
+C<msgid> - Message ID.
+
+=item *
+
+C<relay> - MTA host used for relaying the mail.
+
+=item *
+
+C<status> - Status of the transaction.
+
+=item *
+
+C<delivery_type> - I<(Qmail only)> type of the delivery: C<"local"> or C<"remote">.
+
+=item *
+
+C<delivery_id> - I<(Qmail only)> id number of the delivery.
+
+=back
+
+B<Example>
+
+    while(my $log = $syslog->next) {
+        # do something with $log
+    }
+
+=cut
+
+sub next {
+    my $self = shift;
+    my %mail = ();
+    my @fields = qw(host program timestamp text);
+    my %delivery2id = ();  # used to map delivery id with msg id (Qmail)
+	my $si_id = time();
+	
+    LINE: {
+        my $log = $self->{syslog}->next;
+        return undef unless defined $log;
+        @mail{@fields} = @$log{@fields};
+        my $text = $log->{text};
+
+        # Sendmail & Postfix format parsing ------------------------------------
+        if($log->{program} =~ /^(?:sendmail|sm-mta|postfix)/) {
+            redo LINE if $text =~ /^(?:NOQUEUE|STARTTLS|TLS:)/;
+            redo LINE if $text =~ /prescan: (?:token too long|too many tokens|null leading token) *$/;
+			my $id;
+            $text =~ s/^(\w+): *// and $id = $1;         # gather the MTA transient id
+            if ( $text =~ /^ruleset=/ && not $id ) {
+            	$id = "si_" . $si_id++;         # set id for msgs w/o msgid (by sigi)
+            }
+            redo LINE unless $id;
+
+            redo LINE if $text =~ /^\s*(?:[<-]--|[Mm]ilter|SYSERR)/;   # we don't treat these
+
+            $text =~ s/stat=/status=/;                      # renaming 'stat' field to 'status'
+            $text =~ s/^\s*([^=]+)\s*$/status=$1/;          # format other status messages
+            $text =~ s/^\s*([^=]+)\s*;\s*/status=$1, /;     # format other status messages (2)
+            $text =~ s/collect: /collect=/;                 # treat collect messages as field identifiers
+            $text =~ s/(\S+),\s+([\w-]+)=/$1\t$2=/g;        # replace fields seperator with tab character
+
+            %mail = (%mail, map {
+                    s/,$//;  s/^ +//;  s/ +$//; # cleaning spaces
+                    s/.*\s+([\w-]+=)/$1/;       # cleaning up field names
+                    split /=/, $_, 2            # no more than 2 elements
+                 } split /\t/, $text);
+			($mail{to}) = $1 if $mail{to} =~ /^(.+)\s.*/;		# elim multiple addrs in to-field (sigi)
+			
+            if(exists $mail{ruleset} and exists $mail{arg1}) {
+                $mail{ruleset} eq 'check_mail'  and $mail{from}  = $mail{arg1};
+                $mail{ruleset} eq 'check_rcpt'  and $mail{to}    = $mail{arg1};
+                $mail{ruleset} eq 'check_relay' and $mail{relay} = $mail{arg1};
+
+                unless(exists $mail{status}) {
+                    $mail{reject}     and $mail{status} = "reject: $mail{reject}";
+                    $mail{quarantine} and $mail{status} = "quarantine: $mail{quarantine}";
+                }
+            }
+	
+            $mail{id} = $id;
+
+        # Courier ESMTP -------------------------------------------------------
+        } elsif($log->{program} =~ /^courier/) {
+            redo LINE if $text =~ /^(?:NOQUEUE|STARTTLS|TLS:)/;
+
+            $text =~ s/,status: /,status=/;     # treat status as a field
+            $text =~ s/,(\w+)=/\t$1=/g;         # replace fields separator with tab character
+
+            %mail = (%mail, map {
+                    split /=/, $_, 2
+                } split /\t/, $text);
+
+        # Qmail format parsing -------------------------------------------------
+        } elsif($log->{program} =~ /^qmail/) {
+            $text =~ s/^(\d+\.\d+) // and $mail{qmail_timestamp} = $1;   # Qmail timestamp
+            redo LINE if $text =~ /^(?:status|bounce|warning)/;
+
+            # record 'new' and 'end' events in the status
+            $text =~ s/^(new|end) msg (\d+)$// 
+                and $mail{status} = "$1 message" and $mail{id} = $2 and last;
+
+            # record 'triple bounce' events in the status
+            $text =~ s/^(triple bounce: discarding bounce)\/(\d+)$// 
+                and $mail{status} = $1 and $mail{id} = $2 and last;
+
+            # mail id and its size
+            $text =~ s/^info msg (\d+): bytes (\d+) from (<[^>]*>) // 
+                and $mail{id} = $1 and $mail{size} = $2 and $mail{from} = $3;
+            
+            # begining of the delivery
+            $text =~ s/^(starting delivery (\d+)): msg (\d+) to (local|remote) (.+)$// 
+                and $mail{status} = $1 and $mail{id} = $3 and $delivery2id{$2} = $3 
+                and $mail{delivery_id} = $2 and $mail{delivery_type} = $4 and $mail{to} = $5;
+
+            $text =~ s/^delivery (\d+): +// 
+                and $mail{delivery_id} = $1 and $mail{id} = $delivery2id{$1} || '';
+            
+            # status of the delivery
+            $text =~ s/^(success|deferral|failure): +(\S+)// 
+                and $mail{status} = "$1: $2" and $mail{status} =~ tr/_/ /;
+
+            # in case of missing mail id, generate one
+            $mail{id} ||= 'psm' . time;
+
+        } else {
+            redo LINE
+        }
+    }
+
+    return \%mail
+}
+
+=back
+
+
+=head1 DIAGNOSTICS
+
+=over 4
+
+=item Can't create new %s object: %s
+
+B<(F)> Occurs in C<new()>. As the message says, we were unable to create 
+a new object of the given class. The rest of the error may give more information. 
+
+=item Expected an argument
+
+B<(F)> You tried to call C<new()> with no argument. 
+
+=item First argument of new() must be a file path of a File::Tail object
+
+B<(F)> As the message says, you must give to C<new()> a valid (and readable) 
+file path or a C<File::Tail> object as first argument. 
+
+=back
+
+=head1 SEE ALSO
+
+L<Parse::Syslog>
+
+=head1 TODO
+
+Add support for other mailer daemons (Exim, Courier, Qpsmtpd). 
+Send me logs or, even better, patches, if you want support for your 
+favorite mailer daemon. 
+
+=head1 AUTHOR
+
+SE<eacute>bastien Aperghis-Tramoni E<lt>sebastien@aperghis.netE<gt>
+
+=head1 BUGS
+
+Please report any bugs or feature requests to
+C<bug-parse-syslog-mail@rt.cpan.org>, or through the web interface at
+L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Parse-Syslog-Mail>. 
+I will be notified, and then you'll automatically be notified 
+of progress on your bug as I make changes.
+
+=head1 CAVEATS
+
+Most probably the same as C<Parse::Syslog>, see L<Parse::Syslog/"BUGS">
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright 2005 SE<eacute>bastien Aperghis-Tramoni, All Rights Reserved.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=cut
+
+1; # End of Parse::Syslog::Mail

+ 351 - 0
lib/Parse/Syslog/alt_si_Mail.pm

@@ -0,0 +1,351 @@
+##- Package modified by sigi <pss@zweiernet.ch>
+##- for usage with Yasma
+##- sigi, 2006
+
+
+package Parse::Syslog::Mail;
+use strict;
+use Carp;
+use Parse::Syslog;
+
+{ no strict;
+  $VERSION = '0.09';
+}
+
+=head1 NAME
+
+Parse::Syslog::Mail - Parse mailer logs from syslog
+
+=head1 VERSION
+
+Version 0.09
+
+=head1 SYNOPSIS
+
+    use Parse::Syslog::Mail;
+
+    my $maillog = Parse::Syslog::Mail->new('/var/log/syslog');
+    
+    while(my $log = $maillog->next) {
+	# do something with $log
+        # ...
+    }
+
+=head1 DESCRIPTION
+
+As its names implies, C<Parse::Syslog::Mail> presents a simple interface 
+to gather mail information from a syslog file. It uses C<Parse::Syslog> for 
+reading the syslog, and offer the same simple interface. Currently supported 
+log formats are: Sendmail, Postfix, Qmail.
+
+
+=head1 METHODS
+
+=over 4
+
+=item B<new()>
+
+Creates and returns a new C<Parse::Syslog::Mail> object. 
+A file path or a C<File::Tail> object is expected as first argument. 
+Options can follow as a hash. Most are the same as for C<Parse::Syslog->new()>. 
+
+B<Options>
+
+=over 4
+
+=item *
+
+C<year> - Syslog files usually do store the time of the event without 
+year. With this option you can specify the start-year of this log. If 
+not specified, it will be set to the current year.
+
+=item *
+
+C<GMT> - If this option is set, the time in the syslog will be converted 
+assuming it is GMT time instead of local time.
+
+=item *
+
+C<repeat> - C<Parse::Syslog> will by default repeat xx times events that 
+are followed by messages like C<"last message repeated xx times">. If you 
+set this option to false, it won't do that.
+
+=item *
+
+C<locale> - Specifies an additional locale name or the array of locale 
+names for the parsing of log files with national characters.
+
+=item *
+
+C<allow_future> - If true will allow for timestamps in the future. 
+Otherwise timestamps of one day in the future and more will not be returned 
+(as a safety measure against wrong configurations, bogus --year arguments, 
+etc.)
+
+=back
+
+B<Example>
+
+    my $syslog = new Parse::Syslog::Mail '/var/log/syslog', allow_future => 1;
+
+=cut
+
+sub new {
+    my $self = {
+        syslog => undef, 
+    };
+    my $class = ref $_[0] ? ref shift : shift;
+    bless $self, $class;
+
+    my $file = shift;
+    my %args = @_;
+
+    croak "fatal: Expected an argument"
+      unless defined $file;
+    
+    croak "fatal: First argument of new() must be a file path of a File::Tail object"
+      unless -f $file or $file->isa('File::Tail') or $file->isa('IO::Handle');
+    
+    eval { $self->{syslog} = new Parse::Syslog $file, %args };
+    if($@) {
+        $@ =~ s/ at .*$//;
+        croak "fatal: Can't create new Parse::Syslog object: $@";
+    }
+
+    return $self
+}
+
+=item B<next()>
+
+Returns the next line of the syslog as a hashref, or C<undef> when there 
+is no more lines. The hashref contains at least the following keys: 
+
+=over 4
+
+=item *
+
+C<host> - hostname of the machine.
+
+=item *
+
+C<program> - name of the program. 
+
+=item *
+
+C<timestamp> - Unix timestamp for the event.
+
+=item *
+
+C<id> - Local transient mail identifier. 
+
+=item *
+
+C<text> - text description.
+
+=back
+
+Other available keys:
+
+=over 4
+
+=item *
+
+C<from> - Email address of the sender.
+
+=item *
+
+C<to> - Email addresses of the recipients, coma-separated.
+
+=item *
+
+C<msgid> - Message ID.
+
+=item *
+
+C<relay> - MTA host used for relaying the mail.
+
+=item *
+
+C<status> - Status of the transaction.
+
+=item *
+
+C<delivery_type> - I<(Qmail only)> type of the delivery: C<"local"> or C<"remote">.
+
+=item *
+
+C<delivery_id> - I<(Qmail only)> id number of the delivery.
+
+=back
+
+B<Example>
+
+    while(my $log = $syslog->next) {
+        # do something with $log
+    }
+
+=cut
+
+sub next {
+    my $self = shift;
+    my %mail = ();
+    my @fields = qw(host program timestamp text);
+    my %delivery2id = ();  # used to map delivery id with msg id (Qmail)
+	my $si_id = time();
+	
+    LINE: {
+        my $log = $self->{syslog}->next;
+        return undef unless defined $log;
+        @mail{@fields} = @$log{@fields};
+        my $text = $log->{text};
+
+        # Sendmail & Postfix format parsing ------------------------------------
+        if($log->{program} =~ /^(?:sendmail|sm-mta|postfix)/) {
+            redo LINE if $text =~ /^(?:NOQUEUE|STARTTLS|TLS:)/;
+            redo LINE if $text =~ /prescan: (?:token too long|too many tokens|null leading token) *$/;
+			my $id;
+            $text =~ s/^(\w+): *// and $id = $1;         # gather the MTA transient id
+            if ( $text =~ /^ruleset=/ && not $id ) {
+            	$id = "si_" . $si_id++;         # set id for msgs w/o msgid (by sigi)
+            }
+            redo LINE unless $id;
+
+            redo LINE if $text =~ /^\s*(?:[<-]--|[Mm]ilter|SYSERR)/;   # we don't treat these
+
+            $text =~ s/stat=/status=/;                      # renaming 'stat' field to 'status'
+            $text =~ s/^\s*([^=]+)\s*$/status=$1/;          # format other status messages
+            $text =~ s/^\s*([^=]+)\s*;\s*/status=$1, /;     # format other status messages (2)
+            $text =~ s/collect: /collect=/;                 # treat collect messages as field identifiers
+            $text =~ s/(\S+),\s+([\w-]+)=/$1\t$2=/g;        # replace fields seperator with tab character
+
+            %mail = (%mail, map {
+                    s/,$//;  s/^ +//;  s/ +$//; # cleaning spaces
+                    s/.*\s+([\w-]+=)/$1/;       # cleaning up field names
+                    split /=/, $_, 2            # no more than 2 elements
+                 } split /\t/, $text);
+			my $tmpt = $mail{to};
+			$mail{to} = split /\s/, $tmpt, 1; print "gaga\n";
+			
+            if(exists $mail{ruleset} and exists $mail{arg1}) {
+                $mail{ruleset} eq 'check_mail'  and $mail{from}  = $mail{arg1};
+                $mail{ruleset} eq 'check_rcpt'  and $mail{to}    = $mail{arg1};
+                $mail{ruleset} eq 'check_relay' and $mail{relay} = $mail{arg1};
+
+                unless(exists $mail{status}) {
+                    $mail{reject}     and $mail{status} = "reject: $mail{reject}";
+                    $mail{quarantine} and $mail{status} = "quarantine: $mail{quarantine}";
+                }
+            }
+	
+            $mail{id} = $id;
+
+        # Courier ESMTP -------------------------------------------------------
+        } elsif($log->{program} =~ /^courier/) {
+            redo LINE if $text =~ /^(?:NOQUEUE|STARTTLS|TLS:)/;
+
+            $text =~ s/,status: /,status=/;     # treat status as a field
+            $text =~ s/,(\w+)=/\t$1=/g;         # replace fields separator with tab character
+
+            %mail = (%mail, map {
+                    split /=/, $_, 2
+                } split /\t/, $text);
+
+        # Qmail format parsing -------------------------------------------------
+        } elsif($log->{program} =~ /^qmail/) {
+            $text =~ s/^(\d+\.\d+) // and $mail{qmail_timestamp} = $1;   # Qmail timestamp
+            redo LINE if $text =~ /^(?:status|bounce|warning)/;
+
+            # record 'new' and 'end' events in the status
+            $text =~ s/^(new|end) msg (\d+)$// 
+                and $mail{status} = "$1 message" and $mail{id} = $2 and last;
+
+            # record 'triple bounce' events in the status
+            $text =~ s/^(triple bounce: discarding bounce)\/(\d+)$// 
+                and $mail{status} = $1 and $mail{id} = $2 and last;
+
+            # mail id and its size
+            $text =~ s/^info msg (\d+): bytes (\d+) from (<[^>]*>) // 
+                and $mail{id} = $1 and $mail{size} = $2 and $mail{from} = $3;
+            
+            # begining of the delivery
+            $text =~ s/^(starting delivery (\d+)): msg (\d+) to (local|remote) (.+)$// 
+                and $mail{status} = $1 and $mail{id} = $3 and $delivery2id{$2} = $3 
+                and $mail{delivery_id} = $2 and $mail{delivery_type} = $4 and $mail{to} = $5;
+
+            $text =~ s/^delivery (\d+): +// 
+                and $mail{delivery_id} = $1 and $mail{id} = $delivery2id{$1} || '';
+            
+            # status of the delivery
+            $text =~ s/^(success|deferral|failure): +(\S+)// 
+                and $mail{status} = "$1: $2" and $mail{status} =~ tr/_/ /;
+
+            # in case of missing mail id, generate one
+            $mail{id} ||= 'psm' . time;
+
+        } else {
+            redo LINE
+        }
+    }
+
+    return \%mail
+}
+
+=back
+
+
+=head1 DIAGNOSTICS
+
+=over 4
+
+=item Can't create new %s object: %s
+
+B<(F)> Occurs in C<new()>. As the message says, we were unable to create 
+a new object of the given class. The rest of the error may give more information. 
+
+=item Expected an argument
+
+B<(F)> You tried to call C<new()> with no argument. 
+
+=item First argument of new() must be a file path of a File::Tail object
+
+B<(F)> As the message says, you must give to C<new()> a valid (and readable) 
+file path or a C<File::Tail> object as first argument. 
+
+=back
+
+=head1 SEE ALSO
+
+L<Parse::Syslog>
+
+=head1 TODO
+
+Add support for other mailer daemons (Exim, Courier, Qpsmtpd). 
+Send me logs or, even better, patches, if you want support for your 
+favorite mailer daemon. 
+
+=head1 AUTHOR
+
+SE<eacute>bastien Aperghis-Tramoni E<lt>sebastien@aperghis.netE<gt>
+
+=head1 BUGS
+
+Please report any bugs or feature requests to
+C<bug-parse-syslog-mail@rt.cpan.org>, or through the web interface at
+L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Parse-Syslog-Mail>. 
+I will be notified, and then you'll automatically be notified 
+of progress on your bug as I make changes.
+
+=head1 CAVEATS
+
+Most probably the same as C<Parse::Syslog>, see L<Parse::Syslog/"BUGS">
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright 2005 SE<eacute>bastien Aperghis-Tramoni, All Rights Reserved.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=cut
+
+1; # End of Parse::Syslog::Mail

+ 43 - 0
yasma.conf

@@ -0,0 +1,43 @@
+#-- Yasma Configuration File                               
+#-- All entries are of the form "param = value".
+#-- Lines beginning with a "#" are comments.
+#-----------------------------------------------------------
+
+
+
+#- Hide user-part from mailaddresses ? (true or false)
+HIDE_USERS = FALSE
+
+#- Print each message packet. !!long long list!! (true or false)
+DEBUG = FALSE
+
+#- Full name of html output file.
+#out_file =
+
+#- Full name of GeoIP Database. 
+#-   Empty or comment it out if you want disable the feature.
+geoip_dbase = /usr/local/yasma/GeoIP.dat
+
+#- Number of lines to show:
+#    of Top Sender Report
+max_top_snt = 40
+#    of Top Recipients Report
+max_top_rcv = 40
+#    of Top Senders Size Report
+max_top_siz = 40
+#    of Top Recipient Size Report
+max_top_riz = 40
+#    of Top Deferrer Report
+max_top_def = 25
+#    of Top Rejects Report
+max_top_rej = 25
+#    of Top Supposed Spammers Report
+max_top_ssp = 25
+#-   of Top Staus Messages
+max_top_sts = 25
+#-   of Top Relay Hosts
+max_top_rel = 25
+#-   of Top Mailer
+max_top_mlr = 25
+#-   of Top Mailer Hosts
+max_top_hst = 25

+ 1008 - 0
yasma.pl

@@ -0,0 +1,1008 @@
+#!/usr/bin/perl
+#
+# yasma.pl
+# (Yet Another Sendmail Log Analyzer)
+#
+# Copyright (c) 2006 by Peter_Siegrist(SystemLoesungen)  (PSS@ZweierNet.ch)
+# 
+# All Rights reserved.
+# This program is free software; you can redistribute it and/or 
+# modify it under the terms of the GNU General Public License as 
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
+# GNU General Public License for more details.    
+#
+
+require 5.006_001;
+use strict;
+use subs qw(lprint);
+use locale;
+use POSIX qw(locale_h);
+use CGI qw(:all); $| = 1; # flush
+eval { use Geo::IP::PurePerl; };
+use File::Basename;
+use IO::Handle;
+use Getopt::Std;
+use vars qw($opt_f $opt_c $opt_u);
+
+use lib "./lib";
+use Parse::Syslog::Mail;	# modified version of Parse::Syslog::Mail
+
+my $VERSION = "V0.96";
+
+#--
+sub TRUE  { 1; }
+sub FALSE { 0; }
+
+getopt('fc');
+
+#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#
+my $conf_file = $opt_c || "yasma.conf";
+
+my $geoip_dbase = "";
+
+#- Hide user-part from mailaddresses ?
+my $HIDE_USERS = FALSE;
+
+#- print $OUT each message packet. !!long long list!!
+my $DEBUG = FALSE;
+
+#- Number of lines to show:
+#-   of Top Sender Report
+my $max_top_snt = 20;
+#-   of Top Recipients Report
+my $max_top_rcv = 20;
+#-   of Top Senders Size Report
+my $max_top_siz = 20;
+#-   of Top Recipient Size Report
+my $max_top_riz = 20;
+#-   of Top Deferrer Report
+my $max_top_def = 25;
+#-   of Top Rejects Report
+my $max_top_rej = 25;
+#-   of Top Supposed Spammers Report
+my $max_top_ssp = 25;
+#-   of Top Staus Messages
+my $max_top_sts = 25;
+#-   of Top Relay Hosts
+my $max_top_rel = 25;
+#-   of Top Mailer
+my $max_top_mlr = 25;
+#-   of Top Mailer Hosts
+my $max_top_hst = 25;
+#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#
+
+
+
+my %hash_bas = ();
+my %hash_def = ();
+my %hash_rej = ();
+my %hash_snt = ();
+my %hash_rcv = ();
+my %hash_ssp = ();
+my %hash_sts = ();
+my %hash_rel = ();
+my %hash_mlr = ();
+my %hash_hst = ();
+my %top_rsize = ();
+my %top_ssize = ();
+my %top_sender = ();
+my %top_recv = ();
+my %hash_present = ();
+my $t1 = time();
+my $t2 = 0;
+my $out_file;
+
+my $msg_total = 0;
+my $msg_outbound = 0;
+my $msg_inbound = 0;
+my $msg_relay = 0;
+my $msg_deferred = 0;
+my $msg_rejected = 0;
+my $msg_size = 0;
+my $msg_in_size = 0;
+my $msg_out_size = 0;
+my $msg_relay_size = 0;
+
+
+&read_config;
+
+#-- cmd line opts
+
+$out_file = $opt_f if $opt_f;
+$HIDE_USERS = TRUE if $opt_u;
+
+
+
+#-- output direction
+
+my $OUT = *STDOUT;
+$out_file && do { open($OUT, ">$out_file") or die "Cannot open $out_file: $!\n"; select $OUT; $| = 1; };
+
+	
+#-- Read maillog
+
+foreach my $infile ( map { glob($_) } @ARGV ) {
+	if ( ! -f $infile ) {
+		print STDERR "File: '$infile' don't exist. Skipping ...\n";
+		next;
+	}
+	my $log_year = ""; 
+	print STDERR "Processing $infile\n";
+	$log_year = substr( $1, 0, 4) if $infile =~ /[_\.\-](\d{8})[_\.\-]/;
+	if ($infile =~ /(\.Z$|\.gz$)/) { 
+		open( INZ, "zcat $infile |" ) or die "err: INZ: $!\n";
+		$infile = IO::Handle->new_from_fd( fileno(INZ), "r" ) or die "err-io-handle: $!\n";
+	}
+	my $maillog;
+	if ( $log_year != "" ) {
+		$maillog = new Parse::Syslog::Mail $infile, allow_future => 1, year=>$log_year or die $!;
+	} else {
+		$maillog = new Parse::Syslog::Mail $infile, allow_future => 1 or die $!;
+	}
+	while(my $log = $maillog->next) {
+	    if ( $$log{'timestamp'} ) {
+	    	$t1 = $$log{'timestamp'} if $$log{'timestamp'} < $t1;
+	    	$t2 = $$log{'timestamp'} if $$log{'timestamp'} > $t2;
+	    }
+	    if ( $$log{status} ) {
+	    	
+	    	(exists $$log{host})	 && ($hash_hst{$$log{host}}{count} +=1);
+	    	(exists $$log{mailer})	 && ($hash_mlr{$$log{mailer}}{count} +=1);
+	    	(exists $$log{program})	 && ($hash_bas{program}{$$log{program}}{count} +=1);
+	    	(exists $$log{relay})	 && ($hash_rel{$$log{relay}}{count} +=1);
+	    	CASE: {
+	    		$$log{status} =~ /Deferred\: (.*)/i	&& do {
+	    			my $txt = $1;
+	    			$hash_def{$$log{id}}{count} +=1;
+	    			$hash_def{$$log{id}}{timestamp} = localtime($$log{timestamp});
+	    			$hash_def{$$log{id}}{status} = $txt;
+	    			my $to = $$log{to};
+	    			$to =~ s/^.+(?=\@)(.*)/$1/ if $HIDE_USERS;
+	    			$to =~ s/\<//;
+	    			$to =~ s/\>//;
+	    			$to =~ s/^\\\\//;
+	    			$to = "<>" if $to eq "";
+	    			$hash_def{$$log{id}}{to} = lc($to);
+	    			
+	    			my $ctladdr = lc($$log{ctladdr});
+	    			$ctladdr =~ s/^.+(?=\@)(.*)/$1/ if $HIDE_USERS;
+	    			$ctladdr =~ s/\<//;
+	    			$ctladdr =~ s/\>//;
+	    			$ctladdr =~ s/^\\\\//;
+	    			$ctladdr = "<>" if $ctladdr eq "";
+	    			$hash_def{$$log{id}}{ctladdr} = $ctladdr;
+	    			$hash_sts{'Deferred'}{count} +=1;
+	    			
+	    			last CASE;
+	    		};
+	    		$$log{status} =~ /sender notify\: (.*)/i	&& do {
+	    			my $txt = $1;
+	    			last CASE if not exists $hash_def{$$log{id}};
+	    			my $id = $$log{id};
+	    			$hash_def{$id}{notify} = "sender notify:" . $txt;
+	    			$hash_sts{'Sender notify'}{count} +=1;
+	    			
+	    			last CASE;
+	    		};
+	    		$$log{status} =~ /^reject/i	&& do {
+	    			$$log{reject} =~ s/\b[a-z0-9\.\-_\+]+(\@[a-z0-9\.\-_\+]+)\b/$1/gi if $HIDE_USERS;
+	    			$hash_rej{$$log{reject}}{count} +=1;
+	    			$hash_rej{$$log{reject}}{timestamp} = localtime($$log{timestamp});
+	    			$hash_rej{$$log{reject}}{relay} = lc($$log{relay});
+	    			if ( exists $$log{reject} ) { 
+	    				$$log{reject} =~ /(\d{3})\s+(\d\.\d\.\d)\s+(.*)$/;
+	    				my $smtprc = $1;
+	    				my $dsn = $2;
+	    				my $dsn_text = $3;
+	    				
+	    				$dsn_text =~ s/(?:\w*)\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(.*)/$1/g; # dsn
+	    				$dsn_text =~ s/\<//g;
+	    				$dsn_text =~ s/\>//g;
+	    				$dsn_text =~ s/\b[a-z0-9\.\-_\+]+\@[a-z0-9\.\-_\+]+\b//gi;
+						$dsn_text =~ s/^\s+//;			# leading sp
+						$dsn_text =~ s/\s{2,}/ /;		# dbl sp
+						$dsn_text =~ s/\.{2,}//g; 		# some n-dots
+						$dsn_text =~ s/^\s+(.*)/$1/;	# leading sp
+						$dsn_text =~ s/\[\]//g;			# empty []
+						$dsn_text =~ s/(.*\s+)[^A-Za-z0-9]+(\s+.*)/$1$2/;	# alles ausser
+						$dsn_text =~ s/\s{2,}/ /;		# dbl sp
+						#-- suit to std messages
+						$dsn_text =~ s/.*(Domain name required for sender address).*/$1/;
+						$dsn_text =~ s/.*(Domain of sender address\s).*(does not resolve).*/$1$2/;
+						$dsn_text =~ s/.*(Domain of sender address\s).*(does not exist).*/$1$2/;
+						$dsn_text =~ s/.*(Fix reverse DNS Details.*)/$1/;
+						
+						#--
+						
+	    				if ( exists $$log{arg2} ) {
+	    					$$log{arg2} =~ s/\<//;
+	    					$$log{arg2} =~ s/\>//;
+	    					$$log{arg2} = lc($$log{arg2});
+	    					$hash_ssp{$dsn_text}{$$log{arg2}}{count} += 1;
+	    					$hash_ssp{$dsn_text}{count} += 1;
+	    					$hash_ssp{$dsn_text}{dsn} = $dsn;
+	    				} elsif ( exists $$log{arg1} ) {
+	    					$$log{arg1} =~ s/\<//;
+	    					$$log{arg1} =~ s/\>//;
+	    					$$log{arg1} = lc($$log{arg1});
+	    					$hash_ssp{$dsn_text}{$$log{arg1}}{count} += 1;
+	    					$hash_ssp{$dsn_text}{count} += 1;
+	    					$hash_ssp{$dsn_text}{dsn} = $dsn;
+	    				}
+	    				
+	    			}
+	    			$hash_sts{'Reject'}{count} +=1;
+	    			last CASE;
+	    		};
+	    		$$log{status} =~ /^Sent/i && do {
+	    			last CASE if !exists $hash_present{$$log{id}};	# kein from
+	    			$hash_snt{$$log{id}}{count} +=1;
+	    			$hash_snt{$$log{id}}{timestamp} = localtime($$log{timestamp});
+	    			$hash_snt{$$log{id}}{from} = lc($hash_present{$$log{id}}{from});
+	    			$hash_snt{$$log{id}}{size} = $hash_present{$$log{id}}{size};
+	    			$hash_snt{$$log{id}}{in_size} = $hash_present{$$log{id}}{size} if $$log{mailer} =~ /local/i;
+	    			#-- vorab prog-mailer als inbound !!!!!!!!!!
+					$hash_snt{$$log{id}}{in_size} = $hash_present{$$log{id}}{size} if $$log{mailer} =~ /prog/i;
+	    			$hash_snt{$$log{id}}{out_size} = $hash_present{$$log{id}}{size} if $$log{mailer} =~ /esmtp/i;
+	    			$hash_snt{$$log{id}}{relay_size} = $hash_present{$$log{id}}{size} if $$log{mailer} =~ /relay/i;;
+	    			$hash_snt{$$log{id}}{mailer} = $$log{mailer};
+	    			my $to = $$log{to};
+	    			$to =~ s/^.+(?=\@)(.*)/$1/ if $HIDE_USERS;
+	    			$to =~ s/\<//;
+	    			$to =~ s/\>//;
+	    			$to =~ s/^\\\\//;
+	    			$to = "<>" if $to eq "";
+	    			$hash_snt{$$log{id}}{to} = lc($to);
+	    			$hash_sts{'Sent'}{count} +=1;
+	    			last CASE;
+	    		};
+	    		#-- Defualt
+	    		do {
+	    			my $ls = $$log{status};
+	    			$ls =~ s/^[A-Za-z_0-9]+\d{4}:\s//;
+	    			$ls =~ s/\<//g;
+	    			$ls =~ s/\>//g; 
+	    			my ($status) = $ls; # =~ /^(\w+)+\:.*/;
+	    			$status =~ s/\b[a-z0-9\.\-_\+]+(\@[a-z0-9\.\-_\+]+)\b/$1/gi if $HIDE_USERS;
+	    			$hash_sts{$status}{count} +=1;
+	    			last CASE;
+	    		};
+	    	}
+	    } else {
+	    	next if not exists $$log{from} or $$log{from} eq ""; 
+	    	#next if not exists $$log{daemon} or $$log{daemon} !~ /MTA/i;
+	    	my $from = $$log{from};
+	    	$from =~ s/^.+(?=\@)(.*)/$1/ if $HIDE_USERS;
+	    	$from =~ s/\<//;
+	    	$from =~ s/\>//;
+	    	$from = "<>" if $from eq "";
+	    	$hash_present{$$log{id}}{from} = $from;
+	    	if ( $$log{daemon} =~ /MTA/i ) { 
+	    		$hash_present{$$log{id}}{size} = $$log{size};
+	    	}
+	    }
+	     
+	    
+	}
+	close INZ;
+}
+
+my $estim_day = "";
+my $estim_std = "";
+my $estim_min = "";
+$estim_day = qq(<span style="font-size:9px;"> &nbsp;&nbsp;&nbsp;(estimated)</span>) if (($t2 - $t1) < 86400);
+$estim_std = qq(<span style="font-size:9px;"> &nbsp;&nbsp;&nbsp;(estimated)</span>) if (($t2 - $t1) < 3600 );
+$estim_min = qq(<span style="font-size:9px;"> &nbsp;&nbsp;&nbsp;(estimated)</span>) if (($t2 - $t1) < 60   );
+
+my $std_period = round2(($t2 - $t1) / 3600);
+my $day_period = round2($std_period / 24);
+my ($a1,$a2,$a3,$a4,$a5) = (localtime($t1))[3,4,5,2,1];
+my ($e1,$e2,$e3,$e4,$e5) = (localtime($t2))[3,4,5,2,1];
+$a3 += 1900;
+$e3 += 1900;
+$a2 +=1;
+$e2 +=1;
+
+
+
+
+foreach my $key ( sort { $hash_def{$b}{count} <=> $hash_def{$a}{count} || $a cmp $b  }  keys %hash_def) {
+	$msg_total += $hash_def{$key}{count};
+	$msg_deferred += $hash_def{$key}{count};
+	if ( $hash_def{$key}{mailer} =~ /local|esmtp|relay/i ) {
+		$hash_def{$hash_snt{$key}{from}} +=1;
+		$hash_def{$hash_snt{$key}{to}} +=1;
+	}
+	foreach my $key1 ( keys %{ $hash_def{$key} }) {		
+		lprint "Deferred: $key = $key1: $hash_def{$key}{$key1}\n";
+	} lprint "-------\n";
+}
+foreach my $key ( sort { $hash_rej{$b}{count} <=> $hash_rej{$a}{count} || $a cmp $b  } keys %hash_rej) {
+	$msg_total += $hash_rej{$key}{count};
+	$msg_rejected += $hash_rej{$key}{count};
+	foreach my $key1 (keys %{ $hash_rej{$key} }) {
+		lprint "Rejected: $key = $key1: $hash_rej{$key}{$key1}\n";
+	} lprint "-------\n";
+}
+foreach my $key ( sort { $hash_snt{$b}{count} <=> $hash_snt{$a}{count} || $a cmp $b  } keys %hash_snt) {
+	$msg_total += $hash_snt{$key}{count};
+	$msg_size += $hash_snt{$key}{size};
+	$msg_in_size += $hash_snt{$key}{in_size};
+	$msg_out_size += $hash_snt{$key}{out_size};
+	$msg_relay_size += $hash_snt{$key}{relay_size};
+	$msg_inbound  += $hash_snt{$key}{count} if $hash_snt{$key}{mailer} =~ /local/i;
+	$msg_outbound  += $hash_snt{$key}{count} if $hash_snt{$key}{mailer} =~ /esmtp/i;
+	$msg_relay  += $hash_snt{$key}{count} if $hash_snt{$key}{mailer} =~ /relay/i;
+	if ( $hash_snt{$key}{mailer} =~ /local|esmtp|relay/i ) {
+		$top_sender{$hash_snt{$key}{from}} +=1;
+		$top_recv{$hash_snt{$key}{to}} +=1;
+		$top_rsize{$hash_snt{$key}{to}} += int($hash_snt{$key}{size}/1024);
+		$top_ssize{$hash_snt{$key}{from}} += int($hash_snt{$key}{size}/1024);
+	}
+	foreach my $key1 (keys %{ $hash_snt{$key} }) {
+		lprint "Sent: $key = $key1: $hash_snt{$key}{$key1}\n";
+	}lprint "-------\n";
+}
+
+$msg_size = int($msg_size/1024);
+$msg_in_size = int($msg_in_size/1024);
+$msg_out_size = int($msg_out_size/1024);
+$msg_relay_size = int($msg_relay_size/1024);
+
+
+
+
+#-- CGI Part -------------------------
+
+#-- Locale Settings
+my $LOCALE	= "de_DE.ISO8859-1";
+setlocale(LC_CTYPE, "$LOCALE");
+
+my $Q = new CGI;
+
+&menu1;
+
+exit;
+
+{
+end_script:
+print $OUT $Q->end_html; 
+exit;
+}
+
+#-- SUBS
+
+sub lprint {
+	$DEBUG && print $OUT shift() . "<br>";
+}
+
+sub menu1 {
+	
+	#-- Styles
+	my $STYLE_1		=<<"EOF_STYLE_1";
+A{
+	color: red; 
+	font-family: Avantgarde, Verdana, Arial;
+	font-variant: small-caps;
+	letter-spacing: 0.2em;
+	font-style: normal;
+	font-weight: bold; 
+	text-decoration: none;
+}
+HTML  { 
+	font-size: 10px; 
+	color: #F3F0E0; 
+	font-family: Verdana, Arial, Helvetica, sans-serif; 
+	font-style: normal; 
+	font-variant: normal; 
+	text-decoration: none; 
+	background-color: #707070;
+}
+.hr {
+	color:#F3F0E0; 
+	background-color:;
+	border:1px solid #F3F0E0;
+	height: 0pt;
+}
+.m0 {
+	border-width: 1px;
+	border-style: solid;
+	border-color: #F3F0E0;
+	border-spacing: 2px;
+	empty-cells:hide;
+	padding: 0px;
+	white-space:pre;
+	font: bold 14px Verdana;
+	color: #F3F0E0;
+	margin: 0px;
+	background-color: #808080;
+}
+.m0 table {
+	border-width: 1px;
+	border-style: solid;
+	border-color: #F3F0E0;
+	border-spacing: 1px;
+	empty-cells:hide;
+	padding: 0px;
+	white-space:pre;
+	font: bold 14px Verdana;
+	color: #F3F0E0;
+	margin: 0px;
+	background-color: #808080;
+}
+.m0 td {
+	border-width: 0px;
+	border-style: solid;
+	border-color: #F3F0E0;
+	border-spacing: 0px;
+	padding: 3px;
+	white-space:pre;
+	font: bold 14px Verdana;
+	color: #F3F0E0;
+	margin: 0px;
+	background-color: #404040;
+}
+.m0 th {
+	border-spacing: 1px;
+	font: normal 16px Avantgarde, Verdana, Arial;
+	font-variant:small-caps;
+	letter-spacing:0.2em;
+	padding: 5px;
+	white-space:pre;
+	color: #F3F0E0;
+	margin: 0px;
+	background-color: #808080;
+}
+.m0 tr {
+	border: 0px;
+	margin: 0px;
+	padding: 0px;
+	background-color: #808080;
+}
+
+.m1 {
+	border-width: 1px;
+	border-style: solid;
+	border-color: #F3F0E0;
+	border-spacing: 2px;
+	empty-cells:hide;
+	padding: 1px;
+	white-space:pre;
+	font: bold 12px Verdana;
+	color: #F3F0E0;
+	margin: 0px;
+	background-color: #808080;
+}
+.m1 table {
+	border-width: 1px;
+	border-style: solid;
+	border-color: #F3F0E0;
+	border-spacing: 1px;
+	empty-cells:hide;
+	padding: 1px;
+	white-space:pre;
+	font: bold 12px Verdana;
+	color: #F3F0E0;
+	margin: 0px;
+	background-color: #808080;
+}
+.m1 td {
+	border-width: 0px;
+	border-style: solid;
+	border-color: #F3F0E0;
+	border-spacing: 0px;
+	padding: 3px;
+	white-space:pre;
+	font: bold 12px Verdana;
+	color: #F3F0E0;
+	margin: 0px;
+	background-color: #404040;
+}
+.m1 th {
+	border-spacing: 1px;
+	padding: 5px;
+	white-space:pre;
+	font: normal 16px Avantgarde, Verdana, Arial;
+	font-variant:small-caps;
+	letter-spacing:0.2em;
+	color: #F3F0E0;
+	margin: 0px;
+	background-color: #808080;
+}
+.m1 tr {
+	border: 0px;
+	margin: 0px;
+	padding: 0px;
+	background-color: #808080;
+}
+.trline {
+	border: 0px;
+	margin: 0px;
+	padding: 0px;
+	background-color: #808080;
+	height: 2px;
+	font-size: 2px;
+}
+#graph1 {
+	background-color: #404040;
+	height: 100%;
+	border: 0px;
+	table-layout:auto;
+	padding: 0px;
+	margin: 3px;
+	border-collapse:collapse;
+	vertical-align:middle;
+}
+#graph1 tr {
+	background-color: #404040;
+	height: 50px;
+	border: 0px;
+	padding: 0px;
+}
+#graph1 td {
+	border: 0px;
+	margin: px;
+	padding: 0px;
+	text-align: left;
+}
+#graph1 .go {
+	border-width: 1px 1px 0px 1px;
+	border-style: solid;
+	border-color: black;
+	padding: 2px;
+	padding-left: 0px;
+}
+#graph1 .gm {
+	border-width: 0px 1px 0px 1px;
+	border-style: solid;
+	border-color: black;
+	padding: 2px;
+	padding-left: 0px;
+}
+#graph1 .gu {
+	border-width: 0px 1px 1px 1px;
+	border-style: solid;
+	border-color: black;
+	padding: 2px; 
+	padding-left: 0px;
+}
+div.pc {
+	border: 0px; 
+	width: 100%; 
+	margin: 0px; 
+	padding: 0px; 
+	float: left; 
+	background: transparent;
+	vertical-align: middle;
+}
+div.pc > div {
+	background-color: red; 
+	height: 20px;
+	vertical-align: middle;
+	text-align: right;
+	border-width: 1px 1px 1px 1px;
+	border-style: solid;
+	border-color: black;
+}
+EOF_STYLE_1
+
+	my $p_title = "Yasma - Sendmail Logfile Analyzer";
+	
+	#print $OUT $Q->header( -type => 'text/html' );
+	
+	print $OUT $Q->start_html(	-title => "$p_title",
+				-head => [	meta(	{ 	-http_equiv => 'Content-Type',
+										-content => 'text/html; charset=iso-8859-1'}),
+							meta(	{	-http_equiv => 'content-language',
+										-content => 'de'}) ],
+				-author => "sigi-at-ZweierNet-dot-ch",
+				-style => {-code => "$STYLE_1"} );
+	
+	print $OUT qq(<p><br><font style="font-family: Avantgarde, Verdana, Arial; font-size: 380%;font-variant:small-caps;font-weight:500;letter-spacing:0.2em;">Yasma - </font><font style="font-family: Avantgarde, Verdana, Arial; font-size: 220%;font-variant:small-caps;letter-spacing:0.2em;"> Yet Another Sendmail Logfile Analyzer</font><b>);
+	print $OUT qq(<p style="text-align:right;font-family: Avantgarde, Verdana, Arial; font-size: 100%;font-variant:small-caps;letter-spacing:0.3em;font-weight:500;"> Free <a href="http://pss.zweiernet.ch/index.html?f_down.html">Yasma</a> $VERSION Designed by <a href="http://PSS.ZweierNet.ch">PSS</a></p>);
+	print $OUT qq(<br><p style="font-family: Verdana, Arial; font-size: 150%;font-variant:small-caps;letter-spacing:0.1em;">Report Generated at );
+	print $OUT scalar localtime();
+	print $OUT qq(</p><p><br><p>);
+	
+	&print_totals;
+		
+	&print_avg;
+	
+	&print_tops;
+
+	print $OUT qq(<p><br><p style="text-align:right;font-family: Avantgarde, Verdana, Arial; font-size: 100%;font-variant:small-caps;letter-spacing:0.3em;font-weight:500;"> Free <a href="http://pss.zweiernet.ch/index.html?f_down.html">Yasma</a> $VERSION Designed by <a href="http://PSS.ZweierNet.ch">PSS</a></p><br>);
+	goto end_script;
+}
+
+
+
+sub print_totals {
+	
+	print $OUT qq(<table class="m1" width=90%>);
+	print $OUT qq(<tr><th colspan=3>Overview of Period <b>);
+	printf("%d.%d.%d %02d:%02d - %d.%d.%d %02d:%02d", $a1,$a2,$a3,$a4,$a5, $e1,$e2,$e3,$e4,$e5);
+	$std_period > 24 ?  print $OUT qq!</b> ($day_period days)</th></tr>!
+					 :  print $OUT qq!</b> ($std_period hours)</th></tr>!;
+	print $OUT qq(<tr><td style="border-bottom-width:2px;border-bottom-color: #808080;">Overall Processed Messages</td><td style="border-bottom-width:2px;border-bottom-color: #808080;">$msg_total</td><td width=30% rowspan=12 align=right>);
+	&make_chart;
+	print $OUT qq(</td></tr>);
+	print $OUT qq(<tr><td>Total Delivered Messages</td><td>);
+	print $OUT $msg_outbound + $msg_inbound;
+	print $OUT qq(</td></tr>);
+	print $OUT qq(<tr><td>&nbsp;&nbsp;&nbsp;&nbsp; Outbound</td><td>$msg_outbound</td></tr>);
+	print $OUT qq(<tr><td>&nbsp;&nbsp;&nbsp;&nbsp; Inbound</td><td>$msg_inbound</td></tr>);
+	print $OUT qq(<tr><td style="border-bottom-width:2px;border-bottom-color: #808080;">&nbsp;&nbsp;&nbsp;&nbsp; Relay (of it)</td><td style="border-bottom-width:2px;border-bottom-color: #808080;">$msg_relay</td></tr>);
+	print $OUT qq(<tr><td>Total Waste Messages</td><td>);
+	print $OUT ($msg_rejected + $msg_deferred);
+	print $OUT qq(</td></tr>);
+	print $OUT qq(<tr><td>&nbsp;&nbsp;&nbsp;&nbsp; Deferred Messages</td><td>$msg_deferred</td></tr>);
+	print $OUT qq(<tr><td style="border-bottom-width:2px;border-bottom-color: #808080;">&nbsp;&nbsp;&nbsp;&nbsp; Rejected Messages</td><td style="border-bottom-width:2px;border-bottom-color: #808080;">$msg_rejected</td></tr>);
+	print $OUT qq(<tr><td style="border-bottom-width:2px;border-bottom-color: #808080;">Ratio Delivered : Waste Messages<div class="font-size:100%;"></td><td style="border-bottom-width:2px;border-bottom-color: #808080;">);
+	print $OUT round(eval { (($msg_outbound + $msg_inbound) * 100) / $msg_total } or 0);
+	print $OUT qq( % : );
+	print $OUT round(eval { (($msg_rejected + $msg_deferred) * 100) / $msg_total } or 0);
+	print $OUT qq( %</td></tr>);
+	print $OUT qq(<tr><td>Total Size of Delivered Messages</td><td>);
+	print $OUT fmt_kb($msg_size);
+	print $OUT qq( Kb</td></tr>);
+	print $OUT qq(<tr><td>&nbsp;&nbsp;&nbsp;&nbsp; Outbound</td><td>);
+	print $OUT fmt_kb($msg_out_size);
+	print $OUT qq( Kb</td></tr>);
+	print $OUT qq(<tr><td>&nbsp;&nbsp;&nbsp;&nbsp; Inbound</td><td>);
+	print $OUT fmt_kb($msg_in_size);
+	print $OUT qq( Kb</td></tr>);
+	
+	print $OUT qq(</table>);
+}
+
+sub print_tops {
+	
+	print $OUT qq(<p><br><p><br>);
+	print $OUT qq(<table class="m1" width=60%>);
+	print $OUT qq(<tr><th colspan=2>Top Envelope Sender</th></tr>);
+	foreach  my $adr( sort { $top_sender{$b} <=> $top_sender{$a} || $a cmp $b  } keys %top_sender ) {
+		print $OUT qq(<tr><td>$top_sender{$adr}</td><td width=85%>$adr</td></tr>);
+		last if $max_top_snt-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	print $OUT qq(<p><br><p>);
+	print $OUT qq(<table class="m1" width=60%>);
+	print $OUT qq(<tr><th colspan=2>Top Envelope Recipients</th></tr>);
+	foreach  my $adr( sort { $top_recv{$b} <=> $top_recv{$a} || $a cmp $b } keys %top_recv ) {
+		print $OUT qq(<tr><td>$top_recv{$adr}</td><td width=85%>$adr</td></tr>);
+		last if $max_top_rcv-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	print $OUT qq(<p><br><p>);
+	print $OUT qq(<table class="m1" width=60%>);
+	print $OUT qq(<tr><th colspan=2>Top Senders Message Size (in Kbyte)</th></tr>);
+	foreach  my $adr( sort { $top_ssize{$b} <=> $top_ssize{$a} || $a cmp $b  } keys %top_ssize ) {
+		print $OUT qq(<tr><td>);
+		print $OUT fmt_kb($top_ssize{$adr});
+		print $OUT qq(</td><td width=85%>$adr</td></tr>);
+		last if $max_top_siz-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	print $OUT qq(<p><br><p><br>);
+	print $OUT qq(<table class="m1" width=60%>);
+	print $OUT qq(<tr><th colspan=2>Top Recipients Message Size (in Kbyte)</th></tr>);
+	foreach  my $adr( sort { $top_rsize{$b} <=> $top_rsize{$a} || $a cmp $b  } keys %top_rsize ) {
+		print $OUT qq(<tr><td>);
+		print $OUT fmt_kb($top_rsize{$adr});
+		print $OUT qq(</td><td width=85%>$adr</td></tr>);
+		last if $max_top_riz-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	print $OUT qq(<p><br><p><br>);
+	print $OUT qq(<table class="m1" width=80%>);
+	print $OUT qq(<tr><th colspan=2>Top Deferrer</th></tr>);
+	foreach my $key ( sort { $hash_def{$b}{count} <=> $hash_def{$a}{count} || $a cmp $b  } keys %hash_def) {
+		if ( exists $hash_def{$key}{notify} ) {
+			print $OUT qq(<tr><td>$hash_def{$key}{count}<br> <br> </td><td width=85%>$hash_def{$key}{ctladdr}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;To: $hash_def{$key}{to}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Reason: $hash_def{$key}{status}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;==> Solved with: $hash_def{$key}{notify}</td></tr>);
+		} else {
+			print $OUT qq(<tr><td>$hash_def{$key}{count}<br> <br> </td><td width=85%>$hash_def{$key}{ctladdr}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;To: $hash_def{$key}{to}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Reason: $hash_def{$key}{status}</td></tr>);
+		}
+		last if $max_top_def-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	print $OUT qq(<p><br><p>);
+	print $OUT qq(<table class="m1" width=80%>);
+	print $OUT qq(<tr><th colspan=2>Top Rejects</th></tr>);
+	foreach my $key ( sort { $hash_rej{$b}{count} <=> $hash_rej{$a}{count} || $a cmp $b  } keys %hash_rej) {
+		print $OUT qq(<tr><td width=15%>$hash_rej{$key}{count}<br> </td><td>$key<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Relay: $hash_rej{$key}{relay}</td></tr>);
+		last if $max_top_rej-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	my $GI;
+	$geoip_dbase && ( $GI = Geo::IP::PurePerl->open($geoip_dbase) );
+	print $OUT qq(<p><br><p>);
+	print $OUT qq(<table class="m1" width=80%>);
+	print $OUT qq(<tr><th colspan=4>Top DSN Messages and Supposed Spammers/Abused Addresses</th></tr>);
+	my $stmp = $max_top_ssp;
+	foreach my $key1 ( sort {  $hash_ssp{$b}{count} <=> $hash_ssp{$a}{count} || $a cmp $b } keys %hash_ssp ) {
+		my $scnt = $stmp;
+		my $fk = "($hash_ssp{$key1}{dsn}) $key1"; 
+		my $fkc = $hash_ssp{$key1}{count};
+		my %tmph = ();
+		foreach my $x ( keys %{ $hash_ssp{$key1} } ) {
+			next if $x eq "count" or $x eq "dsn";
+			$tmph{$x} = "$hash_ssp{$key1}{$x}{count}";
+		}
+		foreach my $key (  sort { $tmph{$b} <=> $tmph{$a} || $a cmp $b } keys %tmph ) {
+			my $land = "";
+			if ($key =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ ) { $geoip_dbase && ($land = $GI->country_code_by_addr($key)); }
+			my $keyl = $key;
+			if ($keyl =~ s/.+\@(.*)/$1/ ) { $geoip_dbase && ($land = $GI->country_code_by_name($keyl)); }
+			my $keyu = $key;
+			$keyu =~ s/\b[a-z0-9\.\-_\+]+(\@[a-z0-9\.\-_\+]+)\b/$1/gi if $HIDE_USERS;			
+			print $OUT qq(<tr><td>$fkc</td><td width=30%>$fk</td><td align=left>$tmph{$key} </td><td>$keyu &nbsp;&nbsp; );
+			$geoip_dbase && (print $OUT qq([$land]));
+			print $OUT qq(</td></tr>);
+			$fk = " ";
+			$fkc = " ";
+			last if $scnt-- == 1;
+		}
+		last if $max_top_ssp-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	print $OUT qq(<p><br><p>);
+	print $OUT qq(<table class="m1" width=60%>);
+	print $OUT qq(<tr><th colspan=2>Top Status Messages</th></tr>);
+	foreach  my $key( sort { $hash_sts{$b}{count} <=> $hash_sts{$a}{count} || $a cmp $b  } keys %hash_sts ) {
+		print $OUT qq(<tr><td>$hash_sts{$key}{count}</td><td width=85%>$key</td></tr>);
+		last if $max_top_sts-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	print $OUT qq(<p><br><p>);
+	print $OUT qq(<table class="m1" width=60%>);
+	print $OUT qq(<tr><th colspan=2>Top Relay Hosts</th></tr>);
+	foreach  my $key( sort { $hash_rel{$b}{count} <=> $hash_rel{$a}{count} || $a cmp $b  } keys %hash_rel ) {
+		print $OUT qq(<tr><td>$hash_rel{$key}{count}</td><td width=85%>$key</td></tr>);
+		last if $max_top_rel-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	print $OUT qq(<p><br><p>);
+	print $OUT qq(<table class="m1" width=60%>);
+	print $OUT qq(<tr><th colspan=2>Top Mailer</th></tr>);
+	foreach  my $key( sort { $hash_mlr{$b}{count} <=> $hash_mlr{$a}{count} || $a cmp $b  } keys %hash_mlr ) {
+		print $OUT qq(<tr><td>$hash_mlr{$key}{count}</td><td width=85%>$key</td></tr>);
+		last if $max_top_mlr-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	print $OUT qq(<p><br><p>);
+	print $OUT qq(<table class="m1" width=60%>);
+	print $OUT qq(<tr><th colspan=2>Top Mailer Hosts Processed</th></tr>);
+	foreach  my $key( sort { $hash_hst{$b}{count} <=> $hash_hst{$a}{count} || $a cmp $b  } keys %hash_hst ) {
+		print $OUT qq(<tr><td>$hash_hst{$key}{count}</td><td width=85%>$key</td></tr>);
+		last if $max_top_hst-- == 1;
+	}
+	print $OUT qq(</table>);
+	
+	
+	
+}
+
+sub print_avg {
+	
+	print $OUT qq(<p><br><p>);
+	print $OUT qq(<table class="m1" width=90%>);
+	print $OUT qq(<tr><th colspan=4>Averages of Period <b>);
+	printf("%d.%d.%d %02d:%02d - %d.%d.%d %02d:%02d", $a1,$a2,$a3,$a4,$a5, $e1,$e2,$e3,$e4,$e5);
+	$std_period > 24 ?  print $OUT qq!</b> ($day_period days)</th></tr>!
+					 :  print $OUT qq!</b> ($std_period hours)</th></tr>!;
+	
+	print $OUT qq(<tr><td>Overall Processed/Day $estim_day</td><td style="border-right-width:2px;border-right-color: #808080;">);
+	print $OUT round2(eval { $msg_total / ($std_period / 24) } or 0 );
+	print $OUT qq(</td>);
+	print $OUT qq(<td>Total Delivered/Day $estim_day</td><td>);
+	print $OUT round2(eval {($msg_outbound + $msg_inbound) / ($std_period / 24) } or 0);
+	print $OUT qq(</td></tr>);
+	
+	print $OUT qq(<tr><td>Overall Processed/Hour $estim_std</td><td style="border-right-width:2px;border-right-color: #808080;">);
+	print $OUT round2(eval { $msg_total / $std_period  } or 0);
+	print $OUT qq(</td>);
+	print $OUT qq(<td>Total Delivered/Hour $estim_std</td><td>);
+	print $OUT round2(eval {($msg_outbound + $msg_inbound) / $std_period } or 0);
+	print $OUT qq(</td></tr>);
+	
+	print $OUT qq(<tr><td style="border-bottom-width:2px;border-bottom-color: #808080;">Overall Processed/Min $estim_min</td><td style="border-bottom-width:2px;border-bottom-color: #808080;border-right-width:2px;border-right-color: #808080;">);
+	print $OUT round2(eval { $msg_total / ($std_period * 60) } or 0 );
+	print $OUT qq(</td>);
+	print $OUT qq(<td style="border-bottom-width:2px;border-bottom-color: #808080;">Total Delivered/Min $estim_min</td><td style="border-bottom-width:2px;border-bottom-color: #808080;">);
+	print $OUT round2(eval {($msg_outbound + $msg_inbound) / ($std_period * 60) } or 0);
+	print $OUT qq(</td></tr>);
+	
+	print $OUT qq(<tr><td>Inbound Delivered/Day $estim_day</td><td style="border-right-width:2px;border-right-color: #808080;">);
+	print $OUT round2(eval {$msg_inbound / ($std_period /24) } or 0 );
+	print $OUT qq(</td>);
+	print $OUT qq(<td>Outbound Delivered/Day $estim_day</td><td>);
+	print $OUT round2(eval {$msg_outbound / ($std_period /24) } or 0);
+	print $OUT qq(</td></tr>);
+	
+	print $OUT qq(<tr><td>Inbound Delivered/Hour $estim_std</td><td style="border-right-width:2px;border-right-color: #808080;">);
+	print $OUT round2(eval {$msg_inbound / $std_period } or 0 );
+	print $OUT qq(</td>);
+	print $OUT qq(<td>Outbound Delivered/Hour $estim_std</td><td>);
+	print $OUT round2(eval {$msg_outbound / $std_period  } or 0);
+	print $OUT qq(</td></tr>);
+	
+	print $OUT qq(<tr><td style="border-bottom-width:2px;border-bottom-color: #808080;">Inbound Delivered/Min $estim_min</td><td style="border-bottom-width:2px;border-bottom-color: #808080;border-right-width:2px;border-right-color: #808080;">);
+	print $OUT round2(eval {$msg_inbound / ($std_period * 60) } or 0 );
+	print $OUT qq(</td>);
+	print $OUT qq(<td style="border-bottom-width:2px;border-bottom-color: #808080;">Outbound Delivered/Min $estim_min</td><td style="border-bottom-width:2px;border-bottom-color: #808080;">);
+	print $OUT round2(eval {$msg_outbound / ($std_period * 60) } or 0);
+	print $OUT qq(</td></tr>);
+	
+	print $OUT qq(<tr><td>Rejected Messages/Day $estim_day</td><td style="border-right-width:2px;border-right-color: #808080;">);
+	print $OUT round2(eval {$msg_rejected / ($std_period /24) } or 0 );
+	print $OUT qq(</td>);
+	print $OUT qq(<td>Deferred Messages/Day $estim_day</td><td>);
+	print $OUT round2(eval {$msg_deferred / ($std_period /24) } or 0);
+	print $OUT qq(</td></tr>);
+	
+	print $OUT qq(<tr><td>Rejected Messages/Hour $estim_std</td><td style="border-right-width:2px;border-right-color: #808080;">);
+	print $OUT round2(eval {$msg_rejected / $std_period  } or 0);
+	print $OUT qq(</td>);
+	print $OUT qq(<td>Deferred Messages/Hour $estim_std</td><td>);
+	print $OUT round2(eval {$msg_deferred / $std_period } or 0);
+	print $OUT qq(</td></tr>);
+	
+	print $OUT qq(<tr><td style="border-bottom-width:2px;border-bottom-color: #808080;">Rejected Messages/Min $estim_min</td><td style="border-bottom-width:2px;border-bottom-color: #808080;border-right-width:2px;border-right-color: #808080;">);
+	print $OUT round2(eval {$msg_rejected / ($std_period * 60) } or 0 );
+	print $OUT qq(</td>);
+	print $OUT qq(<td style="border-bottom-width:2px;border-bottom-color: #808080;">Deferred Messages/Min $estim_min</td><td style="border-bottom-width:2px;border-bottom-color: #808080;">);
+	print $OUT round2(eval {$msg_deferred / ($std_period * 60) } or 0);
+	print $OUT qq(</td></tr>);
+	
+	
+	print $OUT qq(<tr><td>Average Size of Delivered Messages</td><td style="border-right-width:2px;border-right-color: #808080;">);
+	print $OUT fmt_kb(round(eval {$msg_size / ($msg_outbound + $msg_inbound) } or 0));
+	print $OUT qq( Kb</td>);
+	print $OUT qq(<td> </td><td>);
+	#print $OUT round2($msg_deferred / ($std_period /24));
+	print $OUT qq( </td></tr>);
+	
+	print $OUT qq(<tr><td>Average Size of Inbound Messages</td><td style="border-right-width:2px;border-right-color: #808080;">);
+	print $OUT fmt_kb(round(eval {$msg_in_size / $msg_inbound } or 0));
+	print $OUT qq( Kb</td>);
+	print $OUT qq(<td>Average Size of Outbound Messages</td><td>);
+	print $OUT fmt_kb(round(eval {$msg_out_size / $msg_outbound } or 0));
+	print $OUT qq( Kb</td></tr>);
+	
+	
+	print $OUT qq(</table>);
+}
+
+
+sub make_chart {
+	# 400x250
+	my $max = 0;
+	$msg_deferred > $max && ($max = $msg_deferred);
+	$msg_rejected > $max && ($max = $msg_rejected);
+	$msg_inbound > $max  && ($max = $msg_inbound);
+	$msg_outbound > $max && ($max = $msg_outbound);
+	my $wde = round(eval { ($msg_deferred * 100) / $max } or 0);
+	my $wre = round(eval { ($msg_rejected * 100) / $max } or 0);
+	my $wib = round(eval { ($msg_inbound * 100) / $max } or 0);
+	my $wob = round(eval { ($msg_outbound * 100) / $max } or 0);
+	      
+	print $OUT <<"EOFHTML";
+<table id="graph1" border=0 width="400" height="250" cellspacing="0" cellpadding="0">
+<tr>
+  <td width="60px">Deferred </td>
+  <td class="go"><div class="pc" style="width: 100%"><div style="width: ${wde}%"> $msg_deferred </div></div></td>
+</tr>
+<tr>
+  <td width="60px">Rejected </td>
+  <td class="gm"><div class="pc" style="width: 100%"><div style="width: ${wre}%"> $msg_rejected </div></div></td>
+</tr>
+<tr>
+  <td width="60px">Inbound  </td>
+  <td class="gm"><div class="pc" style="width: 100%"><div style="width: ${wib}%"> $msg_inbound </div></div></td>
+</tr>
+<tr>
+  <td width="60px">Outbound </td>
+  <td class="gu"><div class="pc" style="width: 100%"><div style="width: ${wob}%"> $msg_outbound </div></div></td>
+</tr>
+</table>
+EOFHTML
+
+	return;
+}
+
+
+sub round {
+	return int( shift() + .5);
+}
+sub round2 {
+	return int( shift() * 100 + .5) / 100;
+}
+sub fmt_kb {
+	my $x = shift;
+	$x =~ s/(\d{1,3})(?=(\d{3})+$)/$1'/g;
+	return $x;
+}
+
+sub read_config {
+	open( CONF, "$conf_file" ) or die "Configfile '$conf_file' Error: $!\n";
+	while ( my $line = <CONF> ) {
+		next if $line =~ /^(#|\s+)/;
+		CS:	{
+			$line =~ /^HIDE_USERS\s*=\s*(.*)/i 		&& do {
+				$HIDE_USERS = TRUE if $1 =~ m/true/i;
+				last CS;
+			};
+			$line =~ /^DEBUG\s*=\s*(.*)/i 			&& do {
+				$DEBUG = TRUE if $1 =~ m/true/i;
+				last CS;
+			};
+			$line =~ /^out_file\s*=\s*(.*)/i 		&& do {
+				$out_file = $1 if $1;
+				last CS;
+			};
+			$line =~ /^geoip_dbase\s*=\s*(.*)/i 	&& do {
+				$geoip_dbase = $1 if $1;
+				last CS;
+			};
+			$line =~ /^max_top_snt\s*=\s*(.*)/i 	&& do {
+				$max_top_snt = $1;
+				last CS;
+			};
+			$line =~ /^max_top_rcv\s*=\s*(.*)/i 	&& do {
+				$max_top_rcv = $1;
+				last CS;
+			};
+			$line =~ /^max_top_siz\s*=\s*(.*)/i 	&& do {
+				$max_top_siz = $1;
+				last CS;
+			};
+			$line =~ /^max_top_riz\s*=\s*(.*)/i 	&& do {
+				$max_top_riz = $1;
+				last CS;
+			};
+			$line =~ /^max_top_def\s*=\s*(.*)/i 	&& do {
+				$max_top_def = $1;
+				last CS;
+			};
+			$line =~ /^max_top_rej\s*=\s*(.*)/i 	&& do {
+				$max_top_rej = $1;
+				last CS;
+			};
+			$line =~ /^max_top_ssp\s*=\s*(.*)/i 	&& do {
+				$max_top_ssp = $1;
+				last CS;
+			};
+			$line =~ /^max_top_sts\s*=\s*(.*)/i 	&& do {
+				$max_top_sts = $1;
+				last CS;
+			};
+			$line =~ /^max_top_rel\s*=\s*(.*)/i 	&& do {
+				$max_top_rel = $1;
+				last CS;
+			};
+			$line =~ /^max_top_mlr\s*=\s*(.*)/i 	&& do {
+				$max_top_mlr = $1;
+				last CS;
+			};
+			$line =~ /^max_top_hst\s*=\s*(.*)/i 	&& do {
+				$max_top_hst = $1;
+				last CS;
+			};
+		}
+	}
+	close CONF;
+}