Mailserver - Logfileauswertungen unter CentOS 7.x

Oft werden vom Management hübsche bunte Auswertungsgraphiken gewünscht, um sich so mehr oder weniger ein Bild davon zu machen, ob und wie der/die Mailserver mit der anfallenden Menge an elektronischer Post umgehen können. Auch als Postmaster und Admin können wir uns so einen kurzen Überblick verschaffen, ob alles im grünen Bereich ist, ohne dazu extra langwierig im Logfile unseres Mailservers herum zu suchen. Hierzu stehen uns mehrere Hilfsprogramme zur Verfügung.

Im Detail wollen wir uns nun drei der Varianten genauer ansehen.

  1. pflogsumm Logfilezusammenfassung generieren und via eMail dem postmaster zur Verfügung stellen.
  2. WEB-GUIs Graphisch sehr ansprechende Logfile Zusammenfassung generieren und zum Abrufen via Browser anbieten
  3. AWStats Logfilezusammenfassung generieren und zum Abrufen via Browser anbieten

pflogsumm

Mit Hilfe dieses kleinen Perlscriptes können wir uns einen täglichen Statusbericht unseres Mailservers erstellen lassen und wissen so das was unserem MX widerfahren ist. :-) Das Perlscript pflogsumm wertet hierzu das Logfile /var/log/maillog unseres Mailservers aus.

Installation

Das Perlscript pflogsumm befindet sich unter CentOS 7.x im Paket postfix-perl-scripts. Die Installation dieses Paketes gestaltet sich im gewohnten Maße sehr einfach mit Hilfe von YUM. Falls das Paket noch nicht installiert wurde, holen wir einfach diesen Schritt kurzer Hand nach.

# yum install postfix-perl-scripts -y

Was uns bei der Installation dieses Paketes alles mitgebracht wurde, zeigt uns folgender Aufruf.

# rpm -qil postfix-perl-scripts
Name        : postfix-perl-scripts
Epoch       : 2
Version     : 2.11.3
Release     : 1.el7.centos
Architecture: x86_64
Install Date: Thu 30 Oct 2014 01:35:54 PM CET
Group       : Applications/System
Size        : 111466
License     : IBM and GPLv2+
Signature   : RSA/SHA1, Thu 30 Oct 2014 01:23:00 PM CET, Key ID 60ecfb9e8195aea0
Source RPM  : postfix-2.11.3-1.el7.centos.src.rpm
Build Date  : Thu 30 Oct 2014 12:57:01 PM CET
Build Host  : vml000200.dmz.nausch.org
Relocations : (not relocatable)
Packager    : Django <django@mailserver.guru>
Vendor      : Django
URL         : http://www.postfix.org
Summary     : Postfix utilities written in perl
Description :
This package contains perl scripts pflogsumm and qshape.

Pflogsumm is a log analyzer/summarizer for the Postfix MTA. It is
designed to provide an over-view of Postfix activity. Pflogsumm
generates summaries and, in some cases, detailed reports of mail
server traffic volumes, rejected and bounced email, and server
warnings, errors and panics.

qshape prints Postfix queue domain and age distribution.
/usr/sbin/pflogsumm
/usr/sbin/qshape
/usr/share/doc/postfix-2.11.3/pflogsumm-faq.txt
/usr/share/man/man1/pflogsumm.1.gz
/usr/share/man/man1/qshape.1.gz

Optionen beim Programmaufruf

Hinweise zur Konfiguration oder besser gesagt über die Optionen beim Aufruf des Programms zeigt ein Blick in die Manpage von pflogsumm.

 # man pflogsumm
PFLOGSUMM(1)                User Contributed Perl Documentation                PFLOGSUMM(1)

NAME
       pflogsumm.pl - Produce Postfix MTA logfile summary

       Copyright (C) 1998-2010 by James S. Seymour, Release 1.1.3.

SYNOPSIS
           pflogsumm.pl -[eq] [-d <today|yesterday>] [--detail <cnt>]
               [--bounce_detail <cnt>] [--deferral_detail <cnt>]
               [-h <cnt>] [-i|--ignore_case] [--iso_date_time] [--mailq]
               [-m|--uucp_mung] [--no_bounce_detail] [--no_deferral_detail]
               [--no_no_msg_size] [--no_reject_detail] [--no_smtpd_warnings]
               [--problems_first] [--rej_add_from] [--reject_detail <cnt>]
               [--smtp_detail <cnt>] [--smtpd_stats]
               [--smtpd_warning_detail <cnt>] [--syslog_name=string]
               [-u <cnt>] [--verbose_msg_detail] [--verp_mung[=<n>]]
               [--zero_fill] [file1 [filen]]

           pflogsumm.pl -[help|version]

           If no file(s) specified, reads from stdin.  Output is to stdout.

DESCRIPTION
           Pflogsumm is a log analyzer/summarizer for the Postfix MTA.  It is
           designed to provide an over-view of Postfix activity, with just enough
           detail to give the administrator a "heads up" for potential trouble
           spots.

           Pflogsumm generates summaries and, in some cases, detailed reports of
           mail server traffic volumes, rejected and bounced email, and server
           warnings, errors and panics.

OPTIONS
           --bounce_detail <cnt>

                          Limit detailed bounce reports to the top <cnt>.  0
                          to suppress entirely.

           -d today       generate report for just today
           -d yesterday   generate report for just "yesterday"

           --deferral_detail <cnt>

                          Limit detailed deferral reports to the top <cnt>.  0
                          to suppress entirely.

           --detail <cnt>

                          Sets all --*_detail, -h and -u to <cnt>.  Is
                          over-ridden by individual settings.  --detail 0
                          suppresses *all* detail.

           -e             extended (extreme? excessive?) detail

                          Emit detailed reports.  At present, this includes
                          only a per-message report, sorted by sender domain,
                          then user-in-domain, then by queue i.d.

                          WARNING: the data built to generate this report can
                          quickly consume very large amounts of memory if a
                          lot of log entries are processed!

           -h <cnt>       top <cnt> to display in host/domain reports.

                          0 = none.

                          See also: "-u" and "--*_detail" options for further
                                    report-limiting options.

           --help         Emit short usage message and bail out.

                          (By happy coincidence, "-h" alone does much the same,
                          being as it requires a numeric argument :-).  Yeah, I
                          know: lame.)

           -i
           --ignore_case  Handle complete email address in a case-insensitive
                          manner.

                          Normally pflogsumm lower-cases only the host and
                          domain parts, leaving the user part alone.  This
                          option causes the entire email address to be lower-
                          cased.

           --iso_date_time

                          For summaries that contain date or time information,
                          use ISO 8601 standard formats (CCYY-MM-DD and HH:MM),
                          rather than "Mon DD CCYY" and "HHMM".

           -m             modify (mung?) UUCP-style bang-paths
           --uucp_mung

                          This is for use when you have a mix of Internet-style
                          domain addresses and UUCP-style bang-paths in the log.
                          Upstream UUCP feeds sometimes mung Internet domain
                          style address into bang-paths.  This option can
                          sometimes undo the "damage".  For example:
                          "somehost.dom!username@foo" (where "foo" is the next
                          host upstream and "somehost.dom" was whence the email
                          originated) will get converted to
                          "foo!username@somehost.dom".  This also affects the
                          extended detail report (-e), to help ensure that by-
                           domain-by-name sorting is more accurate.

           --mailq        Run "mailq" command at end of report.

                          Merely a convenience feature.  (Assumes that "mailq"
                          is in $PATH.  See "$mailqCmd" variable to path thisi
                          if desired.)

           --no_bounce_detail
           --no_deferral_detail
           --no_reject_detail

                          These switches are depreciated in favour of
                          --bounce_detail, --deferral_detail and
                          --reject_detail, respectively.

                          Suppresses the printing of the following detailed
                          reports, respectively:

                               message bounce detail (by relay)
                               message deferral detail
                               message reject detail

                          See also: "-u" and "-h" for further report-limiting
                                    options.

           --no_no_msg_size

                           Do not emit report on "Messages with no size data".

                           Message size is reported only by the queue manager.
                           The message may be delivered long-enough after the
                           (last) qmgr log entry that the information is not in
                           the log(s) processed by a particular run of
                           pflogsumm.pl.  This throws off "Recipients by message
                           size" and the total for "bytes delivered." These are
                           normally reported by pflogsumm as "Messages with no
                           size data."

           --no_smtpd_warnings

                          This switch is depreciated in favour of
                          smtpd_warning_detail

                           On a busy mail server, say at an ISP, SMTPD warnings
                           can result in a rather sizeable report.  This option
                           turns reporting them off.

           --problems_first

                          Emit "problems" reports (bounces, defers, warnings,
                          etc.) before "normal" stats.

           --rej_add_from
                          For those reject reports that list IP addresses or
                          host/domain names: append the email from address to
                          each listing.  (Does not apply to "Improper use of
                          SMTP command pipelining" report.)

           -q             quiet - don't print headings for empty reports

                          note: headings for warning, fatal, and "master"
                          messages will always be printed.

           --reject_detail <cnt>

                          Limit detailed smtpd reject, warn, hold and discard
                          reports to the top <cnt>.  0 to suppress entirely.

           --smtp_detail <cnt>

                          Limit detailed smtp delivery reports to the top <cnt>.
                          0 to suppress entirely.

           --smtpd_stats

                          Generate smtpd connection statistics.

                          The "per-day" report is not generated for single-day
                          reports.  For multiple-day reports: "per-hour" numbers
                          are daily averages (reflected in the report heading).

           --smtpd_warning_detail <cnt>

                          Limit detailed smtpd warnings reports to the top <cnt>.
                          0 to suppress entirely.

           --syslog_name=name

                          Set syslog_name to look for for Postfix log entries.

                          By default, pflogsumm looks for entries in logfiles
                          with a syslog name of "postfix," the default.
                          If you've set a non-default "syslog_name" parameter
                          in your Postfix configuration, use this option to
                          tell pflogsumm what that is.

                          See the discussion about the use of this option under
                          "NOTES," below.

           -u <cnt>       top <cnt> to display in user reports. 0 == none.

                          See also: "-h" and "--*_detail" options for further
                                    report-limiting options.

           --verbose_msg_detail

                          For the message deferral, bounce and reject summaries:
                          display the full "reason", rather than a truncated one.

                          Note: this can result in quite long lines in the report.

           --verp_mung    do "VERP" generated address (?) munging.  Convert
           --verp_mung=2  sender addresses of the form
                          "list-return-NN-someuser=some.dom@host.sender.dom"
                           to
                             "list-return-ID-someuser=some.dom@host.sender.dom"

                           In other words: replace the numeric value with "ID".

                          By specifying the optional "=2" (second form), the
                          munging is more "aggressive", converting the address
                          to something like:

                               "list-return@host.sender.dom"

                          Actually: specifying anything less than 2 does the
                          "simple" munging and anything greater than 1 results
                          in the more "aggressive" hack being applied.

                          See "NOTES" regarding this option.

           --version      Print program name and version and bail out.

           --zero_fill    "Zero-fill" certain arrays so reports come out with
                          data in columns that that might otherwise be blank.

RETURN VALUE
           Pflogsumm doesn't return anything of interest to the shell.

ERRORS
           Error messages are emitted to stderr.

EXAMPLES
           Produce a report of previous day's activities:

               pflogsumm.pl -d yesterday /var/log/maillog

           A report of prior week's activities (after logs rotated):

               pflogsumm.pl /var/log/maillog.0

           What's happened so far today:

               pflogsumm.pl -d today /var/log/maillog

           Crontab entry to generate a report of the previous day's activity
           at 10 minutes after midnight.

               10 0 * * * /usr/local/sbin/pflogsumm -d yesterday /var/log/maillog
               2>&1 |/usr/bin/mailx -s "`uname -n` daily mail stats" postmaster

           Crontab entry to generate a report for the prior week's activity.
           (This example assumes one rotates ones mail logs weekly, some time
           before 4:10 a.m. on Sunday.)

               10 4 * * 0   /usr/local/sbin/pflogsumm /var/log/maillog.0
               2>&1 |/usr/bin/mailx -s "`uname -n` weekly mail stats" postmaster

           The two crontab examples, above, must actually be a single line
           each.  They're broken-up into two-or-more lines due to page
           formatting issues.

SEE ALSO
           The pflogsumm FAQ: pflogsumm-faq.txt.

NOTES
           Pflogsumm makes no attempt to catch/parse non-Postfix log
           entries.  Unless it has "postfix/" in the log entry, it will be
           ignored.

           It's important that the logs are presented to pflogsumm in
           chronological order so that message sizes are available when
           needed.

           For display purposes: integer values are munged into "kilo" and
           "mega" notation as they exceed certain values.  I chose the
           admittedly arbitrary boundaries of 512k and 512m as the points at
           which to do this--my thinking being 512x was the largest number
           (of digits) that most folks can comfortably grok at-a-glance.
           These are "computer" "k" and "m", not 1000 and 1,000,000.  You
           can easily change all of this with some constants near the
           beginning of the program.

           "Items-per-day" reports are not generated for single-day
           reports.  For multiple-day reports: "Items-per-hour" numbers are
           daily averages (reflected in the report headings).

           Message rejects, reject warnings, holds and discards are all
           reported under the "rejects" column for the Per-Hour and Per-Day
           traffic summaries.

           Verp munging may not always result in correct address and
           address-count reduction.

           Verp munging is always in a state of experimentation.  The use
           of this option may result in inaccurate statistics with regards
           to the "senders" count.

           UUCP-style bang-path handling needs more work.  Particularly if
           Postfix is not being run with "swap_bangpath = yes" and/or *is* being
           run with "append_dot_mydomain = yes", the detailed by-message report
           may not be sorted correctly by-domain-by-user.  (Also depends on
           upstream MTA, I suspect.)

           The "percent rejected" and "percent discarded" figures are only
           approximations.  They are calculated as follows (example is for
           "percent rejected"):

               percent rejected =

                   (rejected / (delivered + rejected + discarded)) * 100

           There are some issues with the use of --syslog_name.  The problem is
           that, even with $syslog_name set, Postfix will sometimes still log
           things with "postfix" as the syslog_name.  This is noted in
           /etc/postfix/sample-misc.cf:

               # Beware: a non-default syslog_name setting takes effect only
               # after process initialization. Some initialization errors will be
               # logged with the default name, especially errors while parsing
               # the command line and errors while accessing the Postfix main.cf
               # configuration file.

           As a consequence, pflogsumm must always look for "postfix," in logs,
           as well as whatever is supplied for syslog_name.

           Where this becomes an issue is where people are running two or more
           instances of Postfix, logging to the same file.  In such a case:

               . Neither instance may use the default "postfix" syslog name
                 and...

               . Log entries that fall victim to what's described in
                 sample-misc.cf will be reported under "postfix", so that if
                 you're running pflogsumm twice, once for each syslog_name, such
                 log entries will show up in each report.

           The Pflogsumm Home Page is at:

               http://jimsun.LinxNet.com/postfix_contrib.html

REQUIREMENTS
           For certain options (e.g.: --smtpd_stats), Pflogsumm requires the
           Date::Calc module, which can be obtained from CPAN at
           http://www.perl.com.

           Pflogsumm is currently written and tested under Perl 5.8.3.
           As of version 19990413-02, pflogsumm worked with Perl 5.003, but
           future compatibility is not guaranteed.

LICENSE
           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; either version 2
           of the License, or (at your option) any later version.

           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.

           You may have received a copy of the GNU General Public License
           along with this program; if not, write to the Free Software
           Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
           USA.

           An on-line copy of the GNU General Public License can be found
           http://www.fsf.org/copyleft/gpl.html.

1.1.3                                    2010-03-20                            PFLOGSUMM(1)

Mit der Option -help werden die entsprechenden Optionen ebenfalls in Kurzform angezeigt.

 # pflogsumm -help
usage: pflogsumm.pl -[eq] [-d <today|yesterday>] [--detail <cnt>]
        [--bounce_detail <cnt>] [--deferral_detail <cnt>]
        [-h <cnt>] [-i|--ignore_case] [--iso_date_time] [--mailq]
        [-m|--uucp_mung] [--no_bounce_detail] [--no_deferral_detail]
        [--no_no_msg_size] [--no_reject_detail] [--no_smtpd_warnings]
        [--problems_first] [--rej_add_from] [--reject_detail <cnt>]
        [--smtp_detail <cnt>] [--smtpd_stats]
        [--smtpd_warning_detail <cnt>] [--syslog_name=string]
        [-u <cnt>] [--verbose_msg_detail] [--verp_mung[=<n>]]
        [--zero_fill] [file1 [filen]]

       pflogsumm.pl --[version|help]

manueller Programmaufruf

Wollen wir uns einen Bericht des heutigen Tages ansehen, so generieren wir diesen on-the-fly mit:

# /usr/sbin/pflogsumm -d today /var/log/maillog

Interessiert uns was gestern los war, so lautet der Aufgruf ganz einfach:

# /usr/sbin/pflogsumm -d yesterday /var/log/maillog

automatischer Programmaufruf

Für die tägliche Erstellung unserer Mailserverstatistik bemühen wir nun ganz einfach unseres cron-deamon.

Hierzu legen wir mit dem Editor unserer Wahl eine betreffende Konfigurationsdatei an, bzw. ergänzen die bereits vorhandene Datei.

# vim /etc/crontab
# Django : 2015-01-30
#täglicher Statusbericht unseres Mailservers postfix
10 0 * * * root /usr/sbin/pflogsumm -d yesterday /var/log/maillog 2>&1 | /bin/mailx -s "mx-test.dmz.nausch.org daily mail stats" postmaster@nausch.org

Täglich um 00:10 Uhr wird die Statistik des letzten Tages erstellt und mittels mailx als eMail an den postmaster verschickt. Das Paket mailx muss dazu natürlich installiert sein, falls (noch) nicht, holen wir dies kurz noch nach.

 # yum install mailx -y
Date: Mon, 13 Oct 2008 00:10:03 +0200 (CEST)
From: root <root@nausch.org>
To: postmaster@nausch.org
Subject: mx-test.dmz.nausch.org daily mail stats

Postfix log summaries for Feb 01

Grand Totals
------------
messages

     31   received
     27   delivered
      0   forwarded
      0   deferred
      0   bounced
    339   rejected (92%)
      0   reject warnings
      0   held
      0   discarded (0%)

 353252   bytes received
 353252   bytes delivered
     16   senders
     16   sending hosts/domains
      4   recipients
      1   recipient hosts/domains


Per-Hour Traffic Summary
    time          received  delivered   deferred    bounced     rejected
    --------------------------------------------------------------------
    0000-0100           0          0          0          0          0 
    0100-0200           0          0          0          0          0 
    0200-0300           0          0          0          0          0 
    0300-0400           0          0          0          0          0 
    0400-0500           1          0          0          0          3 
    0500-0600           1          1          0          0         24 
    0600-0700           0          0          0          0         10 
    0700-0800           0          0          0          0          2 
    0800-0900           4          3          0          0          7 
    0900-1000           3          3          0          0         18 
    1000-1100           3          2          0          0         18 
    1100-1200           2          2          0          0         29 
    1200-1300           2          2          0          0         25 
    1300-1400           2          1          0          0         20 
    1400-1500           0          0          0          0         37 
    1500-1600           0          0          0          0         22 
    1600-1700           0          0          0          0         24 
    1700-1800           2          2          0          0         31 
    1800-1900           3          3          0          0         12 
    1900-2000           3          3          0          0         10 
    2000-2100           4          4          0          0         19 
    2100-2200           1          1          0          0          2 
    2200-2300           0          0          0          0         19 
    2300-2400           0          0          0          0          7 

Host/Domain Summary: Message Delivery 
 sent cnt  bytes   defers   avg dly max dly host/domain
 -------- -------  -------  ------- ------- -----------
    872     6405k       0     2.1 s    2.4 m  nausch.org
...

... 

Fatal Errors: none

Panics: none

Master daemon messages
----------------------
      1   reload configuration /etc/postfix

mailgraph

Eine ansprechende graphische Übersicht kann mittels Mailgraph erstellt werden. Das passende Paket mailgraph installieren wir aus dem Repository mailserver.guru.

Mailgraph besteht im wesentlichen aus zwei Teilen. Das eine perl-Script ist zuständig für das Durchsuchen und Analysieren des Mailserver-Logdatei. Die gewonnenen Daten werden in rrd-Datendateien geschrieben. Ein zweites Perl cgi-Script generiert dann beim Aufrufen der zugehörigen Webseite Graphiken mit den Daten der rrd-Dateien.

Installation

Mit Hilfe von yum holen wir uns als erstes das benötigte Paket auf unser System.

 # yum install mailgraph -y

Den Inhalt des Paketes inspizieren wir bei Bedarf mit folgendem Aufruf.

 # rpm -qil mailgraph
Name        : mailgraph
Version     : 1.14
Release     : 1.el7.centos
Architecture: noarch
Install Date: Fri 30 Jan 2015 03:21:39 PM CET
Group       : System Environment/Daemons
Size        : 66890
License     : GPL+
Signature   : RSA/SHA1, Fri 30 Jan 2015 09:57:54 AM CET, Key ID 60ecfb9e8195aea0
Source RPM  : mailgraph-1.14-1.el7.centos.src.rpm
Build Date  : Fri 30 Jan 2015 09:57:42 AM CET
Build Host  : vml000200.dmz.nausch.org
Relocations : (not relocatable)
Packager    : Django <django@mailserver.guru>
URL         : http://mailgraph.schweikert.ch/
Summary     : A RRDtool frontend for Mail statistics
Description :
Mailgraph is a very simple mail statistics RRDtool frontend for Postfix and
Sendmail that produces daily, weekly, monthly and yearly graphs of
received/sent and bounced/rejected mail.
/etc/httpd/conf.d/mailgraph.conf
/etc/sysconfig/mailgraph
/usr/lib/systemd/system/mailgraph.service
/usr/sbin/mailgraph
/usr/share/doc/mailgraph-1.14
/usr/share/doc/mailgraph-1.14/CHANGES
/usr/share/doc/mailgraph-1.14/COPYING
/usr/share/doc/mailgraph-1.14/README
/usr/share/mailgraph
/usr/share/mailgraph/mailgraph.cgi
/usr/share/mailgraph/mailgraph.css
/var/cache/mailgraph
/var/lib/mailgraph

Konfiguration

Die Konfiguration von mailgraph selbst gestaltet sich sehr einfach. Über die Konfigurationsdatei /etc/sysconfig/mailgraph können wir angeben, wo das Logfile unseres Mailservers zu finden ist. Unter CentOS ist dies /var/log/maillog. Mit der Option –ignore-localhost können wir mailgraph anweisen, Nachrichten von localhost nicht in die Statistik aufzunehmen, die Mails also nicht „doppelt“ zu zählen, wenn z.B. Postfix _und_ AMaViS auf dem gleichen Host laufen.

 # vim /etc/sysconfig/mailgraph
/etc/sysconfig/mailgraph
MAILLOG=/var/log/maillog
PRIORITY=-19
OPTIONS=--ignore-localhost

Will man die Sprache bei der Webseite, oder die Farben der Grafiken anpassen, schreibt man einfach seine Änderungen direkt in das übersichtliche CGI-Script.

 # cat /usr/share/mailgraph/mailgraph.cgi
/usr/share/mailgraph/mailgraph.cgi
#!/usr/bin/perl -w
 
# mailgraph -- postfix mail traffic statistics
# copyright (c) 2000-2007 ETH Zurich
# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
# released under the GNU General Public License
 
use RRDs;
use POSIX qw(uname);
 
my $VERSION = "1.14";
 
my $host = (POSIX::uname())[1];
my $scriptname = 'mailgraph.cgi';
my $xpoints = 540;
my $points_per_sample = 3;
my $ypoints = 160;
my $ypoints_err = 96;
my $rrd = '/var/lib/mailgraph/mailgraph.rrd'; # path to where the RRD database is
my $rrd_virus = '/var/lib/mailgraph/mailgraph_virus.rrd'; # path to where the Virus RRD database is
my $tmp_dir = '/var/cache/mailgraph'; # 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    => 'DDBB00',
        spam     => '999999',
);
 
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',
        );
}
 
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,*",
                "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',
 
        );
}
 
sub print_html()
{
        print "Content-Type: text/html\n\n";
 
        print <<HEADER;
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Mail statistics for $host</title>
<meta http-equiv="Refresh" content="300" />
<meta http-equiv="Pragma" content="no-cache" />
<link rel="stylesheet" href="mailgraph.css" type="text/css" />
</head>
<body>
HEADER
 
        print "<h1>Mail statistics for $host</h1>\n";
 
        print "<ul id=\"jump\">\n";
        for my $n (0..$#graphs) {
                print "  <li><a href=\"#G$n\">$graphs[$n]{title}</a>&nbsp;</li>\n";
        }
        print "</ul>\n";
 
        for my $n (0..$#graphs) {
                print "<h2 id=\"G$n\">$graphs[$n]{title}</h2>\n";
                print "<p><img src=\"$scriptname?${n}-n\" alt=\"mailgraph\"/><br/>\n";
                print "<img src=\"$scriptname?${n}-e\" alt=\"mailgraph\"/></p>\n";
        }
 
        print <<FOOTER;
<hr/>
<table><tr><td>
<a href="http://mailgraph.schweikert.ch/">Mailgraph</a> $VERSION
by <a href="http://david.schweikert.ch/">David Schweikert</a></td>
<td align="right">
<a href="http://oss.oetiker.ch/rrdtool/"><img src="http://oss.oetiker.ch/rrdtool/.pics/rrdtool.gif" alt="" width="120" height="34"/></a>
</td></tr></table>
</body></html>
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/mailgraph_$1.png";
                        graph($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-e$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_err.png";
                        graph_err($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                else {
                        die "ERROR: invalid argument\n";
                }
        }
        else {
                print_html;
        }
}
 
main;

Apache VHost anlegen

Damit wir bequem von unserem Browser aus, die aktuellen Graphiken abfragen können, bearbeiten wir entweder die aus dem RPM stammende Konfigurationsdatei oder legen wir nun einen passenden VHost an.

# vim /etc/httpd/conf.d/vhosts.conf
/etc/httpd/conf.d/vhosts.conf
#
# mailgraph.nausch.org
#
<VirtualHost *:80>
	ServerAdmin webmaster@nausch.org
	ServerName mailgraph.nausch.org
	ServerAlias www.mailgraph.nausch.org
	ServerPath /
	DocumentRoot "/usr/share/mailgraph"
	AddHandler cgi-script .cgi
 
    	<Directory "/usr/share/mailgraph">
            AllowOverride None
            Options +ExecCGI
            DirectoryIndex mailgraph.cgi
            Order deny,allow
	    require IP 10.0.
	</Directory>
	ErrorLog logs/mailgraph_error.log
	CustomLog logs/mailgraph_access.log combined
</VirtualHost>

Bevor wir bei unserem Webserver eine Reload der Konfiguration vornehmen, testen wir unsere neue Konfigurationsdatei auf syntaktische Fehler.

 # apachectl -t
 Syntax OK

Da keine Fehler aufgetreten sind, aktivieren wir die neue Konfiguration durch einen Reload des Webserver-Daemon.

 # systemctl reload postfix

NGiNX VHost anlegen

Nutzen wir als Webserver NGiNX können wir auch hier schnell und einfach einen passenden vHOST anlegen.

# vim /etc/nginx/conf.d/vhosts.conf
/etc/nginx/conf.d/vhosts.conf
server {
        listen          80;
        server_name     mailgraph.nausch.org;
        access_log      /var/log/nginx/mailgraph_access.log;
        error_log       /var/log/nginx/mailgraph_errors.log;
 
        root /usr/share/mailgraph/;
        index mailgraph.cgi;
 
       location ~ \.php {
                fastcgi_split_path_info ^(.+\.cgi)(/.+)$;
                fastcgi_index mailgraph.cgi;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
        }
}

Den Parameter fastcgi_pass setzen wir im übrigen auf den Wert aus der Konfigurationsdatei /etc/php-fpm.d/www.conf des PHP FastCGI Process Manager-Daemon php-fpm.

Haben wir die Konfigurationsdatei vervollständigt, prüfen wir diese noch auf syntaktische Fehler.

 # nginx -t
 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
 nginx: configuration file /etc/nginx/nginx.conf test is successful

Somit können wir unsere Konfiguration nun noch aktivieren.

 # systemctl reload nginx

Programmaufruf

erster manueller Start des Dämon

Damit das Mail-Logfile forlaufend ausgelesen wird, starten wir nun noch den Dämon mit Hilfe des mitgelieferten systemd-Start-Scriptes /usr/lib/systemd/system/mailgraph.service.

 # systemctl start mailgraph

Im syslog wurde der Start des Daemon entsprechend dokumentiert.

 # tail -n2 /var/log/messages
 Feb  2 21:17:36 vml000097 systemd: Starting mailgraph mail log file analyzer...
 Feb  2 21:17:37 vml000097 systemd: Started mailgraph mail log file analyzer.

Ebenso kann man den Status des Webservers mit Hilfe des Befehls systemctl abfragen.

 # systemctl status mailgraph
mailgraph.service - mailgraph mail log file analyzer
   Loaded: loaded (/usr/lib/systemd/system/mailgraph.service; disabled)
   Active: active (running) since Mon 2015-02-02 21:17:37 CET; 2min 48s ago
  Process: 2362 ExecStart=/usr/sbin/mailgraph -d -l $MAILLOG --daemon-rrd=/var/lib/mailgraph $OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 2367 (mailgraph)
   CGroup: /system.slice/mailgraph.service
           └─2367 /usr/bin/perl -w /usr/sbin/mailgraph -d -l /var/log/maillog --daemon-rrd=/var/lib/mail...

Feb 02 21:17:37 vml000097.dmz.nausch.org systemd[1]: Started mailgraph mail log file analyzer.

automatischer Start beim Systemstart

Wollen wir den Daemon beim Hochfahren des Systems automatisch starten, greifen wir auf den Befehl systemctl zurück.

 # systemctl enable mailgraph.service
 ln -s '/usr/lib/systemd/system/httpd.service' '/etc/systemd/system/multi-user.target.wants/httpd.service'

Möchten wir uns vergewissern, ob der Daemon beim Systemstart gestartet wird oder nicht, erfahren wir ebenfalls mit dem Befehl systemctl.

 # systemctl is-enabled mailgraph.service
 enabled

Startet der Server nicht automatisch, wird uns ein „disabled“ zurückgemeldet.

Webaufruf

Über unseren vHOST erhalten wir nun optisch schön ansprechende Übersichten über den Mailverkehr unseres MX.

Bildschirmhardcopy: mailgraph

queuegraph

Möchte man einen graphischen Überblick über die Queues haben, so liefert uns das gerade vorgestellte und installierte Mailgraph leider keine grafischen werte. Hierzu greifen wir auf das Programm Queuegraph von Ralf Hildebrandt zurück.

Das passende Programmpaket queuegraph installieren am einfachsten aus dem Repository mailserver.guru.

Installation

Mit Hilfe von yum holen wir uns als erstes das benötigte Paket auf unser System.

 # yum install queuegraph -y

Den Inhalt des Paketes inspizieren wir bei Bedarf mit folgendem Aufruf.

 # rpm -qil queuegraph
Name        : queuegraph
Version     : 1.1
Release     : 1.el7.centos
Architecture: noarch
Install Date: Mon 02 Feb 2015 09:29:25 PM CET
Group       : System Environment/Daemons
Size        : 6271
License     : GPL+
Signature   : RSA/SHA1, Fri 30 Jan 2015 09:21:58 PM CET, Key ID 60ecfb9e8195aea0
Source RPM  : queuegraph-1.1-1.el7.centos.src.rpm
Build Date  : Fri 30 Jan 2015 09:21:46 PM CET
Build Host  : vml000200.dmz.nausch.org
Relocations : (not relocatable)
Packager    : Django <django@mailserver.guru>
URL         : http://www.arschkrebs.de/postfix/queuegraph/
Summary     : A RRDtool frontend for Mail statistics
Description :
Queuegraph is a very simple mail statistics RRDtool frontend for Postfix that
produces daily, weekly, monthly and yearly graphs of Postfix's active,
deferred, incoming and bounce queues.
/etc/cron.d/queuegraph
/etc/httpd/conf.d/queuegraph.conf
/usr/sbin/queuegraph-rrd.sh
/usr/share/doc/queuegraph-1.1
/usr/share/doc/queuegraph-1.1/README
/usr/share/queuegraph
/usr/share/queuegraph/queuegraph.cgi
/var/cache/queuegraph
/var/lib/queuegraph

Konfiguration

Die Konfiguration von queuegraph selbst gestaltet sich sehr einfach, da es gar nichts großartrig zu konfigurieren gibt!

Will man die Sprache bei der Webseite, oder die Farben anpassen, so nimmt man die Änderungen direkt im CGI-Script vor.

 # vim /usr/share/queuegraph/queuegraph.cgi
/usr/share/queuegraph/queuegraph.cgi
#!/usr/bin/perl -w
 
# queuegraph -- a postfix queue statistics rrdtool frontend
# based on mailgraph, which is
# copyright (c) 2000-2002 David Schweikert <dws@ee.ethz.ch>
# released under the GNU General Public License
 
use RRDs;
use POSIX qw(uname);
 
my $VERSION = "1.1";
 
my $host = (POSIX::uname())[1];
my $scriptname = 'queuegraph.cgi';
my $xpoints = 800;
my $points_per_sample = 3;
my $ypoints = 160;
my $ypoints_err = 80;
my $rrd = '/var/lib/queuegraph/mailqueues.rrd'; # path to where the RRD database is
my $tmp_dir = '/var/cache/queuegraph'; # temporary directory where to store the images
my $rrdtool_1_0 = ($RRDs::VERSION < 1.199908);
 
my @graphs = (
        { title => 'Day Graph',   seconds => 3600*24,        },
        { title => 'Week Graph',  seconds => 3600*24*7,      },
        { title => 'Month Graph', seconds => 3600*24*31,     },
        { title => 'Year Graph',  seconds => 3600*24*365, },
);
 
my %color = (
        sent     => '000099', # rrggbb in hex
        received => '00FF00',
        rejected => '999999', 
        bounced  => '993399',
        virus    => 'FFFF00',
        spam     => 'FF0000',
);
 
sub graph($$$)
{
        my $range = shift;
        my $file = shift;
        my $title = shift;
        my $step = $range*$points_per_sample/$xpoints;
        my $date = localtime(time);
        $date =~ s|:|\\:|g unless $rrdtool_1_0;
 
        my ($graphret,$xs,$ys) = RRDs::graph($file,
                '--imgformat', 'PNG',
                '--width', $xpoints,
                '--height', $ypoints,
                '--start', "-$range",
                '--end', "-".int($range*0.01),
                '--vertical-label', 'queuefiles',
                '--title', $title,
                '--lazy',
                $rrdtool_1_0 ? () : (
                        '--slope-mode'
                ),
 
                "DEF:active=$rrd:active:AVERAGE",
                "DEF:deferred=$rrd:deferred:AVERAGE",
 
                'LINE2:active#00ff00:Active+Incoming+Maildrop\:',
                'GPRINT:active:MAX:Maximum\: %0.0lf ',
                'GPRINT:active:AVERAGE:Average\: %0.0lf/min\n',
 
                'LINE1:deferred#0000ff:Deferred\:',
                'GPRINT:deferred:MAX:Maximum\: %0.0lf ',
                'GPRINT:deferred:AVERAGE:Average\: %0.0lf/min\l',
 
                'HRULE:0#000000',
                'COMMENT:\n',
                'COMMENT:['.$date.']\r',
     );
        my $ERR=RRDs::error;
        die "ERROR: $ERR\n" if $ERR;
}
 
sub print_html()
{
        print "Content-Type: text/html\n\n";
 
        print <<HEADER;
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Queue Statistics for $host</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
HEADER
 
        print "<H1>Postfix Queue Statistics for $host</H1>\n";
        for my $n (0..$#graphs) {
                print "<H2>$graphs[$n]{title}</H2>\n";
                print "<P><IMG BORDER=\"0\" SRC=\"$scriptname/queuegraph_${n}.png\" ALT=\"queuegraph\">\n";
        }
 
        print <<FOOTER;
<table border="0" width="400"><tr><td align="left">
<A href="http://www.arschkrebs.de/postfix/queuegraph">queuegraph</A> $VERSION
by <A href="http://www.arschkrebs.de/">Ralf Hildebrandt</A>, 
based on <A href="http://mailgraph.schweikert.ch/">mailgraph</A> 
by <A href="http://david.schweikert.ch/">David Schweikert</A></td>
<td ALIGN="right">
<a HREF="http://oss.oetiker.ch/rrdtool/"><img border="0" src="http://tobi.oetiker.ch/webtools/rrdtool/.pics/rrdtool.gif" alt="rrdtool" width="120" height="34"></a>
</td></tr></table>
</BODY>
FOOTER
}
 
sub send_image($)
{
        my $file = shift;
        -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, 1;
}
 
sub main()
{
        if($ENV{PATH_INFO}) {
                my $uri = $ENV{REQUEST_URI};
                $uri =~ s/\/[^\/]+$//;
                $uri =~ s/\//,/g;
                $uri =~ s/\~/tilde,/g;
                mkdir "$tmp_dir/$uri", 0777 unless -d "$tmp_dir/$uri";
                my $file = "$tmp_dir/$uri$ENV{PATH_INFO}";
                if($ENV{PATH_INFO} =~ /^\/queuegraph_(\d+)\.png$/) {
                        graph($graphs[$1]{seconds}, $file, $graphs[$1]{title});
                }
                else {
                        print "Content-Type: text/plain\n\nERROR: unknown image $ENV{PATH_INFO}\n";
                        exit 1;
                }
                send_image($file);
        }
        else {
                print_html;
        }
}
 
main;

Optionen beim Programmaufruf

Hinweise zur Konfiguration oder besser gesagt über die Optionen beim Aufruf des Programms zeigt ein Blick in die Datei /usr/share/doc/queuegraph-1.1/README.

 # cat /usr/share/doc/queuegraph-1.1/README
/usr/share/doc/queuegraph-1.1/README
README for queuegraph
 
Dependencies: rrdtools, librrds-perl
 
To install, adjust the path to your rrdtools binaries in queuegraph-rrd.sh
 
Create a cronjob that runs queuegraph-rrd.sh every minute -- this
populates the *.rrd database:
 
* * * * * /usr/local/bin/queuegraph-rrd.sh
 
Now put queuegraph.cgi into the cgi-bin directory of your webserver.
chmod 755 queuegraph.cgi

Apache VHost anlegen

Damit wir bequem von unserem Browser aus, die aktuellen Graphiken abfragen können, bearbeiten wir entweder die aus dem RPM stammende Konfigurationsdatei oder legen wir nun einen passenden VHost an.

# vim /etc/httpd/conf.d/vhosts.conf
/etc/httpd/conf.d/vhosts.conf
#
# queuegraph.nausch.org
#
<VirtualHost *:80>
	ServerAdmin webmaster@nausch.org
	ServerName queue.nausch.org
	ServerAlias www.queue.nausch.org
	ServerPath /
	DocumentRoot "/usr/share/queuegraph"
	AddHandler cgi-script .cgi
 
	<Directory "/usr/share/queuegraph">
    AllowOverride None
    Options +ExecCGI
    DirectoryIndex queuegraph.cgi
		Order deny,allow
		require IP 10.0.
	</Directory>
	ErrorLog logs/queuegraph_error.log
	CustomLog logs/queuegraph_access.log combined
</VirtualHost>

Bevor wir bei unserem Webserver eine Reload der Konfiguration vornehmen, testen wir unsere neue Konfigurationsdatei auf syntaktische Fehler.

 # apachectl -t
 Syntax OK

Da keine Fehler aufgetreten sind, aktivieren wir die neue Konfiguration durch einen Reload des Webserver-Daemon.

 # systemctl reload postfix

NGiNX VHost anlegen

Nutzen wir als Webserver NGiNX können wir auch hier schnell und einfach einen passenden vHOST anlegen.

# vim /etc/nginx/conf.d/vhosts.conf
/etc/nginx/conf.d/vhosts.conf
server {
        listen          80;
        server_name     queuegraph.nausch.org;
        access_log      /var/log/nginx/queuegraph_access.log;
        error_log       /var/log/nginx/queuegraph_errors.log;
 
        root /usr/share/queuegraph/;
        index queuegraph.cgi;
 
       location ~ \.php {
                fastcgi_split_path_info ^(.+\.cgi)(/.+)$;
                fastcgi_index queuegraph.cgi;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
        }
}

Den Parameter fastcgi_pass setzen wir im übrigen auf den Wert aus der Konfigurationsdatei /etc/php-fpm.d/www.conf des PHP FastCGI Process Manager-Daemon php-fpm.

Haben wir die Konfigurationsdatei vervollständigt, prüfen wir diese noch auf syntaktische Fehler.

 # nginx -t
 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
 nginx: configuration file /etc/nginx/nginx.conf test is successful

Somit können wir unsere Konfiguration nun noch aktivieren.

 # systemctl reload nginx

Programmaufruf

Die automatische Befüllung der rrd-files übernimmt ein cronjob, der jede Minute das Script queuegraph-rrd.sh startet.

 # cat /etc/cron.d/queuegraph
/etc/cron.d/queuegraph
# Runs the queuegraph update program
#
# This will run every one minute
* * * * * root /usr/sbin/queuegraph-rrd.sh &> /dev/null
 # less  /usr/sbin/queuegraph-rrd.sh
/usr/sbin/queuegraph-rrd.sh
#!/bin/sh
 
# output the number of messages in the incoming, active, and deferred
# queues of postfix one per line suitable for use with snmpd/cricket/rrdtool
#
# 2003/01/24 Mike Saunders <method at method DOT cx>
#            mailqsize was originally written by Vivek Khera.  All I did was
#            make it update an rrd.
# 2003/04/14 Ralf Hildebrandt <ralf.hildebrandt at charite DOT de>
#            I bundled this with a modified mailgraph
# 2007/07/28 Ralf Hildebrandt <ralf.hildebrandt at charite DOT de>
#            find rrdtool using "which"
 
# change this to the location of rrdtool
RRDTOOL=`which rrdtool`
 
# change this to the location you want to store the rrd
RRDFILE=/var/lib/queuegraph/mailqueues.rrd
 
if test ! -x $RRDTOOL ; then
        echo "ERROR: $RRDTOOL does not exist or is not executable"
        exit
fi
 
if test ! -f $RRDFILE ; then
        echo "Creating RRD file $RRDFILE"
 
        $RRDTOOL create $RRDFILE --step 60 \
                DS:active:GAUGE:900:0:U \
                DS:deferred:GAUGE:900:0:U \
                RRA:AVERAGE:0.5:1:1440 \
                RRA:AVERAGE:0.5:30:2016 \
                RRA:AVERAGE:0.5:60:105120 \
                RRA:MAX:0.5:1:1440 \
                RRA:MAX:0.5:30:2016 \
                RRA:MAX:0.5:60:105120
fi
 
#set -x
qdir=`/usr/sbin/postconf -h queue_directory`
active=`find $qdir/incoming $qdir/active $qdir/maildrop -type f -print | wc -l | awk '{print $1}'`
deferred=`find $qdir/deferred -type f -print | wc -l | awk '{print $1}'`
#printf "active: %d\ndeferred: %d\n" $active $deferred
 
$RRDTOOL update $RRDFILE "N:$active:$deferred"

Webaufruf

Über unseren Vhost erhalten wir nun optisch schön ansprechende Übersichten über die Mail-Queues unseres MX.

Bildschirmhardcopy: queuegraph

Mailgraph NextGeneration

Wem all die vorgenannten graphischen Aufbereitungen noch nicht ausführlich genug sind, dem bietet der Fork von David Schweikert's Mailgraph zusätzliche Übersichten:

  • Mail Ein und -Ausgang
  • geblockte Nachrichten
  • Greylisting Übersicht
  • Greylisting Detailansicht
  • Postscreen Übersicht
  • Postscreen Detailansicht
  • Übersicht Mail-Queues
  • DANE / TLSA Verbindungen
  • Sender policy Framework - SPF-Prüfungen
  • DomainKeys Identified Mail - DKIM-Prüfungen
  • Domain-based Message Authentication, Reporting & Conformance - DMARC-Prüfungen

Das Ganze ist aber zugegebener Maßen keine großartige Neuprogrammierung, sondern setzt auf bekannte Programm(pakete) auf.

Besonderer Dank geht daher an:

Doch genug der Vorrede, einen Blick auf die Ausgabe des Webfrontends von Mailgraph-ng sagt mehr als 1.000 Worte. Die Ausgabe zeigt exemplarisch alle möglichen Graphen an, die auf einem Testsystem generiert wurden.

Bild: Bildschirmausgabe des WEB-Frontends von Mailgraph-ng

Im Realbetrieb wird man entweder Greylisting oder Postscreen einsetzen, somit werden sicher zwei der gezeigten Graphen wegfallen. Wie das geht, wird hier gezeigt.

Installation

Bei der Installation der NextGeneration Version von Mailgraph gibt es mehrere Möglichkeiten.

  • RPM-basiert
  • Update mit Hilfe eines tar.gz-Archives
  • manuelle Installation/Update

Auf die einzelnen Installationsarten gehen wir nun entsprechend ein.

RPM-Installation

Die wohl einfachste Art ist die der RPM-basierten Installation. Das passende Paket mailgraph installieren wir aus dem Repository mailserver.guru. Aktuell wird vom Paketmaintainer die Version 1.15 bereitgestellt. Haben wir das Repository mailserver.guru installiert, reicht der folgende Aufruf.

 # yum install mailgraph

Das abhängige Paket Queuegraph von Ralf Hildebrandt, welches zum Erstellen und Aktualisieren des Queuegraph rrd-Datenbankdatei verwendet wird, wird automatisch mit installiert.

Will man die beiden Pakete „nur lokal“ installieren verwendet man folgenden Aufruf.

 # yum localinstall http://repo.mailserver.guru/7/x86_64/mailgraph-1.15.2-1.el7.centos.noarch.rpm \
                    http://repo.mailserver.guru/7/x86_64/queuegraph-1.1-1.el7.centos.noarch.rpm

manuelle Installation

Hat man bereits Mailgraph in einer früheren Version (manuell) installiert und möchte diese Installation erweitern, so kann man natürlich Mailgraph auch manuell installieren.

Hierzu laden wir uns erst einmal das betreffende Paket auf unseren Server. Zuerst wechseln wir in unseren lokalen Paketspeicher.

 # cd /usr/local/src/packages
 # wget http://repository.nausch.org/public/var/mailgraph-1.15.2.tar.gz

Anschließend entpacken wir das Archiv.

 # tar xzfv mailgraph-1.15.2.tar.gz -C /tmp/

Dann erstellen wir, falls noch nicht im System vorhanden, die einzelnen Zielordner.

  1. RRD-Speicherverzeichnis
     # mkdir -p /var/lib/mailgraph
  2. Cachingverzeichnis für die Graphen
     mkdir -p /var/cache/mailgraph
  3. WEB Root Verzeichnis für die Webseite
     mkdir -p /usr/share/mailgraph

    Anschließend passen wir noch die Gruppenrechte an dem Ordner an.

     # chgrp apache /usr/share/mailgraph
     # chmod g+w /usr/share/mailgraph

Nun kopieren wir die Dateien aus unserem temporären Verzeichnis an die richtige Stelle im System.

 # cp /tmp/mailgraph-1.15.2/mailgraph.c* /usr/share/mailgraph/ -y
 # cp /tmp/mailgraph-1.15.2/rrdtool-3dlogo.png /usr/share/mailgraph/ -y

Das Script zur Datengenerierung kopieren wir dann noch in das Verzeichnis /usr/sbin/. Vorher halten wir ggf. einen bereits laufenden mailgraph-Daemon an!

 # systemctl stop mailgraph
 # cp /tmp/mailgraph-1.15.2/mailgraph /usr/sbin/ -y

Konfiguration

/etc/sysconfig/mailgraph

Die Konfiguration von mailgraph selbst gestaltet sich sehr einfach. Über die Konfigurationsdatei /etc/sysconfig/mailgraph können wir angeben, wo das Logfile unseres Mailservers zu finden ist. Unter CentOS ist dies /var/log/maillog. Mit der Option –ignore-localhost können wir mailgraph anweisen, Nachrichten von localhost nicht in die Statistik aufzunehmen, die Mails also nicht „doppelt“ zu zählen, wenn z.B. Postfix _und_ AMaViS auf dem gleichen Host laufen.

 # vim /etc/sysconfig/mailgraph
/etc/sysconfig/mailgraph
MAILLOG=/var/log/maillog
PRIORITY=-19
OPTIONS=--ignore-localhost

/usr/share/mailgraph/mailgraph.cgi

Will man die Sprache bei der Webseite, oder die Farben der Grafiken anpassen, schreibt man einfach seine Änderungen direkt in das übersichtliche CGI-Script.

 # vim /usr/share/mailgraph/mailgraph.cgi
/usr/share/mailgraph/mailgraph.cgi
#!/usr/bin/perl -w
 
# mailgraph -- detailed postfix mail traffic statistics
# copyright (c) 2000-2007 ETH Zurich
# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
# modified 2011 for queuegraph by Ralf Hildebrandt <Ralf.Hildebrandt@computerbeschimpfung.de>
# modified 2015 for mailgraph-ng by Django <django@mailserver.guru> based on
# patches from  Sebastian van de Meer <kernel-error@kernel-error.de>
# released under the GNU General Public License
 
use RRDs;
use POSIX qw(uname);
 
my $VERSION = "1.15";
 
my $host = (POSIX::uname())[1];
my $scriptname = $ENV{"SCRIPT_NAME"};
my $xpoints = 800;
my $points_per_sample = 3;
my $ypoints = 160;
my $rrd = '/var/lib/mailgraph/mailgraph.rrd';                   # path to where the Mailgraph  RRD database is
my $rrd_virus = '/var/lib/mailgraph/mailgraph_virus.rrd';       # path to where the Virus      RRD database is
my $rrd_grey = '/var/lib/mailgraph/mailgraph_grey.rrd';         # path to where the Greygraph  RRD database is
my $rrd_dane = '/var/lib/mailgraph/mailgraph_dane.rrd';         # path to where the DANE       RRD database is
my $rrd_dmarc = '/var/lib/mailgraph/mailgraph_dmarc.rrd';       # path to where the DMARC      RRD database is
my $rrd_queue = '/var/lib/mailgraph/mailqueues.rrd';           # path to where the Mailqueue  RRD database is
my $rrd_post = '/var/lib/mailgraph/mailgraph_post.rrd';         # path to where the Postscreen RRD database is
 
my $tmp_dir = '/var/cache/mailgraph';                           # temporary directory where to store the images
my @graphs = (
        { title => 'Letzter Tag',   seconds => 3600*24,        },
        { title => 'Letzte Woche',  seconds => 3600*24*7,      },
        { title => 'Letzter Monat', seconds => 3600*24*31,     },
        { title => 'Letztes Jahr',  seconds => 3600*24*365,    },
);
 
my %color = (                                                   # rrggbb in hex
        # n
        sent            => '000099',
        received        => '009900',
 
        bounced         => '000000',
        virus           => 'DDBB00',
        spam            => '999999',
        rejected        => 'AA0000',
 
        greylisted      => 'CCCCCC',
        delayed         => '006400',
        whitelist       => '00D8FF',
        awl             => 'FF7700',
        early           => 'AA0000',
 
        pswl            => 'C33333', #
        psbl            => 'EBBAD5',
        passold         => 'C39233', #
        veto            => 'EBEBD5',
        pregreet        => 'EBA8D5',
        dnsbl           => 'EB75D5',
        pipelining      => 'B85BA7',
        nonsmtp         => '793C6E',
        barenewline     => '793C2E',
        command         => '47231B',
        hangup          => 'C12C0A',
        passnew         => 'C3D533', #
 
        new             => 'FF77EE',
        reconnectok     => '7700DD',
 
        active          => 'EFEF00',
        deferred        => 'DD8800',
 
        anonymoustls    => '000099',
        trustedtls      => '009900',
        untrustedtls    => 'AA0000',
        verifiedtls     => '000000',
 
        spfnone         => '12FF0A',
        spffail         => 'f80b6f',
        spfpass         => '2E5fEC',
 
        dkimnone        => 'E6E27A',
        dkimfail        => 'FF6600',
        dkimpass        => '3013EC',
 
        dmarcnone       => 'F0B166',
        dmarcfail       => 'f11717',
        dmarcpass       => '00FFD5',
);
 
sub rrd_graph(@)
{
        my ($range, $file, $ypoints, @rrdargs) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        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\: %15.0lf msgs',
                'GPRINT:rsent:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmsent:MAX:max\: %11.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\: %15.0lf msgs',
                'GPRINT:rrecv:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmrecv:MAX:max\: %11.0lf msgs/min\l',
        );
}
 
sub graph_virus($$)
{
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
                "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\: %15.0lf msgs',
                'GPRINT:rbounced:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmbounced:MAX:max\: %11.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\: %15.0lf msgs',
                'GPRINT:rvirus:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmvirus:MAX:max\: %11.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\: %15.0lf msgs',
                'GPRINT:rspam:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmspam:MAX:max\: %11.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\: %15.0lf msgs',
                'GPRINT:rrejected:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmrejected:MAX:max\: %11.0lf msgs/min\l',
 
        );
}
 
 
sub graph_greylist($$)
        {
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
 
                "DEF:new=$rrd_grey:new:AVERAGE",
                "DEF:mnew=$rrd_grey:new:MAX",
                "CDEF:rnew=new,60,*",
                "CDEF:rmnew=mnew,60,*",
                "CDEF:dnew=new,UN,0,new,IF,$step,*",
                "CDEF:snew=PREV,UN,dnew,PREV,IF,dnew,+",
                "AREA:rnew#$color{new}:New                     ",
                'GPRINT:snew:MAX:total\: %15.0lf msgs',
                'GPRINT:rnew:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmnew:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:reconnectok=$rrd_grey:reconnectok:AVERAGE",
                "DEF:mreconnectok=$rrd_grey:reconnectok:MAX",
                "CDEF:rreconnectok=reconnectok,60,*",
                "CDEF:dreconnectok=reconnectok,UN,0,reconnectok,IF,$step,*",
                "CDEF:sreconnectok=PREV,UN,dreconnectok,PREV,IF,dreconnectok,+",
                "CDEF:rmreconnectok=mreconnectok,60,*",
                "LINE2:rreconnectok#$color{reconnectok}:Reconnect O.K.          ",
                'GPRINT:sreconnectok:MAX:total\: %15.0lf msgs',
                'GPRINT:rreconnectok:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmreconnectok:MAX:max\: %11.0lf msgs/min\l',
 
        );
}
 
 
sub graph_greystats($$)
        {
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
 
                "DEF:greylisted=$rrd_grey:greylisted:AVERAGE",
                "DEF:mgreylisted=$rrd_grey:greylisted:MAX",
                "CDEF:rgreylisted=greylisted,60,*",
                "CDEF:rmgreylisted=mgreylisted,60,*",
                "CDEF:dgreylisted=greylisted,UN,0,greylisted,IF,$step,*",
                "CDEF:sgreylisted=PREV,UN,dgreylisted,PREV,IF,dgreylisted,+",
                "AREA:rgreylisted#$color{greylisted}:Greylisted              ",
                'GPRINT:sgreylisted:MAX:total\: %15.0lf msgs',
                'GPRINT:rgreylisted:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmgreylisted:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:delayed=$rrd_grey:delayed:AVERAGE",
                "DEF:mdelayed=$rrd_grey:delayed:MAX",
                "CDEF:rdelayed=delayed,60,*",
                "CDEF:rmdelayed=mdelayed,60,*",
                "CDEF:ddelayed=delayed,UN,0,delayed,IF,$step,*",
                "CDEF:sdelayed=PREV,UN,ddelayed,PREV,IF,ddelayed,+",
                "LINE2:rdelayed#$color{delayed}:Delayed                 ",
                'GPRINT:sdelayed:MAX:total\: %15.0lf msgs',
                'GPRINT:rdelayed:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmdelayed:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:whitelist=$rrd_grey:whitelist:AVERAGE",
                "DEF:mwhitelist=$rrd_grey:whitelist:MAX",
                "CDEF:rwhitelist=whitelist,60,*",
                "CDEF:rmwhitelist=mwhitelist,60,*",
                "CDEF:dwhitelist=whitelist,UN,0,whitelist,IF,$step,*",
                "CDEF:swhitelist=PREV,UN,dwhitelist,PREV,IF,dwhitelist,+",
                "AREA:rwhitelist#$color{whitelist}:Whitelist               ",
                'GPRINT:swhitelist:MAX:total\: %15.0lf msgs',
                'GPRINT:rwhitelist:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmwhitelist:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:awl=$rrd_grey:awl:AVERAGE",
                "DEF:mawl=$rrd_grey:awl:MAX",
                "CDEF:rawl=awl,60,*",
                "CDEF:dawl=awl,UN,0,awl,IF,$step,*",
                "CDEF:sawl=PREV,UN,dawl,PREV,IF,dawl,+",
                "CDEF:rmawl=mawl,60,*",
                "LINE2:rawl#$color{awl}:Auto whitelist          ",
                'GPRINT:sawl:MAX:total\: %15.0lf msgs',
                'GPRINT:rawl:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmawl:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:early=$rrd_grey:early:AVERAGE",
                "DEF:mearly=$rrd_grey:early:MAX",
                "CDEF:rearly=early,60,*",
                "CDEF:rmearly=mearly,60,*",
                "CDEF:dearly=early,UN,0,early,IF,$step,*",
                "CDEF:searly=PREV,UN,dearly,PREV,IF,dearly,+",
                "AREA:rearly#$color{early}:Early connect            ",
                'GPRINT:searly:MAX:total\: %15.0lf msgs',
                'GPRINT:rearly:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmearly:MAX:max\: %11.0lf msgs/min\l',
 
 
        );
}
 
sub graph_postscreen($$)
        {
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
 
                "DEF:pswl=$rrd_post:pswl:AVERAGE",
                "DEF:mpswl=$rrd_post:pswl:MAX",
                "CDEF:rpswl=pswl,60,*",
                "CDEF:rmpswl=mpswl,60,*",
                "CDEF:dpswl=pswl,UN,0,pswl,IF,$step,*",
                "CDEF:spswl=PREV,UN,dpswl,PREV,IF,dpswl,+",
                "AREA:rpswl#$color{pswl}:WHITELISTED             ",
                'GPRINT:spswl:MAX:total\: %15.0lf msgs',
                'GPRINT:rpswl:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmpswl:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:passold=$rrd_post:passold:AVERAGE",
                "DEF:mpassold=$rrd_post:passold:MAX",
                "CDEF:rpassold=passold,60,*",
                "CDEF:dpassold=passold,UN,0,passold,IF,$step,*",
                "CDEF:spassold=PREV,UN,dpassold,PREV,IF,dpassold,+",
                "CDEF:rmpassold=mpassold,60,*",
                "LINE2:rpassold#$color{passold}:PASS OLD                ",
                'GPRINT:spassold:MAX:total\: %15.0lf msgs',
                'GPRINT:rpassold:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmpassold:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:passnew=$rrd_post:passnew:AVERAGE",
                "DEF:mpassnew=$rrd_post:passnew:MAX",
                "CDEF:rpassnew=passnew,60,*",
                "CDEF:dpassnew=passnew,UN,0,passnew,IF,$step,*",
                "CDEF:spassnew=PREV,UN,dpassnew,PREV,IF,dpassnew,+",
                "CDEF:rmpassnew=mpassnew,60,*",
                "LINE2:rpassnew#$color{passnew}:PASS NEW                ",
                'GPRINT:spassnew:MAX:total\: %15.0lf msgs',
                'GPRINT:rpassnew:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmpassnew:MAX:max\: %11.0lf msgs/min\l',
        );
}
 
sub graph_postscreenstats($$)
{
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
                "DEF:psbl=$rrd_post:psbl:AVERAGE",
                "DEF:mpsbl=$rrd_post:psbl:MAX",
                "CDEF:rpsbl=psbl,60,*",
                "CDEF:dpsbl=psbl,UN,0,psbl,IF,$step,*",
                "CDEF:spsbl=PREV,UN,dpsbl,PREV,IF,dpsbl,+",
                "CDEF:rmpsbl=mpsbl,60,*",
                "AREA:rpsbl#$color{psbl}:BLACKLISTED             ",
                'GPRINT:spsbl:MAX:total\: %15.0lf msgs',
                'GPRINT:rpsbl:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmpsbl:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:veto=$rrd_post:veto:AVERAGE",
                "DEF:mveto=$rrd_post:veto:MAX",
                "CDEF:rveto=veto,60,*",
                "CDEF:dveto=veto,UN,0,veto,IF,$step,*",
                "CDEF:sveto=PREV,UN,dveto,PREV,IF,dveto,+",
                "CDEF:rmveto=mveto,60,*",
                "STACK:rveto#$color{veto}:WHITELIST VETO          ",
                'GPRINT:sveto:MAX:total\: %15.0lf msgs',
                'GPRINT:rveto:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmveto:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:pregreet=$rrd_post:pregreet:AVERAGE",
                "DEF:mpregreet=$rrd_post:pregreet:MAX",
                "CDEF:rpregreet=pregreet,60,*",
                "CDEF:dpregreet=pregreet,UN,0,pregreet,IF,$step,*",
                "CDEF:spregreet=PREV,UN,dpregreet,PREV,IF,dpregreet,+",
                "CDEF:rmpregreet=mpregreet,60,*",
                "STACK:rpregreet#$color{pregreet}:PREGREET                ",
                'GPRINT:spregreet:MAX:total\: %15.0lf msgs',
                'GPRINT:rpregreet:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmpregreet:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:dnsbl=$rrd_post:dnsbl:AVERAGE",
                "DEF:mdnsbl=$rrd_post:dnsbl:MAX",
                "CDEF:rdnsbl=dnsbl,60,*",
                "CDEF:ddnsbl=dnsbl,UN,0,dnsbl,IF,$step,*",
                "CDEF:sdnsbl=PREV,UN,ddnsbl,PREV,IF,ddnsbl,+",
                "CDEF:rmdnsbl=mdnsbl,60,*",
                "STACK:rdnsbl#$color{dnsbl}:DNSBL                   ",
                'GPRINT:sdnsbl:MAX:total\: %15.0lf msgs',
                'GPRINT:rdnsbl:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmdnsbl:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:pipelining=$rrd_post:pipelining:AVERAGE",
                "DEF:mpipelining=$rrd_post:pipelining:MAX",
                "CDEF:rpipelining=pipelining,60,*",
                "CDEF:dpipelining=pipelining,UN,0,pipelining,IF,$step,*",
                "CDEF:spipelining=PREV,UN,dpipelining,PREV,IF,dpipelining,+",
                "CDEF:rmpipelining=mpipelining,60,*",
                "STACK:rpipelining#$color{pipelining}:PIPELINING              ",
                'GPRINT:spipelining:MAX:total\: %15.0lf msgs',
                'GPRINT:rpipelining:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmpipelining:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:nonsmtp=$rrd_post:nonsmtp:AVERAGE",
                "DEF:mnonsmtp=$rrd_post:nonsmtp:MAX",
                "CDEF:rnonsmtp=nonsmtp,60,*",
                "CDEF:dnonsmtp=nonsmtp,UN,0,nonsmtp,IF,$step,*",
                "CDEF:snonsmtp=PREV,UN,dnonsmtp,PREV,IF,dnonsmtp,+",
                "CDEF:rmnonsmtp=mnonsmtp,60,*",
                "STACK:rnonsmtp#$color{nonsmtp}:NON SMTP COMMAND        ",
                'GPRINT:snonsmtp:MAX:total\: %15.0lf msgs',
                'GPRINT:rnonsmtp:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmnonsmtp:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:barenewline=$rrd_post:barenewline:AVERAGE",
                "DEF:mbarenewline=$rrd_post:barenewline:MAX",
                "CDEF:rbarenewline=barenewline,60,*",
                "CDEF:dbarenewline=barenewline,UN,0,barenewline,IF,$step,*",
                "CDEF:sbarenewline=PREV,UN,dbarenewline,PREV,IF,dbarenewline,+",
                "CDEF:rmbarenewline=mbarenewline,60,*",
                "STACK:rbarenewline#$color{barenewline}:BARE NEWLINE            ",
                'GPRINT:sbarenewline:MAX:total\: %15.0lf msgs',
                'GPRINT:rbarenewline:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmbarenewline:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:command=$rrd_post:command:AVERAGE",
                "DEF:mcommand=$rrd_post:command:MAX",
                "CDEF:rcommand=command,60,*",
                "CDEF:dcommand=command,UN,0,command,IF,$step,*",
                "CDEF:scommand=PREV,UN,dcommand,PREV,IF,dcommand,+",
                "CDEF:rmcommand=mcommand,60,*",
                "STACK:rcommand#$color{command}:COMMAND LIMITS          ",
                'GPRINT:scommand:MAX:total\: %15.0lf msgs',
                'GPRINT:rcommand:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmcommand:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:hangup=$rrd_post:hangup:AVERAGE",
                "DEF:mhangup=$rrd_post:hangup:MAX",
                "CDEF:rhangup=hangup,60,*",
                "CDEF:dhangup=hangup,UN,0,hangup,IF,$step,*",
                "CDEF:shangup=PREV,UN,dhangup,PREV,IF,dhangup,+",
                "CDEF:rmhangup=mhangup,60,*",
                "STACK:rhangup#$color{hangup}:HUNGUP                  ",
                'GPRINT:shangup:MAX:total\: %15.0lf msgs',
                'GPRINT:rhangup:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmhangup:MAX:max\: %11.0lf msgs/min\l',
        );
}
 
 
 
sub graph_dane($$)
{
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
                "DEF:anonymoustls=$rrd_dane:anonymoustls:AVERAGE",
                "DEF:manonymoustls=$rrd_dane:anonymoustls:MAX",
                "CDEF:ranonymoustls=anonymoustls,60,*",
                "CDEF:danonymoustls=anonymoustls,UN,0,anonymoustls,IF,$step,*",
                "CDEF:sanonymoustls=PREV,UN,danonymoustls,PREV,IF,danonymoustls,+",
                "CDEF:rmanonymoustls=manonymoustls,60,*",
                "AREA:ranonymoustls#$color{anonymoustls}:Out Anonymous TLS       ",
                'GPRINT:sanonymoustls:MAX:total\: %15.0lf msgs',
                'GPRINT:ranonymoustls:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmanonymoustls:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:trustedtls=$rrd_dane:trustedtls:AVERAGE",
                "DEF:mtrustedtls=$rrd_dane:trustedtls:MAX",
                "CDEF:rtrustedtls=trustedtls,60,*",
                "CDEF:dtrustedtls=trustedtls,UN,0,trustedtls,IF,$step,*",
                "CDEF:strustedtls=PREV,UN,dtrustedtls,PREV,IF,dtrustedtls,+",
                "CDEF:rmtrustedtls=mtrustedtls,60,*",
                "STACK:rtrustedtls#$color{trustedtls}:Out Trusted TLS         ",
                'GPRINT:strustedtls:MAX:total\: %15.0lf msgs',
                'GPRINT:rtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmtrustedtls:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:untrustedtls=$rrd_dane:untrustedtls:AVERAGE",
                "DEF:muntrustedtls=$rrd_dane:untrustedtls:MAX",
                "CDEF:runtrustedtls=untrustedtls,60,*",
                "CDEF:duntrustedtls=untrustedtls,UN,0,untrustedtls,IF,$step,*",
                "CDEF:suntrustedtls=PREV,UN,duntrustedtls,PREV,IF,duntrustedtls,+",
                "CDEF:rmuntrustedtls=muntrustedtls,60,*",
                "STACK:runtrustedtls#$color{untrustedtls}:Out Untrusted TLS       ",
                'GPRINT:suntrustedtls:MAX:total\: %15.0lf msgs',
                'GPRINT:runtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmuntrustedtls:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:verifiedtls=$rrd_dane:verifiedtls:AVERAGE",
                "DEF:mverifiedtls=$rrd_dane:verifiedtls:MAX",
                "CDEF:rverifiedtls=verifiedtls,60,*",
                "CDEF:dverifiedtls=verifiedtls,UN,0,verifiedtls,IF,$step,*",
                "CDEF:sverifiedtls=PREV,UN,dverifiedtls,PREV,IF,dverifiedtls,+",
                "CDEF:rmverifiedtls=mverifiedtls,60,*",
                "LINE2:rverifiedtls#$color{verifiedtls}:Out Verified TLS        ",
                'GPRINT:sverifiedtls:MAX:total\: %15.0lf msgs',
                'GPRINT:rverifiedtls:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmverifiedtls:MAX:max\: %11.0lf msgs/min\l',
        );
}
 
 
 
sub graph_spf($$)
{
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
                "DEF:spfpass=$rrd_dmarc:spfpass:AVERAGE",
                "DEF:mspfpass=$rrd_dmarc:spfpass:MAX",
                "CDEF:rspfpass=spfpass,60,*",
                "CDEF:dspfpass=spfpass,UN,0,spfpass,IF,$step,*",
                "CDEF:sspfpass=PREV,UN,dspfpass,PREV,IF,dspfpass,+",
                "CDEF:rmspfpass=mspfpass,60,*",
                "AREA:rspfpass#$color{spfpass}:SPF pass                ",
                'GPRINT:sspfpass:MAX:total\: %15.0lf msgs',
                'GPRINT:rspfpass:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmspfpass:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:spfnone=$rrd_dmarc:spfnone:AVERAGE",
                "DEF:mspfnone=$rrd_dmarc:spfnone:MAX",
                "CDEF:rspfnone=spfnone,60,*",
                "CDEF:dspfnone=spfnone,UN,0,spfnone,IF,$step,*",
                "CDEF:sspfnone=PREV,UN,dspfnone,PREV,IF,dspfnone,+",
                "CDEF:rmspfnone=mspfnone,60,*",
                "LINE2:rspfnone#$color{spfnone}:SPF none                ",
                'GPRINT:sspfnone:MAX:total\: %15.0lf msgs',
                'GPRINT:rspfnone:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmspfnone:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:spffail=$rrd_dmarc:spffail:AVERAGE",
                "DEF:mspffail=$rrd_dmarc:spffail:MAX",
                "CDEF:rspffail=spffail,60,*",
                "CDEF:dspffail=spffail,UN,0,spffail,IF,$step,*",
                "CDEF:sspffail=PREV,UN,dspffail,PREV,IF,dspffail,+",
                "CDEF:rmspffail=mspffail,60,*",
                "LINE2:rspffail#$color{spffail}:SPF fail                ",
                'GPRINT:sspffail:MAX:total\: %15.0lf msgs',
                'GPRINT:rspffail:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmspffail:MAX:max\: %11.0lf msgs/min\l',
        );
}
 
sub graph_dkim($$)
{
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
                "DEF:dkimpass=$rrd_dmarc:dkimpass:AVERAGE",
                "DEF:mdkimpass=$rrd_dmarc:dkimpass:MAX",
                "CDEF:rdkimpass=dkimpass,60,*",
                "CDEF:ddkimpass=dkimpass,UN,0,dkimpass,IF,$step,*",
                "CDEF:sdkimpass=PREV,UN,ddkimpass,PREV,IF,ddkimpass,+",
                "CDEF:rmdkimpass=mdkimpass,60,*",
                "AREA:rdkimpass#$color{dkimpass}:DKIM pass               ",
                'GPRINT:sdkimpass:MAX:total\: %15.0lf msgs',
                'GPRINT:rdkimpass:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmdkimpass:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:dkimnone=$rrd_dmarc:dkimnone:AVERAGE",
                "DEF:mdkimnone=$rrd_dmarc:dkimnone:MAX",
                "CDEF:rdkimnone=dkimnone,60,*",
                "CDEF:ddkimnone=dkimnone,UN,0,dkimnone,IF,$step,*",
                "CDEF:sdkimnone=PREV,UN,ddkimnone,PREV,IF,ddkimnone,+",
                "CDEF:rmdkimnone=mdkimnone,60,*",
                "LINE2:rdkimnone#$color{dkimnone}:DKIM none               ",
                'GPRINT:sdkimnone:MAX:total\: %15.0lf msgs',
                'GPRINT:rdkimnone:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmdkimnone:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:dkimfail=$rrd_dmarc:dkimfail:AVERAGE",
                "DEF:mdkimfail=$rrd_dmarc:dkimfail:MAX",
                "CDEF:rdkimfail=dkimfail,60,*",
                "CDEF:ddkimfail=dkimfail,UN,0,dkimfail,IF,$step,*",
                "CDEF:sdkimfail=PREV,UN,ddkimfail,PREV,IF,ddkimfail,+",
                "CDEF:rmdkimfail=mdkimfail,60,*",
                "LINE2:rdkimfail#$color{dkimfail}:DKIM fail               ",
                'GPRINT:sdkimfail:MAX:total\: %15.0lf msgs',
                'GPRINT:rdkimfail:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmdkimfail:MAX:max\: %11.0lf msgs/min\l',
        );
}
 
sub graph_dmarc($$)
{
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
                "DEF:dmarcpass=$rrd_dmarc:dmarcpass:AVERAGE",
                "DEF:mdmarcpass=$rrd_dmarc:dmarcpass:MAX",
                "CDEF:rdmarcpass=dmarcpass,60,*",
                "CDEF:ddmarcpass=dmarcpass,UN,0,dmarcpass,IF,$step,*",
                "CDEF:sdmarcpass=PREV,UN,ddmarcpass,PREV,IF,ddmarcpass,+",
                "CDEF:rmdmarcpass=mdmarcpass,60,*",
                "AREA:rdmarcpass#$color{dmarcpass}:DMARC pass              ",
                'GPRINT:sdmarcpass:MAX:total\: %15.0lf msgs',
                'GPRINT:rdmarcpass:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmdmarcpass:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:dmarcnone=$rrd_dmarc:dmarcnone:AVERAGE",
                "DEF:mdmarcnone=$rrd_dmarc:dmarcnone:MAX",
                "CDEF:rdmarcnone=dmarcnone,60,*",
                "CDEF:ddmarcnone=dmarcnone,UN,0,dmarcnone,IF,$step,*",
                "CDEF:sdmarcnone=PREV,UN,ddmarcnone,PREV,IF,ddmarcnone,+",
                "CDEF:rmdmarcnone=mdmarcnone,60,*",
                "LINE2:rdmarcnone#$color{dmarcnone}:DMARC none              ",
                'GPRINT:sdmarcnone:MAX:total\: %15.0lf msgs',
                'GPRINT:rdmarcnone:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmdmarcnone:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:dmarcfail=$rrd_dmarc:dmarcfail:AVERAGE",
                "DEF:mdmarcfail=$rrd_dmarc:dmarcfail:MAX",
                "CDEF:rdmarcfail=dmarcfail,60,*",
                "CDEF:ddmarcfail=dmarcfail,UN,0,dmarcfail,IF,$step,*",
                "CDEF:sdmarcfail=PREV,UN,ddmarcfail,PREV,IF,ddmarcfail,+",
                "CDEF:rmdmarcfail=mdmarcfail,60,*",
                "LINE2:rdmarcfail#$color{dmarcfail}:DMARC fail              ",
                'GPRINT:sdmarcfail:MAX:total\: %15.0lf msgs',
                'GPRINT:rdmarcfail:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:rmdmarcfail:MAX:max\: %11.0lf msgs/min\l',
        );
}
 
 
 
sub graph_queue($$)
        {
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,
                "DEF:deferred=$rrd_queue:deferred:AVERAGE",
                "AREA:deferred#$color{deferred}:Deferred                ",
                'GPRINT:deferred:MAX:total\: %15.0lf msgs',
                'GPRINT:deferred:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:deferred:MAX:max\: %11.0lf msgs/min\l',
 
                "DEF:active=$rrd_queue:active:AVERAGE",
                "LINE2:active#$color{active}:Active+Incoming+Maildrop",
                'GPRINT:active:MAX:total\: %15.0lf msgs',
                'GPRINT:active:AVERAGE:avg\: %12.2lf msgs/min',
                'GPRINT:active:MAX:max\: %11.0lf msgs/min\l',
        );
}
 
 
 
 
 
sub print_html()
{
        print "Content-Type: text/html\n\n";
 
        print <<HEADER;
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>Mailserver Statistiken auf $host</title>
  <meta http-equiv="Refresh" content="300" />
  <meta http-equiv="Pragma" content="no-cache" />
  <link rel="stylesheet" href="mailgraph.css" type="text/css" />
 </head>
 <body>
HEADER
 
        print "<h1>Mail statistics for $host</h1>\n";
 
        print "<ul id=\"jump\">\n";
        for my $n (0..$#graphs) {
                print "  <li><a href=\"#G$n\">$graphs[$n]{title}</a>&nbsp;</li>\n";
        }
        print "</ul>\n";
 
        for my $n (0..$#graphs) {
 
                print "<h2 id=\"G$n\">$graphs[$n]{title}</h2>\n";
                print "<h4><center>Mail Ein und -Ausgang</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-n\" alt=\"mailgraph - received and sent\"/></p>\n";
                print "<h4><center>geblockte Nachrichten</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-e\" alt=\"mailgraph - blocked\"/></p>\n";
                print "<h4><center>Greylisting &Uuml;bersicht</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-g\" alt=\"mailgraph - greylist\"/></p>\n";
                print "<h4><center>Greylisting Detailansicht</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-d\" alt=\"mailgraph - greystats\"/></p>\n";
                print "<h4><center>Postscreen &Uuml;bersicht</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-v\" alt=\"mailgraph - postscreen\"/></p>\n";
                print "<h4><center>Postscreen Detailansicht</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-w\" alt=\"mailgraph - postscreenstats\"/></p>\n";
                print "<h4><center>&Uuml;bersicht Mail-Queues</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-q\" alt=\"mailgraph - mailqueues\"/></p>\n";
                print "<h4><center>DANE / TLSA Verbindungen</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-t\" alt=\"mailgraph - dane checked\"/></p>\n";
                print "<h4><center>Sender policy Framework - SPF-Pr&uuml;fungen</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-f\" alt=\"mailgraph - spf checked\"/></p>\n";
                print "<h4><center>DomainKeys Identified Mail - DKIM-Pr&uuml;fungen</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-m\" alt=\"mailgraph - dkim checked\"/></p>\n";
                print "<h4><center>Domain-based Message Authentication, Reporting & Conformance - DMARC-Pr&uuml;fungen</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-c\" alt=\"mailgraph - dmarc checked\"/></p>\n";
        }
 
        print <<FOOTER;
 <hr/>
  <table border="0" style="font-size:12px" width="900">
   <colgroup>
    <col width="225">
    <col width="430">
    <col width="125">
   </colgroup>
   <tr class="row0">
    <td class="col0 leftalign">
     <a href="http://dokuwiki.nausch.org/doku.php/centos:mail_c7:mta_13?&#mailgraph_nextgeneration">Mailgraph(-ng) </a>$VERSION by 
     <a href="mailto:django@mailserver.guru?subject=Mailgraph-NG%20for%20my%20Mailserver">Django</a> based on
    </td>
    <td>
     <a href="http://david.schweikert.ch/">David Schweikert's</a> <a href="http://mailgraph.schweikert.ch/">Mailgraph</a>, 
     <a href="http://www.gichenbacher.de/kontakt">Markus Neubauer's </a> 
     <a href="http://www.std-soft.com/bfaq/46-k-faq-server/117-greygraph-mail-statistik.html">Greygraph</a>,
    </td>
    <td class="col2 rightalign" rowspan="3"> 
     <a href="http://oss.oetiker.ch/rrdtool/"><img src="rrdtool-3dlogo.png" alt="" width="97" height="43" align="right" align="middle"/></a>
    </td>
   </tr>
   <tr class="row1">
    <td class="col0 leftalign">          
    </td>
    <td class="col1 leftalign">
     <a href="http://www.arschkrebs.de/">Ralf Hildebrandt's </a><a href="http://www.arschkrebs.de/postfix/queuegraph">Queuegraph</a> and
     <a href="https://www.kernel-error.de/">Sebastian van de Meer's </a> <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt">mailgraphpatch 1</a> and 
     <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt-2">mailgraphpatch 2</a>
    </td>
   </tr>
  </table>
 </body>
</html>
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/mailgraph_$1.png";
                        graph($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-e$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_err.png";
                        graph_virus($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-g$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_greylist.png";
                        graph_greylist($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-d$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_greystats.png";
                        graph_greystats($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-v$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_postscreen.png";
                        graph_postscreen($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-w$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_postscreenstats.png";
                        graph_postscreenstats($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-q$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_queue.png";
                        graph_queue($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-t$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dane.png";
                        graph_dane($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-f$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_spf.png";
                        graph_spf($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-m$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dkim.png";
                        graph_dkim($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-c$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dmarc.png";
                        graph_dmarc($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                else {
                        die "ERROR: invalid argument\n";
                }
        }
        else {
                print_html;
        }
}
 
main;

/usr/share/mailgraph/mailgraph.css

Änderungen am Aussehen im Punkto Schriften und Farben der Webseite können auch mit Hilfe der CSS-Datei vorgenommen werden.

 # vim /usr/share/mailgraph/mailgraph.css
/usr/share/mailgraph/mailgraph.css
*     { margin: 0; padding: 0 }
body  { width: 900px; background-color: white;
        font-family: sans-serif;
        font-size: 12pt;
        margin: 5px }
h1    { margin-top: 20px; margin-bottom: 30px;
        text-align: center }
h2    { background-color: #ddd;
        padding: 2px 0 2px 4px }
hr    { height: 1px;
        border: 0;
        border-top: 1px solid #aaa }
table { border: 0px; width: 100% }
img   { border: 0 }
a     { text-decoration: none; color: #00e }
a:hover  { text-decoration: underline; }
#jump    { margin: 0 0 10px 4px }
#jump li { list-style: none; display: inline;
           font-size: 90%; }
#jump li:after            { content: "|"; }
#jump li:last-child:after { content: ""; }

/usr/sbin/mailgraph

Der Vollständigkeit halber ist nachfolgend das Perl-Script zum erstellen und Befüllen der RRD-Datenbankfiles aufgeführt. Eine Konfiguration des scriptes ist aber nicht vorgesehen und notwendig!

 # less /usr/sbin/mailgraph
/usr/sbin/mailgraph
#!/usr/bin/perl -w
 
# mailgraph -- an rrdtool frontend for mail statistics
# copyright (c) 2000-2007 ETH Zurich
# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
# copyright (c) 2011      Markus Neubauer <neubauer@std-service.com>
# copyright (c) 2014-2015 Django <django@mailserver.guru>
# released under the GNU General Public License
# with spf-, dkim-, dmarc, and dane-patch Sebastian van de Meer <kernel-error@kernel-error.de>
 
######## Parse::Syslog 1.09 (automatically embedded) ########
package Parse::Syslog;
use Carp;
use Symbol;
use Time::Local;
use IO::File;
use strict;
use vars qw($VERSION);
my %months_map = (
    'Jan' => 0, 'Feb' => 1, 'Mar' => 2,
    'Apr' => 3, 'May' => 4, 'Jun' => 5,
    'Jul' => 6, 'Aug' => 7, 'Sep' => 8,
    'Oct' => 9, 'Nov' =>10, 'Dec' =>11,
    'jan' => 0, 'feb' => 1, 'mar' => 2,
    'apr' => 3, 'may' => 4, 'jun' => 5,
    'jul' => 6, 'aug' => 7, 'sep' => 8,
    'oct' => 9, 'nov' =>10, 'dec' =>11,
);
sub is_dst_switch($$$)
{
    my ($self, $t, $time) = @_;
    # calculate the time in one hour and see if the difference is 3600 seconds.
    # if not, we are in a dst-switch hour
    # note that right now we only support 1-hour dst offsets
    # cache the result
    if(defined $self->{is_dst_switch_last_hour} and
        $self->{is_dst_switch_last_hour} == $t->[3]<<5+$t->[2]) {
        return @{$self->{is_dst_switch_result}};
    }
    # calculate a number out of the day and hour to identify the hour
    $self->{is_dst_switch_last_hour} = $t->[3]<<5+$t->[2];
    # calculating hour+1 (below) is a problem if the hour is 23. as far as I
    # know, nobody does the DST switch at this time, so just assume it isn't
    # DST switch if the hour is 23.
    if($t->[2]==23) {
        @{$self->{is_dst_switch_result}} = (0, undef);
        return @{$self->{is_dst_switch_result}};
    }
    # let's see the timestamp in one hour
    # 0: sec, 1: min, 2: h, 3: day, 4: month, 5: year
    my $time_plus_1h = timelocal($t->[0], $t->[1], $t->[2]+1, $t->[3], $t->[4], $t->[5]);
    if($time_plus_1h - $time > 4000) {
        @{$self->{is_dst_switch_result}} = (3600, $time-$time%3600+3600);
    }
    else {
        @{$self->{is_dst_switch_result}} = (0, undef);
    }
    return @{$self->{is_dst_switch_result}};
}
# fast timelocal, cache minute's timestamp
# don't cache more than minute because of daylight saving time switch
# 0: sec, 1: min, 2: h, 3: day, 4: month, 5: year
sub str2time($$$$$$$$)
{
    my $self = shift @_;
    my $GMT = pop @_;
    my $lastmin = $self->{str2time_lastmin};
    if(defined $lastmin and
        $lastmin->[0] == $_[1] and
        $lastmin->[1] == $_[2] and
        $lastmin->[2] == $_[3] and
        $lastmin->[3] == $_[4] and
        $lastmin->[4] == $_[5])
    {
        $self->{last_time} = $self->{str2time_lastmin_time} + $_[0];
        return $self->{last_time} + ($self->{dst_comp}||0);
    }
    my $time;
    if($GMT) {
        $time = timegm(@_);
    }
    else {
        $time = timelocal(@_);
    }
    # compensate for DST-switch
    # - if a timewarp is detected (1:00 -> 1:30 -> 1:00):
    # - test if we are in a DST-switch-hour
    # - compensate if yes
    # note that we assume that the DST-switch goes like this:
    # time   1:00  1:30  2:00  2:30  2:00  2:30  3:00  3:30
    # stamp   1     2     3     4     3     3     7     8  
    # comp.   0     0     0     0     2     2     0     0
    # result  1     2     3     4     5     6     7     8
    # old Time::Local versions behave differently (1 2  5 6 5 6 7 8)
    if(!$GMT and !defined $self->{dst_comp} and
        defined $self->{last_time} and
        $self->{last_time}-$time > 1200 and
        $self->{last_time}-$time < 3600)
    {
        my ($off, $until) = $self->is_dst_switch(\@_, $time);
        if($off) {
            $self->{dst_comp} = $off;
            $self->{dst_comp_until} = $until;
        }
    }
    if(defined $self->{dst_comp_until} and $time > $self->{dst_comp_until}) {
        delete $self->{dst_comp};
        delete $self->{dst_comp_until};
    }
    $self->{str2time_lastmin} = [ @_[1..5] ];
    $self->{str2time_lastmin_time} = $time-$_[0];
    $self->{last_time} = $time;
    return $time+($self->{dst_comp}||0);
}
sub _use_locale($)
{
    use POSIX qw(locale_h strftime);
    my $old_locale = setlocale(LC_TIME);
    for my $locale (@_) {
        croak "new(): wrong 'locale' value: '$locale'" unless setlocale(LC_TIME, $locale);
        for my $month (0..11) {
            $months_map{strftime("%b", 0, 0, 0, 1, $month, 96)} = $month;
        }
    }
    setlocale(LC_TIME, $old_locale);
}
sub new($$;%)
{
    my ($class, $file, %data) = @_;
    croak "new() requires one argument: file" unless defined $file;
    %data = () unless %data;
    if(not defined $data{year}) {
        $data{year} = (localtime(time))[5]+1900;
    }
    $data{type} = 'syslog' unless defined $data{type};
    $data{_repeat}=0;
    if(UNIVERSAL::isa($file, 'IO::Handle')) {
        $data{file} = $file;
    }
    elsif(UNIVERSAL::isa($file, 'File::Tail')) {
        $data{file} = $file;
        $data{filetail}=1;
    }
    elsif(! ref $file) {
        if($file eq '-') {
            my $io = new IO::Handle;
            $data{file} = $io->fdopen(fileno(STDIN),"r");
        }
        else {
            $data{file} = new IO::File($file, "<");
            defined $data{file} or croak "can't open $file: $!";
        }
    }
    else {
        croak "argument must be either a file-name or an IO::Handle object.";
    }
    if(defined $data{locale}) {
        if(ref $data{locale} eq 'ARRAY') {
            _use_locale @{$data{locale}};
        }
        elsif(ref $data{locale} eq '') {
            _use_locale $data{locale};
        }
        else {
            croak "'locale' parameter must be scalar or array of scalars";
        }
    }
    return bless \%data, $class;
}
sub _year_increment($$)
{
    my ($self, $mon) = @_;
    # year change
    if($mon==0) {
        $self->{year}++ if defined $self->{_last_mon} and $self->{_last_mon} == 11;
        $self->{enable_year_decrement} = 1;
    }
    elsif($mon == 11) {
        if($self->{enable_year_decrement}) {
            $self->{year}-- if defined $self->{_last_mon} and $self->{_last_mon} != 11;
        }
    }
    else {
        $self->{enable_year_decrement} = 0;
    }
    $self->{_last_mon} = $mon;
}
sub _next_line($)
{
    my $self = shift;
    my $f = $self->{file};
    if(defined $self->{filetail}) {
        return $f->read;
    }
    else {
        return $f->getline;
    }
}
sub _next_syslog($)
{
    my ($self) = @_;
    while($self->{_repeat}>0) {
        $self->{_repeat}--;
        return $self->{_repeat_data};
    }
    my $file = $self->{file};
    line: while(defined (my $str = $self->_next_line)) {
        # date, time and host 
        $str =~ /^
            (\S{3})\s+(\d+)      # date  -- 1, 2
            \s
            (\d+):(\d+):(\d+)    # time  -- 3, 4, 5
            (?:\s<\w+\.\w+>)?    # FreeBSD's verbose-mode
            \s
            ([-\w\.\@:]+)        # host  -- 6
            \s+
            (?:\[LOG_[A-Z]+\]\s+)?  # FreeBSD
            (.*)                 # text  -- 7
            $/x or do
        {
            warn "WARNING: line not in syslog format: $str";
            next line;
        };
        my $mon = $months_map{$1};
        defined $mon or croak "unknown month $1\n";
        $self->_year_increment($mon);
        # convert to unix time
        my $time = $self->str2time($5,$4,$3,$2,$mon,$self->{year}-1900,$self->{GMT});
        if(not $self->{allow_future}) {
            # accept maximum one day in the present future
            if($time - time > 86400) {
                warn "WARNING: ignoring future date in syslog line: $str";
                next line;
            }
        }
        my ($host, $text) = ($6, $7);
        # last message repeated ... times
        if($text =~ /^(?:last message repeated|above message repeats) (\d+) time/) {
            next line if defined $self->{repeat} and not $self->{repeat};
            next line if not defined $self->{_last_data}{$host};
            $1 > 0 or do {
                warn "WARNING: last message repeated 0 or less times??\n";
                next line;
            };
            $self->{_repeat}=$1-1;
            $self->{_repeat_data}=$self->{_last_data}{$host};
            return $self->{_last_data}{$host};
        }
        # marks
        next if $text eq '-- MARK --';
        # some systems send over the network their
        # hostname prefixed to the text. strip that.
        $text =~ s/^$host\s+//;
        # discard ':' in HP-UX 'su' entries like this:
        # Apr 24 19:09:40 remedy : su : + tty?? root-oracle
        $text =~ s/^:\s+//;
        $text =~ /^
            ([^:]+?)        # program   -- 1
            (?:\[(\d+)\])?  # PID       -- 2
            :\s+
            (?:\[ID\ (\d+)\ ([a-z0-9]+)\.([a-z]+)\]\ )?   # Solaris 8 "message id" -- 3, 4, 5
            (.*)            # text      -- 6
            $/x or do
        {
            warn "WARNING: line not in syslog format: $str";
            next line;
        };
        if($self->{arrayref}) {
            $self->{_last_data}{$host} = [
                $time,  # 0: timestamp 
                $host,  # 1: host      
                $1,     # 2: program   
                $2,     # 3: pid       
                $6,     # 4: text      
                ];
        }
        else {
            $self->{_last_data}{$host} = {
                timestamp => $time,
                host      => $host,
                program   => $1,
                pid       => $2,
                msgid     => $3,
                facility  => $4,
                level     => $5,
                text      => $6,
            };
        }
        return $self->{_last_data}{$host};
    }
    return undef;
}
sub _next_metalog($)
{
    my ($self) = @_;
    my $file = $self->{file};
    line: while(my $str = $self->_next_line) {
        # date, time and host 
        $str =~ /^
            (\S{3})\s+(\d+)   # date  -- 1, 2
            \s
            (\d+):(\d+):(\d+) # time  -- 3, 4, 5
                              # host is not logged
            \s+
            (.*)              # text  -- 6
            $/x or do
        {
            warn "WARNING: line not in metalog format: $str";
            next line;
        };
        my $mon = $months_map{$1};
        defined $mon or croak "unknown month $1\n";
        $self->_year_increment($mon);
        # convert to unix time
        my $time = $self->str2time($5,$4,$3,$2,$mon,$self->{year}-1900,$self->{GMT});
        my $text = $6;
        $text =~ /^
            \[(.*?)\]        # program   -- 1
                             # no PID
            \s+
            (.*)             # text      -- 2
            $/x or do
        {
            warn "WARNING: text line not in metalog format: $text ($str)";
            next line;
        };
        if($self->{arrayref}) {
            return [
                $time,  # 0: timestamp 
                'localhost',  # 1: host      
                $1,     # 2: program   
                undef,  # 3: (no) pid
                $2,     # 4: text
                ];
        }
        else {
            return {
                timestamp => $time,
                host      => 'localhost',
                program   => $1,
                text      => $2,
            };
        }
    }
    return undef;
}
sub next($)
{
    my ($self) = @_;
    if($self->{type} eq 'syslog') {
        return $self->_next_syslog();
    }
    elsif($self->{type} eq 'metalog') {
        return $self->_next_metalog();
    }
    croak "Internal error: unknown type: $self->{type}";
}
 
#####################################################################
#####################################################################
#####################################################################
 
use RRDs;
 
use strict;
use File::Tail;
use Getopt::Long;
use POSIX 'setsid';
 
my $VERSION = "1.15";
 
# config
my $rrdstep           = 60;
my $xpoints           = 540;
my $points_per_sample = 3;
 
my $daemon_logfile = '/var/log/mailgraph.log';
my $daemon_pidfile = '/var/run/mailgraph.pid';
my $daemon_rrd_dir = '/var/log';
 
# global variables
my $logfile;
my $rrd            = "mailgraph.rrd";
my $rrd_virus      = "mailgraph_virus.rrd";
my $rrd_grey       = "mailgraph_grey.rrd";
my $rrd_dane       = "mailgraph_dane.rrd";
my $rrd_dmarc      = "mailgraph_dmarc.rrd";
my $rrd_post       = "mailgraph_post.rrd";
my $year;
my $this_minute;
my %sum = ( sent => 0, received => 0, bounced => 0, rejected => 0, virus => 0, spam => 0, greylisted => 0, delayed => 0,whitelist => 0, new => 0, awl => 0, early => 0, reconnectok => 0, anonymoustls => 0, trustedtls => 0, untrustedtls => 0, verifiedtls => 0, spfnone => 0, spffail => 0, spfpass => 0, dmarcnone => 0, dmarcfail => 0, dmarcpass => 0, dkimnone => 0, dkimfail => 0, dkimpass => 0, pswl => 0, psbl => 0, passold => 0, veto => 0, pregreet => 0, dnsbl => 0, pipelining =>, nonsmtp => 0, barenewline => 0, command => 0, hangup =>, passnew => 0);
my $rrd_inited=0;
 
my %opt = ();
 
# prototypes
sub daemonize();
sub process_line($);
#4
sub event_sent($);
sub event_received($);
sub event_bounced($);
sub event_rejected($);
#2
sub event_virus($);
sub event_spam($);
#7
sub event_greylisted($);
sub event_delayed($);
sub event_whitelist($);
sub event_new($);
sub event_awl($);
sub event_early($);
sub event_reconnectok($);
#4
sub event_anonymoustls($);
sub event_trustedtls($);
sub event_untrustedtls($);
sub event_verifiedtls($);
#9
sub event_spfnone($);
sub event_spffail($);
sub event_spfpass($);
sub event_dmarcnone($);
sub event_dmarcfail($);
sub event_dmarcpass($);
sub event_dkimnone($);
sub event_dkimfail($);
sub event_dkimpass($);
#12
sub event_pswl($);
sub event_psbl($);
sub event_passold($);
sub event_veto($);
sub event_pregreet($);
sub event_dnsbl($);
sub event_pipelining($);
sub event_nonsmtp($);
sub event_barenewline($);
sub event_command($);
sub event_hangup($);
sub event_passnew($);
#
sub init_rrd($);
sub update($);
 
sub usage
{
        print "usage: mailgraph [*options*]\n\n";
        print "  -h, --help         display this help and exit\n";
        print "  -v, --verbose      be verbose about what you do\n";
        print "  -V, --version      output version information and exit\n";
        print "  -c, --cat          causes the logfile to be only read and not monitored\n";
        print "  -l, --logfile f    monitor logfile f instead of /var/log/syslog\n";
        print "  -t, --logtype t    set logfile's type (default: syslog)\n";
        print "  -y, --year         starting year of the log file (default: current year)\n";
        print "      --host=HOST    use only entries for HOST (regexp) in syslog\n";
        print "  -d, --daemon       start in the background\n";
        print "  --daemon-pid=FILE  write PID to FILE instead of /var/run/mailgraph.pid\n";
        print "  --daemon-rrd=DIR   write RRDs to DIR instead of /var/log\n";
        print "  --daemon-log=FILE  write verbose-log to FILE instead of /var/log/mailgraph.log\n";
        print "  --ignore-localhost ignore mail to/from localhost (used for virus scanner)\n";
        print "  --ignore-host=HOST ignore mail to/from HOST regexp (used for virus scanner)\n";
        print "  --no-mail-rrd      no update mail rrd\n";
        print "  --no-virus-rrd     no update virus rrd\n";
        print "  --no-grey-rrd      no update grey rrd\n";
        print "  --no-dmarc-rrd     no update dmarc rrd\n";
        print "  --no-dane-rrd      no update dane rrd\n";
        print "  --no-post-rrd      no update post rrd\n";
        print "  --only-mail-rrd    update only the mail rrd\n";
        print "  --only-virus-rrd   update only the virus rrd\n";
        print "  --rrd-name=NAME    use NAME.rrd and NAME_virus.rrd for the rrd files\n";
        print "  --rbl-is-spam      count rbl rejects as spam\n";
        print "  --virbl-is-virus   count virbl rejects as viruses\n";
 
        exit;
}
 
sub main
{
        Getopt::Long::Configure('no_ignore_case');
        GetOptions(\%opt, 'help|h', 'cat|c', 'logfile|l=s', 'logtype|t=s', 'version|V',
                'year|y=i', 'host=s', 'verbose|v', 'daemon|d!',
                'daemon_pid|daemon-pid=s', 'daemon_rrd|daemon-rrd=s',
                'daemon_log|daemon-log=s', 'ignore-localhost!', 'ignore-host=s@',
                'no-mail-rrd', 'no-virus-rrd', 'no-grey-rrd', 'no-dane-rrd', 
                'no-post', 'no-dmarc-rrd', 'only-mail-rrd', 'only-virus-rrd', 
                'rrd_name|rrd-name=s', 'rbl-is-spam', 'virbl-is-virus'
                ) or exit(1);
        usage if $opt{help};
 
        if($opt{version}) {
                print "mailgraph $VERSION by david\@schweikert.ch and django\@mailserver.guru\n";
                exit;
        }
 
        $daemon_pidfile = $opt{daemon_pid} if defined $opt{daemon_pid};
        $daemon_logfile = $opt{daemon_log} if defined $opt{daemon_log};
        $daemon_rrd_dir = $opt{daemon_rrd} if defined $opt{daemon_rrd};
        $rrd            = $opt{rrd_name}.".rrd" if defined $opt{rrd_name};
        $rrd_virus      = $opt{rrd_name}."_virus.rrd" if defined $opt{rrd_name};
        $rrd_grey       = $opt{rrd_name}."_grey.rrd" if defined $opt{rrd_name};
        $rrd_dane       = $opt{rrd_name}."_dane.rrd" if defined $opt{rrd_name};
        $rrd_dmarc      = $opt{rrd_name}."_dmarc.rrd" if defined $opt{rrd_name};
        $rrd_post       = $opt{rrd_name}."_post.rrd" if defined $opt{rrd_name};
 
 
        # compile --ignore-host regexps
        if(defined $opt{'ignore-host'}) {
                for my $ih (@{$opt{'ignore-host'}}) {
                        push @{$opt{'ignore-host-re'}}, qr{\brelay=[^\s,]*$ih}i;
                }
        }
 
        if($opt{daemon} or $opt{daemon_rrd}) {
                chdir $daemon_rrd_dir or die "mailgraph: can't chdir to $daemon_rrd_dir: $!";
                -w $daemon_rrd_dir or die "mailgraph: can't write to $daemon_rrd_dir\n";
        }
 
        daemonize if $opt{daemon};
 
        my $logfile = defined $opt{logfile} ? $opt{logfile} : '/var/log/messages';
        my $file;
        if($opt{cat}) {
                $file = $logfile;
        }
        else {
                $file = File::Tail->new(name=>$logfile, tail=>-1);
        }
        my $parser = new Parse::Syslog($file, year => $opt{year}, arrayref => 1,
                type => defined $opt{logtype} ? $opt{logtype} : 'syslog');
 
        if(not defined $opt{host}) {
                while(my $sl = $parser->next) {
                        process_line($sl);
                }
        }
        else {
                my $host = qr/^$opt{host}$/i;
                while(my $sl = $parser->next) {
                        process_line($sl) if $sl->[1] =~ $host;
                }
        }
}
 
sub daemonize()
{
        open STDIN, '/dev/null' or die "mailgraph: can't read /dev/null: $!";
        if($opt{verbose}) {
                open STDOUT, ">>$daemon_logfile"
                        or die "mailgraph: can't write to $daemon_logfile: $!";
        }
        else {
                open STDOUT, '>/dev/null'
                        or die "mailgraph: can't write to /dev/null: $!";
        }
        defined(my $pid = fork) or die "mailgraph: can't fork: $!";
        if($pid) {
                # parent
                open PIDFILE, ">$daemon_pidfile"
                        or die "mailgraph: can't write to $daemon_pidfile: $!\n";
                print PIDFILE "$pid\n";
                close(PIDFILE);
                exit;
        }
        # child
        setsid                  or die "mailgraph: can't start a new session: $!";
        open STDERR, '>&STDOUT' or die "mailgraph: can't dup stdout: $!";
}
 
sub init_rrd($)
{
        my $m = shift;
        my $rows = $xpoints/$points_per_sample;
        my $realrows = int($rows*1.1); # ensure that the full range is covered
        my $day_steps = int(3600*24 / ($rrdstep*$rows));
        # use multiples, otherwise rrdtool could choose the wrong RRA
        my $week_steps = $day_steps*7;
        my $month_steps = $week_steps*5;
        my $year_steps = $month_steps*12;
 
        # mail rrd
        if(! -f $rrd and ! $opt{'only-virus-rrd'}) {
                RRDs::create($rrd, '--start', $m, '--step', $rrdstep,
                                'DS:sent:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:recv:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:bounced:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:rejected: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
                                );
                $this_minute = $m;
        }
        elsif(-f $rrd) {
                $this_minute = RRDs::last($rrd) + $rrdstep;
        }
 
        # virus rrd
        if(! -f $rrd_virus and ! $opt{'only-mail-rrd'}) {
                RRDs::create($rrd_virus, '--start', $m, '--step', $rrdstep,
                                'DS:virus:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:spam: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_virus and ! defined $rrd_virus) {
                $this_minute = RRDs::last($rrd_virus) + $rrdstep;
        }
 
        # grey rrd
        if(! -f $rrd_grey and ! $opt{'no-grey-rrd'}) {
                RRDs::create($rrd_grey, '--start', $m, '--step', $rrdstep,
                                'DS:greylisted:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:delayed:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:whitelist:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:new:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:awl:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:early:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:reconnectok: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
                                );
                        $this_minute = $m;
        }
        elsif(-f $rrd_grey and ! defined $rrd_grey) {
                $this_minute = RRDs::last($rrd_grey) + $rrdstep;
        }
 
        # dane rrd
        if(! -f $rrd_dane and ! $opt{'no-dane-rrd'}) {
                RRDs::create($rrd_dane, '--start', $m, '--step', $rrdstep,
                                'DS:anonymoustls:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:trustedtls:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:untrustedtls:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:verifiedtls: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
                                );
                        $this_minute = $m;
        }
        elsif(-f $rrd_dane and ! defined $rrd_dane) {
                $this_minute = RRDs::last($rrd_dane) + $rrdstep;
        }
 
        # dmarc rrd
        if(! -f $rrd_dmarc and ! $opt{'no-dmarc-rrd'}) {
                RRDs::create($rrd_dmarc, '--start', $m, '--step', $rrdstep,
                                'DS:spfnone:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:spffail:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:spfpass:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:dmarcnone:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:dmarcfail:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:dmarcpass:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:dkimnone:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:dkimfail:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:dkimpass: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
                                );
                        $this_minute = $m;
        }
        elsif(-f $rrd_dmarc and ! defined $rrd_dmarc) {
                $this_minute = RRDs::last($rrd_dmarc) + $rrdstep;
        }
 
        # post rrd
        if(! -f $rrd_post and ! $opt{'no-post-rrd'}) {
                RRDs::create($rrd_post, '--start', $m, '--step', $rrdstep,
                                'DS:pswl:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:psbl:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:passold:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:veto:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:pregreet:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:dnsbl:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:pipelining:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:nonsmtp:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:barenewline:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:command:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:hangup:ABSOLUTE:'.($rrdstep*2).':0:U',
                                'DS:passnew: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
                                );
                        $this_minute = $m;
        }
        elsif(-f $rrd_post and ! defined $rrd_post) {
                $this_minute = RRDs::last($rrd_post) + $rrdstep;
        }
 
        $rrd_inited=1;
}
 
sub process_line($)
{
        my $sl = shift;
        my $time = $sl->[0];
        my $prog = $sl->[2];
        my $text = $sl->[4];
 
        if($prog =~ /^postfix\/(.*)/) {
                my $prog = $1;
                if($prog eq 'smtp') {
                        if($text =~ /\bstatus=sent\b/) {
                                return if $opt{'ignore-localhost'} and
                                        $text =~ /\brelay=[^\s\[]*\[127\.0\.0\.1\]/;
                                if(defined $opt{'ignore-host-re'}) {
                                        for my $ih (@{$opt{'ignore-host-re'}}) {
                                              warn "MATCH! $text\n" if $text =~ $ih;
                                              return if $text =~ $ih;
                                        }
                                }
                                event($time, 'sent');
                        }
                        elsif($text =~ /\bstatus=bounced\b/) {
                                event($time, 'bounced');
                        }
                        elsif($text =~ /Anonymous TLS connection established to/) {
                                event($time, 'anonymoustls');
                        }
                        elsif($text =~ /Trusted TLS connection established to/) {
                                event($time, 'trustedtls');
                        }
                        elsif($text =~ /Untrusted TLS connection established to/) {
                                event($time, 'untrustedtls');
                        }
                        elsif($text =~ /Verified TLS connection established to/) {
                                event($time, 'verifiedtls');
                        }
                }
                elsif($prog eq 'local') {
                        if($text =~ /\bstatus=bounced\b/) {
                                event($time, 'bounced');
                        }
                }
                elsif($prog eq 'smtpd') {
                        if($text =~ /^[0-9A-Z]+: client=(\S+)/) {
                                my $client = $1;
                                return if $opt{'ignore-localhost'} and
                                        $client =~ /\[127\.0\.0\.1\]$/;
                                return if $opt{'ignore-host'} and
                                        $client =~ /$opt{'ignore-host'}/oi;
                                event($time, 'received');
                        }
                        elsif($opt{'virbl-is-virus'} and $text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: .*: 554.* blocked using virbl.dnsbl.bit.nl/) {
                                event($time, 'virus');
                        }
                        elsif($opt{'rbl-is-spam'} and $text    =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: .*: 554.* blocked using/) {
                                event($time, 'spam');
                        }
                        #elsif($text =~ /Greylisted/) {
                        #       event($time, 'greylisted');
                        #}
                        elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?reject: /) {
                                event($time, 'rejected');
                        }
                        elsif($text =~ /^(?:[0-9A-Z]+: |NOQUEUE: )?milter-reject: /) {
                                if($text =~ /Blocked by SpamAssassin/) {
                                        event($time, 'spam');
                                }
                                else {
                                        event($time, 'rejected');
                                }
                        }
                }
                elsif($prog eq 'error') {
                        if($text =~ /\bstatus=bounced\b/) {
                                event($time, 'bounced');
                        }
                }
                elsif($prog eq 'cleanup') {
                        if($text =~ /^[0-9A-Z]+: (?:reject|discard): /) {
                                event($time, 'rejected');
                        }
                }
                elsif($prog eq 'postscreen') {
                        if($text =~ /WHITELISTED/) {
                                event($time, 'pswl');
                        }
                        if($text =~ /BLACKLISTED/) {
                                event($time, 'psbl');
                        }
                        if($text =~ /BLACKLISTED/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /PASS OLD/) {
                                event($time, 'passold');
                        }
                        if($text =~ /WHITELIST VETO/) {
                                event($time, 'veto');
                        }
                        if($text =~ /WHITELIST VETO/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /PREGREET/) {
                                event($time, 'pregreet');
                        }
                        if($text =~ /PREGREET/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /DNSBL/) {
                                event($time, 'dnsbl');
                        }
                        if($text =~ /DNSBL/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /COMMAND PIPELINING/) {
                                event($time, 'pipelining');
                        }
                        if($text =~ /COMMAND PIPELINING/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /NON-SMTP COMMAND/) {
                                event($time, 'nonsmtp');
                        }
                        if($text =~ /NON-SMTP COMMAND/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /BARE NEWLINE/) {
                                event($time, 'barenewline');
                        }
                        if($text =~ /BARE NEWLINE/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /COMMAND TIME LIMIT/) {
                                event($time, 'command');
                        }
                        if($text =~ /COMMAND TIME LIMIT/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /COMMAND COUNT LIMIT/) {
                                event($time, 'command');
                        }
                        if($text =~ /COMMAND COUNT LIMIT/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /COMMAND LENGTH LIMIT/) {
                                event($time, 'command');
                        }
                        if($text =~ /COMMAND LENGTH LIMIT/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /HANGUP/) {
                                event($time, 'hangup');
                        }
                        if($text =~ /HANGUP/) {
                                event($time, 'rejected');
                        }
                        if($text =~ /PASS NEW/) {
                                event($time, 'passnew');
                        }
                }
        }
        elsif($prog eq 'sendmail' or $prog eq 'sm-mta') {
                if($text =~ /\bmailer=local\b/ ) {
                        event($time, 'received');
                }
                elsif($text =~ /\bmailer=relay\b/) {
                        event($time, 'received');
                }
                elsif($text =~ /\bstat=Sent\b/ ) {
                        event($time, 'sent');
                }
                elsif($text =~ /\bmailer=esmtp\b/ ) {
                        event($time, 'sent');
                }
                elsif($text =~ /\bruleset=check_XS4ALL\b/ ) {
                        event($time, 'rejected');
                }
                elsif($text =~ /\blost input channel\b/ ) {
                        event($time, 'rejected');
                }
                elsif($text =~ /\bruleset=check_rcpt\b/ ) {
                        event($time, 'rejected');
                }
                elsif($text =~ /\bstat=virus\b/ ) {
                        event($time, 'virus');
                }
                elsif($text =~ /\bruleset=check_relay\b/ ) {
                        if (($opt{'virbl-is-virus'}) and ($text =~ /\bivirbl\b/ )) {
                                event($time, 'virus');
                        } elsif ($opt{'rbl-is-spam'}) {
                                event($time, 'spam');
                        } else {
                                event($time, 'rejected');
                        }
                }
                elsif($text =~ /\bsender blocked\b/ ) {
                        event($time, 'rejected');
                }
                elsif($text =~ /\bsender denied\b/ ) {
                        event($time, 'rejected');
                }
                elsif($text =~ /\brecipient denied\b/ ) {
                        event($time, 'rejected');
                }
                elsif($text =~ /\brecipient unknown\b/ ) {
                        event($time, 'rejected');
                }
                elsif($text =~ /\bUser unknown$/i ) {
                        event($time, 'bounced');
                }
                elsif($text =~ /\bMilter:.*\breject=55/ ) {
                        event($time, 'rejected');
                }
        }
        elsif($prog eq 'exim') {
                if($text =~ /^[0-9a-zA-Z]{6}-[0-9a-zA-Z]{6}-[0-9a-zA-Z]{2} <= \S+/) {
                        event($time, 'received');
                }
                elsif($text =~ /^[0-9a-zA-Z]{6}-[0-9a-zA-Z]{6}-[0-9a-zA-Z]{2} => \S+/) {
                        event($time, 'sent');
                }
                elsif($text =~ / rejected because \S+ is in a black list at \S+/) {
                        if($opt{'rbl-is-spam'}) {
                                event($time, 'spam');
                        } else {
                                event($time, 'rejected');
                        }
                }
                elsif($text =~ / rejected RCPT \S+: (Sender verify failed|Unknown user)/) {
                        event($time, 'rejected');
                }
        }
        elsif($prog eq 'amavis' || $prog eq 'amavisd') {
                if(   $text =~ /^\([\w-]+\) (Passed|Blocked) SPAM(?:MY)?\b/) {
                        if($text !~ /\btag2=/) { # ignore new per-recipient log entry (2.2.0)
                                event($time, 'spam'); # since amavisd-new-2004xxxx
                        }
                }
                elsif($text =~ /^\([\w-]+\) (Passed|Not-Delivered)\b.*\bquarantine spam/) {
                        event($time, 'spam'); # amavisd-new-20030616 and earlier
                }
                elsif($text =~ /^\([\w-]+\) (Passed |Blocked )?INFECTED\b/) {
                        if($text !~ /\btag2=/) {
                                event($time, 'virus');# Passed|Blocked inserted since 2004xxxx
                        }
                }
                elsif($text =~ /^\([\w-]+\) (Passed |Blocked )?BANNED\b/) {
                        if($text !~ /\btag2=/) {
                               event($time, 'virus');
                        }
                }
                elsif($text =~ /^Virus found\b/) {
                        event($time, 'virus');# AMaViS 0.3.12 and amavisd-0.1
                }
#               elsif($text =~ /^\([\w-]+\) Passed|Blocked BAD-HEADER\b/) {
#                      event($time, 'badh');
#               }
        }
        elsif($prog eq 'vagatefwd') {
                # Vexira antivirus (old)
                if($text =~ /^VIRUS/) {
                        event($time, 'virus');
                }
        }
        elsif($prog eq 'hook') {
                # Vexira antivirus
                if($text =~ /^\*+ Virus\b/) {
                        event($time, 'virus');
                }
                # Vexira antispam
                elsif($text =~ /\bcontains spam\b/) {
                        event($time, 'spam');
                }
        }
        elsif($prog eq 'avgatefwd' or $prog eq 'avmailgate.bin') {
                # AntiVir MailGate
                if($text =~ /^Alert!/) {
                        event($time, 'virus');
                }
                elsif($text =~ /blocked\.$/) {
                        event($time, 'virus');
                }
        }
        elsif($prog eq 'avcheck') {
                # avcheck
                if($text =~ /^infected/) {
                        event($time, 'virus');
                }
        }
        elsif($prog eq 'spamd') {
                if($text =~ /^(?:spamd: )?identified spam/) {
                        event($time, 'spam');
                }
                # ClamAV SpamAssassin-plugin
                elsif($text =~ /(?:result: )?CLAMAV/) {
                        event($time, 'virus');
                }
        }
        elsif($prog eq 'dspam') {
                if($text =~ /spam detected from/) {
                        event($time, 'spam');
                }
        }
        elsif($prog eq 'spamproxyd' or $prog eq 'spampd') {
                if($text =~ /^\s*SPAM/ or $text =~ /^identified spam/) {
                        event($time, 'spam');
                }
        }
        elsif($prog eq 'drweb-postfix') {
                # DrWeb
                if($text =~ /infected/) {
                        event($time, 'virus');
                }
        }
        elsif($prog eq 'BlackHole') {
                if($text =~ /Virus/) {
                        event($time, 'virus');
                }
                if($text =~ /(?:RBL|Razor|Spam)/) {
                        event($time, 'spam');
                }
        }
        elsif($prog eq 'MailScanner') {
                if($text =~ /(Virus Scanning: Found)/ ) {
                        event($time, 'virus');
                }
                elsif($text =~ /Bounce to/ ) {
                        event($time, 'bounced');
                }
                elsif($text =~ /^Spam Checks: Found ([0-9]+) spam messages/) {
                        my $cnt = $1;
                        for (my $i=0; $i<$cnt; $i++) {
                                event($time, 'spam');
                        }
                }
        }
        elsif($prog eq 'clamsmtpd') {
                if($text =~ /status=VIRUS/) {
                        event($time, 'virus');
                }
        }
        elsif($prog eq 'clamav-milter') {
                if($text =~ /Intercepted/) {
                        event($time, 'virus');
                }
        }
        # uncommment for clamassassin:
        #elsif($prog eq 'clamd') {
        #       if($text =~ /^stream: .* FOUND$/) {
        #               event($time, 'virus');
        #       }
        #}
        elsif ($prog eq 'smtp-vilter') {
                if ($text =~ /clamd: found/) {
                        event($time, 'virus');
                }
        }
        elsif($prog eq 'avmilter') {
                # AntiVir Milter
                if($text =~ /^Alert!/) {
                        event($time, 'virus');
                }
                elsif($text =~ /blocked\.$/) {
                        event($time, 'virus');
                }
        }
        elsif($prog eq 'bogofilter') {
                if($text =~ /Spam/) {
                        event($time, 'spam');
                }
        }
        elsif($prog eq 'filter-module') {
                if($text =~ /\bspam_status\=(?:yes|spam)/) {
                        event($time, 'spam');
                }
        }
        elsif($prog eq 'sta_scanner') {
                if($text =~ /^[0-9A-F]+: virus/) {
                        event($time, 'virus');
                }
        }
        elsif($prog eq 'postgrey') {
                if($text =~ /action=greylist/) {
                        event($time, 'greylisted');
                }
                ## Old versions (up to 1.27)
                #if($text =~ /delayed [0-9]+ seconds: client/) {
                #       event($time, 'delayed');
                #}
                # New versions (from 1.28)
                elsif($text =~ /delay=[0-9]+/) {
                        event($time, 'delayed');
                }
                elsif($text =~ /action=pass, reason=client whitelist/) {
                        event($time, 'whitelist');
                }
                elsif($text =~ /action=greylist, reason=new/) {
                        event($time, 'new');
                }
                elsif($text =~ /action=pass, reason=client AWL/) {
                        event($time, 'awl');
                }
                elsif($text =~ /action=greylist, reason=early-retry/) {
                        event($time, 'early');
                }
                elsif($text =~ /triplet found/) {
                        event($time, 'reconnectok');
                }
        }
        elsif ($prog eq 'smf-spf') {
                if ($text =~ /SPF pass:/) {
                        event($time, 'spfpass');
                }    
                elsif($text =~ /SPF none:/) {
                        event($time, 'spfnone');
                }    
                elsif($text =~ /fail:/) {
                        event($time, 'spffail');
                }
#                elsif($text =~ /SPF fail:\b/) {
#                        event($time, 'spffail');
#                }    
#                elsif($text =~ /SPF softfail:\b/) {
#                        event($time, 'spffail');
#                }
        }    
        elsif ($prog eq 'opendkim') {
                if ($text =~ /DKIM verification successful/) {
                        event($time, 'dkimpass');
                }    
                elsif($text =~ /no signature data/) {
                        event($time, 'dkimnone');
                }    
                elsif($text =~ /bad signature data/) {
                        event($time, 'dkimfail');
                }    
        }    
        elsif ($prog eq 'opendmarc') {
                if ($text =~ /pass/) {
                        event($time, 'dmarcpass');
                }    
                elsif($text =~ /none/) {
                        event($time, 'dmarcnone');
                }    
                elsif($text =~ /fail/) {
                        event($time, 'dmarcfail');
                }    
        }   
}
 
sub event($$)
{
        my ($t, $type) = @_;
        update($t) and $sum{$type}++;
}
 
# returns 1 if $sum should be updated
sub update($)
{
        my $t = shift;
        my $m = $t - $t%$rrdstep;
        init_rrd($m) unless $rrd_inited;
        return 1 if $m == $this_minute;
        return 0 if $m < $this_minute;
 
        print "update $this_minute:$sum{sent}:$sum{received}:$sum{bounced}:$sum{rejected}:$sum{virus}:$sum{spam}:$sum{greylisted}:$sum{delayed}:$sum{whitelist}:$sum{new}:$sum{awl}:$sum{early}:$sum{reconnectok}:$sum{anonymoustls}:$sum{trustedtls}:$sum{untrustedtls}:$sum{verifiedtls}:$sum{spfnone}:$sum{spffail}:$sum{spfpass}:$sum{dmarcnone}:$sum{dmarcfail}:$sum{dmarcpass}:$sum{dkimnone}:$sum{dkimfail}:$sum{dkimpass}:$sum{pswl}:$sum{psbl}:$sum{passold}:$sum{veto}:$sum{pregreet}:$sum{dnsbl}:$sum{pipelining}:$sum{nonsmtp}:$sum{barenewline}:$sum{command}:$sum{hangup}:$sum{passnew}\n" if $opt{verbose};
        #4
        RRDs::update $rrd, "$this_minute:$sum{sent}:$sum{received}:$sum{bounced}:$sum{rejected}" unless $opt{'only-virus-rrd'};
        #2
        RRDs::update $rrd_virus, "$this_minute:$sum{virus}:$sum{spam}" unless $opt{'only-mail-rrd'};
        #7 
        RRDs::update $rrd_grey, "$this_minute:$sum{greylisted}:$sum{delayed}:$sum{whitelist}:$sum{new}:$sum{awl}:$sum{early}:$sum{reconnectok}" unless $opt{'no-greylist-rrd'};
        #4
        RRDs::update $rrd_dane, "$this_minute:$sum{anonymoustls}:$sum{trustedtls}:$sum{untrustedtls}:$sum{verifiedtls}" unless $opt{'no-dane-rrd'};
        #9
        RRDs::update $rrd_dmarc, "$this_minute:$sum{spfnone}:$sum{spffail}:$sum{spfpass}:$sum{dmarcnone}:$sum{dmarcfail}:$sum{dmarcpass}:$sum{dkimnone}:$sum{dkimfail}:$sum{dkimpass}" unless $opt{'no-dane-rrd'};
        #12
        RRDs::update $rrd_post, "$this_minute:$sum{pswl}:$sum{psbl}:$sum{passold}:$sum{veto}:$sum{pregreet}:$sum{dnsbl}:$sum{pipelining}:$sum{nonsmtp}:$sum{barenewline}:$sum{command}:$sum{hangup}:$sum{passnew}" unless $opt{'no-post-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};
                        #4
                        RRDs::update $rrd, "$sm:0:0:0:0" unless $opt{'only-virus-rrd'};
                        #2
                        RRDs::update $rrd_virus, "$sm:0:0" unless $opt{'only-mail-rrd'};
                        #7
                        RRDs::update $rrd_grey, "$sm:0:0:0:0:0:0:0" unless $opt{'no-grey-rrd'};
                        #4
                        RRDs::update $rrd_dane, "$sm:0:0:0:0" unless $opt{'no-dane-rrd'};
                        #9
                        RRDs::update $rrd_dmarc, "$sm:0:0:0:0:0:0:0:0:0" unless $opt{'no-dane-rrd'};
                        #12
                        RRDs::update $rrd_post, "$sm:0:0:0:0:0:0:0:0:0:0:0:0" unless $opt{'no-post-rrd'};
                }
        }
        $this_minute = $m;
        #4
        $sum{sent}=0;
        $sum{received}=0;
        $sum{bounced}=0;
        $sum{rejected}=0;
        #2
        $sum{virus}=0;
        $sum{spam}=0;
        #7
        $sum{greylisted}=0;
        $sum{delayed}=0;
        $sum{whitelist}=0;
        $sum{new}=0;
        $sum{awl}=0;
        $sum{early}=0;
        $sum{reconnectok}=0;
        #4
        $sum{anonymoustls}=0;
        $sum{trustedtls}=0;
        $sum{untrustedtls}=0;
        $sum{verifiedtls}=0;
        #9
        $sum{spfnone}=0;
        $sum{spffail}=0;
        $sum{spfpass}=0;
        $sum{dmarcnone}=0;
        $sum{dmarcfail}=0;
        $sum{dmarcpass}=0;
        $sum{dkimnone}=0;
        $sum{dkimfail}=0;
        $sum{dkimpass}=0;
        # 12
        $sum{pswl}=0;
        $sum{psbl}=0;
        $sum{passold}=0;
        $sum{veto}=0;
        $sum{pregreet}=0;
        $sum{dnsbl}=0;
        $sum{pipelining}=0;
        $sum{nonsmtp}=0;
        $sum{barenewline}=0;
        $sum{command}=0;
        $sum{hangup}=0;
        $sum{passnew}=0;
        return 1;
}
 
main;
 
__END__
 
=head1 NAME
 
mailgraph.pl - rrdtool frontend for mail statistics
 
=head1 SYNOPSIS
 
B<mailgraph> [I<options>...]
 
     --man          show man-page and exit
 -h, --help         display this help and exit
     --version      output version information and exit
 -h, --help         display this help and exit
 -v, --verbose      be verbose about what you do
 -V, --version      output version information and exit
 -c, --cat          causes the logfile to be only read and not monitored
 -l, --logfile f    monitor logfile f instead of /var/log/messages
 -t, --logtype t    set logfile's type (default: syslog)
 -y, --year         starting year of the log file (default: current year)
     --host=HOST    use only entries for HOST (regexp) in syslog
 -d, --daemon       start in the background
 --daemon-pid=FILE  write PID to FILE instead of /var/run/mailgraph.pid
 --daemon-rrd=DIR   write RRDs to DIR instead of /var/log
 --daemon-log=FILE  write verbose-log to FILE instead of /var/log/mailgraph.log
 --ignore-localhost ignore mail to/from localhost (used for virus scanner)
 --ignore-host=HOST ignore mail to/from HOST regexp (used for virus scanner)
 --no-mail-rrd      do not update mail rrd
 --no-virus-rrd     do not update virus rrd
 --no-grey-rrd      do not update grey rrd
 --no-dane-rrd      do not update dane rrd
 --only-mail-rrd    update only the mail rrd
 --only-virus-rrd   update only the virus rrd
 --rrd-name=NAME    use NAME.rrd and NAME_virus.rrd for the rrd files
 --rbl-is-spam      count rbl rejects as spam
 --virbl-is-virus   count virbl rejects as viruses
 
=head1 DESCRIPTION
 
This script does parse syslog and updates the RRD database (mailgraph.rrd) in
the current directory.
 
=head2 Log-Types
 
The following types can be given to --logtype:
 
=over 10
 
=item syslog
 
Traditional "syslog" (default)
 
=item metalog
 
Metalog (see http://metalog.sourceforge.net/)
 
=back
 
=head1 COPYRIGHT
 
Copyright (c) 2000-2007 by ETH Zurich
Copyright (c) 2000-2007 by David Schweikert
Copyright (c) 2011      Markus Neubauer
Copyright (c) 2014-2015 by Django
 
=head1 LICENSE
 
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; either version 2 of the License, or
(at your option) any later version.
 
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.
 
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
=head1 AUTHOR
 
S<David Schweikert E<lt>david@schweikert.chE<gt>>
 
=cut
 
# vi: sw=8

Apache VHost anlegen

Damit wir bequem von unserem Browser aus, die aktuellen Graphiken abfragen können, bearbeiten wir entweder die aus dem RPM stammende Konfigurationsdatei oder legen wir nun einen passenden VHost an.

# vim /etc/httpd/conf.d/vhosts.conf
/etc/httpd/conf.d/vhosts.conf
#
# mailgraph.nausch.org
#
<VirtualHost *:80>
	ServerAdmin webmaster@nausch.org
	ServerName mailgraph.nausch.org
	ServerAlias www.mailgraph.nausch.org
	ServerPath /
	DocumentRoot "/usr/share/mailgraph"
	AddHandler cgi-script .cgi
 
	<Directory "/usr/share/mailgraph">
    AllowOverride None
    Options +ExecCGI
    DirectoryIndex mailgraph.cgi
		Order deny,allow
		require IP 10.0.
	</Directory>
	ErrorLog logs/mailgraph_error.log
	CustomLog logs/mailgraph_access.log combined
</VirtualHost>

Bevor wir bei unserem Webserver eine Reload der Konfiguration vornehmen, testen wir unsere neue Konfigurationsdatei auf syntaktische Fehler.

 # apachectl -t
 Syntax OK

Da keine Fehler aufgetreten sind, aktivieren wir die neue Konfiguration durch einen Reload des Webserver-Daemon.

 # systemctl reload postfix

NGiNX VHost anlegen

Nutzen wir als Webserver NGiNX können wir auch hier schnell und einfach einen passenden vHOST anlegen.

# vim /etc/nginx/conf.d/vhosts.conf
/etc/nginx/conf.d/vhosts.conf
server {
        listen          80;
        server_name     queuegraph.nausch.org;
        access_log      /var/log/nginx/queuegraph_access.log;
        error_log       /var/log/nginx/queuegraph_errors.log;
 
        root /usr/share/queuegraph/;
        index queuegraph.cgi;
 
       location ~ \.php {
                fastcgi_split_path_info ^(.+\.cgi)(/.+)$;
                fastcgi_index queuegraph.cgi;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
        }
}

Den Parameter fastcgi_pass setzen wir im übrigen auf den Wert aus der Konfigurationsdatei /etc/php-fpm.d/www.conf des PHP FastCGI Process Manager-Daemon php-fpm.

Haben wir die Konfigurationsdatei vervollständigt, prüfen wir diese noch auf syntaktische Fehler.

 # nginx -t
 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
 nginx: configuration file /etc/nginx/nginx.conf test is successful

Somit können wir unsere Konfiguration nun noch aktivieren.

 # systemctl reload nginx

Programmaufruf

erster manueller Start des Dämon

Damit das Mail-Logfile forlaufend ausgelesen wird, starten wir nun noch den Dämon mit Hilfe des mitgelieferten systemd-Start-Scriptes /usr/lib/systemd/system/mailgraph.service.

 # systemctl start mailgraph

Im syslog wurde der Start des Daemon entsprechend dokumentiert.

 # tail -n2 /var/log/messages
 Feb  2 21:17:36 vml000097 systemd: Starting mailgraph mail log file analyzer...
 Feb  2 21:17:37 vml000097 systemd: Started mailgraph mail log file analyzer.

Ebenso kann man den Status des Webservers mit Hilfe des Befehls systemctl abfragen.

 # systemctl status mailgraph
mailgraph.service - mailgraph mail log file analyzer
   Loaded: loaded (/usr/lib/systemd/system/mailgraph.service; disabled)
   Active: active (running) since Mon 2015-02-02 21:17:37 CET; 2min 48s ago
  Process: 2362 ExecStart=/usr/sbin/mailgraph -d -l $MAILLOG --daemon-rrd=/var/lib/mailgraph $OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 2367 (mailgraph)
   CGroup: /system.slice/mailgraph.service
           └─2367 /usr/bin/perl -w /usr/sbin/mailgraph -d -l /var/log/maillog --daemon-rrd=/var/lib/mail...

Feb 02 21:17:37 vml000097.dmz.nausch.org systemd[1]: Started mailgraph mail log file analyzer.

automatischer Start beim Systemstart

Wollen wir den Daemon beim Hochfahren des Systems automatisch starten, greifen wir auf den Befehl systemctl zurück.

 # systemctl enable mailgraph.service
 ln -s '/usr/lib/systemd/system/httpd.service' '/etc/systemd/system/multi-user.target.wants/httpd.service'

Möchten wir uns vergewissern, ob der Daemon beim Systemstart gestartet wird oder nicht, erfahren wir ebenfalls mit dem Befehl systemctl.

 # systemctl is-enabled mailgraph.service
 enabled

Startet der Server nicht automatisch, wird uns ein „disabled“ zurückgemeldet.

Webaufruf

Über unseren vHOST erhalten wir nun optisch schön ansprechende Übersichten über den Mailverkehr unseres MX.

Bild: Bildschirmausgabe des WEB-Frontends von Mailgraph-ng

Ausgabe individualisieren

Der zeitgleiche Einsatz von Postgrey und Postscreen macht i.d.R. keinen besonderen Sinn und ist auch nicht zu empfehlen. In der CGI-Datei, die das Generieren der Graphiken und der Webseite vornimmt, sind aber alle aktuell möglichen Szenarien abgebildet.

Einzelne Graphen lassen sich sehr leicht und einfach ausblenden, in dem man die zugehörigen Zeilen einfach auskommentiert, also mit einem # am Zeilenanfang versieht. Diese Zeilen findet man im letzten ¼ der Datei.

Im folgendem Beispiel wollen wir die Greylisting-Graphen nicht mehr ausgeben lassen. Mit dem Editor unserer Wahl bearbeiten wir das cgi-script.

 # vim /usr/share/mailgraph/mailgraph.cgi
...
 
        for my $n (0..$#graphs) {
 
                print "<h2 id=\"G$n\">$graphs[$n]{title}</h2>\n";
                print "<h4><center>Mail Ein und -Ausgang</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-n\" alt=\"mailgraph - received and sent\"/></p>\n";
                print "<h4><center>geblockte Nachrichten</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-e\" alt=\"mailgraph - blocked\"/></p>\n";
                #print "<h4><center>Greylisting &Uuml;bersicht</center></h3>\n";
                #print "<p><img src=\"$scriptname?${n}-g\" alt=\"mailgraph - greylist\"/></p>\n";
                #print "<h4><center>Greylisting Detailansicht</center></h3>\n";
                #print "<p><img src=\"$scriptname?${n}-d\" alt=\"mailgraph - greystats\"/></p>\n";
                print "<h4><center>Postscreen &Uuml;bersicht</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-v\" alt=\"mailgraph - postscreen\"/></p>\n";
                print "<h4><center>Postscreen Detailansicht</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-w\" alt=\"mailgraph - postscreenstats\"/></p>\n";
                print "<h4><center>&Uuml;bersicht Mail-Queues</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-q\" alt=\"mailgraph - mailqueues\"/></p>\n";
                print "<h4><center>DANE / TLSA Verbindungen</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-t\" alt=\"mailgraph - dane checked\"/></p>\n";
                print "<h4><center>Sender policy Framework - SPF-Pr&uuml;fungen</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-f\" alt=\"mailgraph - spf checked\"/></p>\n";
                print "<h4><center>DomainKeys Identified Mail - DKIM-Pr&uuml;fungen</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-m\" alt=\"mailgraph - dkim checked\"/></p>\n";
                print "<h4><center>Domain-based Message Authentication, Reporting & Conformance - DMARC-Pr&uuml;fungen</center></h3>\n";
                print "<p><img src=\"$scriptname?${n}-c\" alt=\"mailgraph - dmarc checked\"/></p>\n";
        }
 
...

So lassen sich z.B. auch die Reports zu SPF, DKIM und DMARC ausblenden, wenn man diese (aktuell noch) nicht haben möchte. Will man später die Graphen wieder sehen, reicht das Entfernen des Kommentarzeichens # am Anfang der betreffenden Zeilen.

Mailgraph-NG für mehrere Mailserver

Betreibt man mehrere Mailserver will man neben den Einzelstatistiken der einzelnen Mailserver oft auch einen Überblick über den gesamten Mail-Server-Verkehr haben. Auch das lässt sich mit einem überschaubaren Aufwand realisieren. Hierzu gehen wir wie folgt vor.

  1. Von den einzelnen Mailservern holen wir die RRD-Dateien ab.
  2. Diese kopieren wir dann auf ein zentrales Verzeichnis auf unserem WEB-Server.
  3. Wir passen unser CGI-Script, welches für die dynamische Generierung der WEB-Seite benutzt wird, soweit an, dass beim Erstellen eines Graphen nicht nur eine Datenquelle herangezogen wird, sondern die Summe unserer einzelnen Mailserver verwendet werden soll.

Auf diese drei Schritte gehen wir nun im Detail noch ein.

RRD-Dateien einsammeln

Die einzelnen RRD-Dateien unserer Mailserver kopieren wir mit scp in ein temporäres Arbeitsverzeichnis unseres WEB-Servers. Hierzu legen wir uns erst einmal einen eigenen User an.

 # groupadd -g 1100 mx-transfer && useradd -c "MX Transfer User" -d /home/mx-transfer -g 1100 -s /bin/bash -u 1100 mx-transfer

Das Gleiche machen wir auf jedem der MX-Hosts, von denen wir später die Dateien einsammeln möchten.

Anschließend generieren wir uns einen ssh-key für diesen transfer-User. Hierzu schlüpfen wir erst in die Rolle dieses speziellen Users.

 # su - mx-transfer

Anschließend legen wir das Unterverzeichnis für den SSH-Schlüssel an und passen dessen Verzeichnisrechte an.

 $ mkdir ~/.ssh
 $ chmod 700 ~/.ssh

Auch hier legen wir auf jedem der MX-Hosts, von denen wir später die Dateien einsammeln möchten, das Verzeichnis mit den nötigen Berechtigungen an.

Nun legen wir uns einen ssh-key ohne Passphrase an, in dem wir bei der Frage nach der Passphrase einfach keine angeben.

 $ ssh-keygen -b 4096 -t rsa -C "MX Transfer User"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/mx-transfer/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/mx-transfer/.ssh/id_rsa.
Your public key has been saved in /home/mx-transfer/.ssh/id_rsa.pub.
The key fingerprint is:
30:9d:ab:8e:62:66:85:da:49:9e:29:75:60:23:a7:e2 MX Transfer User
The key's randomart image is:
+--[ RSA 4096]----+
|                 |
|       . .       |
|      o o        |
|. =    o .       |
| = +    S        |
|o + o  .         |
|o* *  .          |
|oE@  o           |
| = .. .          |
+-----------------+

Den public-key kopieren wir nun anschließend auf jeden unserer MX-Hosts.

 $ ssh-copy-id -i ~/.ssh/id_rsa.pub mx11.dmz.nausch.org
 $ ssh-copy-id -i ~/.ssh/id_rsa.pub mx12.dmz.nausch.org
 $ ssh-copy-id -i ~/.ssh/id_rsa.pub mx13.dmz.nausch.org
 $ ssh-copy-id -i ~/.ssh/id_rsa.pub mx14.dmz.nausch.org

Als nächstes legen wir uns die temporären Zielverzeichnisse auf unserem Webserver an, in die wir später via scp die RRD-Dateien kopieren.

 $ mkdir -p ~/mailgraph/{mx11,mx12,mx13,mx14}

Somit haben wir folgende Verzeichnisstruktur auf unserem Server.

/home/mx-transfer/
└── mailgraph
    ├── mx11
    ├── mx12
    ├── mx13
    └── mx14

Für das Einsammeln der RRD-Dateien legen wir uns ein passendes Script im Verzeichnis ~/bin an. Fehlt dieses Verzeichnis, so legen wir zuvor dieses noch an.

 $ mkdir ~/bin
 $ vim ~/bin/mx-collect
~/bin/mx-collect
#!/bin/bash
# Django : 2015-02-26
# Einsammeln der RRD-Files von den einzelnen Mailservern
 
cd /home/mx-transfer/mailgraph/mx11
scp 10.100.0.87:/var/lib/queuegraph/mailqueues.rrd .
scp 10.100.0.87:/var/lib/mailgraph/*.rrd .
 
cd /home/mx-transfer/mailgraph/mx12
scp 10.200.0.87:/var/lib/queuegraph/mailqueues.rrd .
scp 10.200.0.87:/var/lib/mailgraph/*.rrd .
 
cd /home/mx-transfer/mailgraph/mx13
scp 10.300.0.87:/var/lib/queuegraph/mailqueues.rrd .
scp 10.300.0.87:/var/lib/mailgraph/*.rrd .
 
cd /home/mx-transfer/mailgraph/mx14
scp 10.400.0.87:/var/lib/queuegraph/mailqueues.rrd .
scp 10.400.0.87:/var/lib/mailgraph/*.rrd .

Damit das Script auch ausgeführt werden kann, geben wir ihm noch das entsprechende x-Recht.

 $ chmod +x ~/bin/mx-collect

Im 5-minütigen Abstand holen wird dann die Daten von den Mailservern ab. Hierzu nutzen wir die benutzerindividuelle crontab unseres Transfer-Users.

 $ crontab -e
# Django : 2015-02-26
# alles 5 Minuten die RRD-Files von den einzelnen Mailservern einsammeln
2,7,12,17,22,27,32,37,42,47,52,57 * * * * /home/mx-transfer/bin/mx-collect  &> /dev/null

Nach kurzer Zeit haben wir nunmehr von den einzelnen Mailservern die Statistikdaten mit einer Aktualität von 5 Minuten vorliegen.

/home/mx-transfer/mailgraph/                    
├── mx11                                        
│   ├── mailgraph_dane.rrd                      
│   ├── mailgraph_dmarc.rrd                     
│   ├── mailgraph_grey.rrd                      
│   ├── mailgraph_post.rrd                      
│   ├── mailgraph.rrd                           
│   ├── mailgraph_smtpd.rrd                     
│   ├── mailgraph_virus.rrd                     
│   └── mailqueues.rrd                          
├── mx12                                        
│   ├── mailgraph_dane.rrd                      
│   ├── mailgraph_dmarc.rrd                     
│   ├── mailgraph_grey.rrd                      
│   ├── mailgraph_post.rrd                      
│   ├── mailgraph.rrd                           
│   ├── mailgraph_smtpd.rrd                     
│   ├── mailgraph_virus.rrd                     
│   └── mailqueues.rrd                          
├── mx13                                        
│   ├── mailgraph_dane.rrd                      
│   ├── mailgraph_dmarc.rrd                     
│   ├── mailgraph_grey.rrd                      
│   ├── mailgraph_post.rrd                      
│   ├── mailgraph.rrd                           
│   ├── mailgraph_smtpd.rrd                     
│   ├── mailgraph_virus.rrd                     
│   └── mailqueues.rrd                          
└── mx14                                        
    ├── mailgraph_dane.rrd                      
    ├── mailgraph_dmarc.rrd                     
    ├── mailgraph_grey.rrd                      
    ├── mailgraph_post.rrd                      
    ├── mailgraph.rrd                           
    ├── mailgraph_smtpd.rrd                     
    ├── mailgraph_virus.rrd                     
    └── mailqueues.rrd                          

Datenbereitstellung

Das CGI-Script, welches wir zum dynamischen Generieren der Statistikgraphen verwenden, bekommt im Verzeichnis /var/lib/mailgraph/ zur Verfügung gestellt. Für jeden unserer Mailserver legen wir dort ein Unterverzeichnis an.

 # mkdir -p /var/lib/mailgraph/{mx11,mx12,mx13,mx14}

Die Befüllung dieser Zeilverzeichnisse nehmen wir jeweils mit Hilfe eines rsync-Aufrufs vor, den wir via cronjob ausführen lassen.

 # vim /etc/crontab
/etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
 
# For details see man 4 crontabs
 
# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name command to be executed
 
# Django : 2015-02-26
# alles 5 Minuten die RRD-Files vom Transfer-Verzeichnis abholen und in den Zielverzeichnissen ablegen
*/5  *  *  *  * root /usr/bin/rsync /home/mx-transfer/mailgraph/mx11/* /var/lib/mailgraph/mx11/ &> /dev/null
*/5  *  *  *  * root /usr/bin/rsync /home/mx-transfer/mailgraph/mx12/* /var/lib/mailgraph/mx12/ &> /dev/null
*/5  *  *  *  * root /usr/bin/rsync /home/mx-transfer/mailgraph/mx13/* /var/lib/mailgraph/mx13/ &> /dev/null
*/5  *  *  *  * root /usr/bin/rsync /home/mx-transfer/mailgraph/mx14/* /var/lib/mailgraph/mx14/ &> /dev/null

Nach wenigen Minuten stehen die Statistikdaten, jeweils mit einer Aktualität von 5 Minuten, in den Zielverzeichnissen zur Verfügen, wo diese nun von dem CGI-Script eingelesen und aus den Daten die Graphen produziert werden können.

Generierung Statistikgraphen und Webseite(n)

Zur Präsentation unserer Statistikdaten benötigen wir nun noch einen Webserver, der die aufbereiteten Daten bereitstellt. Hierzu legen wir uns folgende Verzeichnisstruktur an.

/usr/share/mailgraph-ng/
├── mx
├── mx11
├── mx12
├── mx13
└── mx14
 # mkdir -p /usr/share/mailgraph-ng/{mx,mx11,mx12,mx13,mx14}

Bei der RPM-Installation von mailgraph wurden das Logo /usr/share/mailgraph/rrdtool-3dlogo.png wie auch die CSS-Datei /usr/share/mailgraph/mailgraph.css bereits auf unseren Server kopiert. Fehlen diese beiden Dateien, kann man diese hier aus Djangos WIKI herunterladen. Bild: RRDTOOL 3D Logo

/usr/share/mailgraph/mailgraph.css
*     { margin: 0; padding: 0 }
body  { width: 900px; background-color: white;
        font-family: sans-serif;
        font-size: 12pt;
        margin: 5px }
h1    { margin-top: 20px; margin-bottom: 5px;
        text-align: center }
h2    { background-color: #ddd;
        padding: 2px 0 2px 4px }
h3    { margin-top: 10px; margin-bottom: 20px;
        text-align: center }
hr    { height: 1px;
        border: 0;
        border-top: 1px solid #aaa }
table { border: 0px; width: 100% }
img   { border: 0 }
a     { text-decoration: none; color: #00e }
a:hover  { text-decoration: underline; }
#jump    { margin: 0 0 10px 4px }
#jump li { list-style: none; display: inline;
           font-size: 90%; }
#jump li:after            { content: "|"; }
#jump li:last-child:after { content: ""; }

Das Logo und die CSS-Date kopieren wir nun die die einzelnen Unterverzeichnisse.

 # cp /usr/share/mailgraph/rrdtool-3dlogo.png /usr/share/mailgraph-ng/mx
 # cp /usr/share/mailgraph/rrdtool-3dlogo.png /usr/share/mailgraph-ng/mx11
 # cp /usr/share/mailgraph/rrdtool-3dlogo.png /usr/share/mailgraph-ng/mx12
 # cp /usr/share/mailgraph/rrdtool-3dlogo.png /usr/share/mailgraph-ng/mx13
 # cp /usr/share/mailgraph/rrdtool-3dlogo.png /usr/share/mailgraph-ng/mx14
 # cp /usr/share/mailgraph/mailgraph.css /usr/share/mailgraph-ng/mx
 # cp /usr/share/mailgraph/mailgraph.css /usr/share/mailgraph-ng/mx11
 # cp /usr/share/mailgraph/mailgraph.css /usr/share/mailgraph-ng/mx12
 # cp /usr/share/mailgraph/mailgraph.css /usr/share/mailgraph-ng/mx13
 # cp /usr/share/mailgraph/mailgraph.css /usr/share/mailgraph-ng/mx14

In den Unterverzeichnissen mx11 bis mx14 legen wir jeweils ein CGI-Script ab. Damit wir später zwischen den Einzelanzeigen der Mailserver und der kumulierten Übersicht jeweils wechseln können, versehen wir das Originalscript aus dem RPM mailgraph mit entsprechenden angepassten Verweisen.

Die wesentlichen Änderungen/Erweiterung zum Originalscript aus dem RPM sind:

  • Änderungen des Verzeichnisses der Datenquellen (RRD-Dateien).
    Bsp. MX11:
    ...
     
    my $rrd       = '/var/lib/mailgraph/mx11/mailgraph.rrd';
    my $rrd_virus = '/var/lib/mailgraph/mx11/mailgraph_virus.rrd';
    my $rrd_grey  = '/var/lib/mailgraph/mx11/mailgraph_grey.rrd'; 
    my $rrd_dane  = '/var/lib/mailgraph/mx11/mailgraph_dane.rrd';
    my $rrd_dmarc = '/var/lib/mailgraph/mx11/mailgraph_dmarc.rrd';
    my $rrd_smtpd = '/var/lib/mailgraph/mx11/mailgraph_smtpd.rrd';
    my $rrd_queue = '/var/lib/mailgraph/mx11/mailqueues.rrd';
    my $rrd_post  = '/var/lib/mailgraph/mx11/mailgraph_post.rrd';
     
    ...
  • Einfügen der Variablen url für die Verweise zu den jeweils anderen Statistikseiten.
    Bsp. MX11:
    ...
     
    my $url   = "http://mailgraph-ng.nausch.org/mx/";
    my $urlg  = "http://mailgraph-ng.nausch.org/mx/#G";
    my $url1  = "http://mailgraph-ng.nausch.org/mx11/";
    my $url2  = "http://mailgraph-ng.nausch.org/mx12/";
    my $url3  = "http://mailgraph-ng.nausch.org/mx13/";
    my $url4  = "http://mailgraph-ng.nausch.org/mx14/";
    my $url11 = "http://mailgraph-ng.nausch.org/mx11/#G";
    my $url12 = "http://mailgraph-ng.nausch.org/mx12/#G";
    my $url13 = "http://mailgraph-ng.nausch.org/mx13/#G";
    my $url14 = "http://mailgraph-ng.nausch.org/mx14/#G";
     
     
    ...
  • Erweiterung des Unterprogramms print_html um die links zu den verweisenden Statistikseiten.
    ... 
     
    sub print_html()
    {
            print "Content-Type: text/html\n\n";
     
            print <<HEADER;
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html>
     <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <title>Mailserver Statistiken auf $host</title>
      <meta http-equiv="Refresh" content="300" />
      <meta http-equiv="Pragma" content="no-cache" />
      <link rel="stylesheet" href="mailgraph.css" type="text/css" />
     </head>
     <body>
    HEADER
     
            print "<h1>Mailserver Statistiken f&uuml;r mx11.dmz.nausch.org</h1>\n";
            print "<h3>(<a href='$url'>Summen-</a> und Einzelaufstellungen ";
            #print "<a href='$url'>MX11</a>, ";
            print "<a href='$url2'>MX12</a>, ";
            print "<a href='$url3'>MX13</a> und ";
            print "<a href='$url4'>MX14</a></a>)</h4>\n";
     
            print "<ul id=\"jump\">\n";
            for my $n (0..$#graphs) {
                    print "  <li><a href=\"#G$n\">$graphs[$n]{title}</a>&nbsp;</li>\n";
            }
            print "</ul>\n";
     
            for my $n (0..$#graphs) {
                    print "<h2 id=\"G$n\">$graphs[$n]{title}</h2>\n";
                    print "<h4><center>Mail Ein und -Ausgang</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-n\" alt=\"mailgraph - received and sent\"/></p>\n";
     
                    print "<h4><center>geblockte Nachrichten</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-e\" alt=\"mailgraph - blocked\"/></p>\n";
     
                    print "<h4><center>Greylisting &Uuml;bersicht</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-g\" alt=\"mailgraph - greylist\"/></p>\n";
     
                    print "<h4><center>Greylisting Detailansicht</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-d\" alt=\"mailgraph - greystats\"/></p>\n";
     
                    print "<h4><center>Postscreen (positive) &Uuml;bersicht</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-v\" alt=\"mailgraph - postscreen\"/></p>\n";
     
                    print "<h4><center>Postscreen Detailansicht</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-w\" alt=\"mailgraph - postscreenstats\"/></p>\n";
     
                    print "<h4><center>&Uuml;bersicht Mail-Queues</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-q\" alt=\"mailgraph - mailqueues\"/></p>\n";
     
                    print "<h4><center>ausgehende DANE/TLSA-gesicherte Verbindungen</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-t\" alt=\"mailgraph - dane checked\"/></p>\n";
     
                    print "<h4><center>ankommende TLS-gesicherte Verbindungen</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-i\" alt=\"mailgraph - smtpd details\"/></p>\n";
     
                    print "<h4><center>Sender policy Framework - SPF-Pr&uuml;fungen</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-f\" alt=\"mailgraph - spf checked\"/></p>\n";
     
                    print "<h4><center>DomainKeys Identified Mail - DKIM-Pr&uuml;fungen</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-m\" alt=\"mailgraph - dkim checked\"/></p>\n";
     
                    print "<h4><center>Domain-based Message Authentication, Reporting & Conformance - DMARC-Pr&uuml;fungen</center></h4>\n";
                    print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";
                    print "<a href='$url12$n'>MX12</a>\n";
                    print "<a href='$url13$n'>MX13</a>\n";
                    print "<a href='$url14$n'>MX14</a> )</center></h5>\n";
                    print "<p><img src=\"$scriptname?${n}-c\" alt=\"mailgraph - dmarc checked\"/></p>\n";
     
            }
     
            print <<FOOTER;
      <hr/>
      <table border="0" style="font-size:12px" width="900">
       <colgroup>
        <col width="225">
        <col width="430">
        <col width="125">
       </colgroup>
       <tr class="row0">
        <td class="col0 leftalign">
         <a href="http://dokuwiki.nausch.org/doku.php/centos:mail_c7:mta_13?&#mailgraph_nextgeneration">Mailgraph(-ng) </a>$VERSION by
         <a href="mailto:django@mailserver.guru?subject=Mailgraph-NG%20for%20my%20Mailserver">Django</a> based on
        </td>
        <td>
         <a href="http://david.schweikert.ch/">David Schweikert's</a> <a href="http://mailgraph.schweikert.ch/">Mailgraph</a>,
         <a href="http://www.gichenbacher.de/kontakt">Markus Neubauer's </a>
         <a href="http://www.std-soft.com/bfaq/46-k-faq-server/117-greygraph-mail-statistik.html">Greygraph</a>,
        </td>
        <td class="col2 rightalign" rowspan="3">
         <a href="http://oss.oetiker.ch/rrdtool/"><img src="rrdtool-3dlogo.png" alt="" width="135" height="50" align="right" align="middle"/></a>
        </td>
       </tr>
       <tr class="row1">
        <td class="col0 leftalign">
        </td>
        <td class="col1 leftalign">
         <a href="http://www.arschkrebs.de/">Ralf Hildebrandt's </a><a href="http://www.arschkrebs.de/postfix/queuegraph">Queuegraph</a> and
         <a href="https://www.kernel-error.de/">Sebastian van de Meer's </a> <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt">mailgraphpatch 1</a> and
         <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt-2">mailgraphpatch 2</a>
        </td>
       </tr>
      </table>
     </body>
    </html>
    FOOTER
    }
     
    ...

CGI-Scripte für die Einzelsysteme

Für die vier Mailserver ergeben sich folgende CGI-Scripte.

/usr/share/mailgraph-ng/mx11/mailgraph.cgi

 # vim /usr/share/mailgraph-ng/mx11/mailgraph.cgi
/usr/share/mailgraph-ng/mx11/mailgraph.cgi
#!/usr/bin/perl -w                                                                
 
# mailgraph -- detailed postfix mail traffic statistics
# copyright (c) 2000-2007 ETH Zurich                   
# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
# modified 2011 for queuegraph by Ralf Hildebrandt <Ralf.Hildebrandt@computerbeschimpfung.de>
# modified 2015 for mailgraph-ng by Django <django@mailserver.guru> based on                 
# patches from  Sebastian van de Meer <kernel-error@kernel-error.de>                         
# released under the GNU General Public License                                              
 
use RRDs;
use POSIX qw(uname);
 
my $VERSION = "1.15";
 
my $host = (POSIX::uname())[1];
my $scriptname = $ENV{"SCRIPT_NAME"};
my $xpoints = 800;                   
my $points_per_sample = 3;           
my $ypoints = 160;                   
my $rrd = '/var/lib/mailgraph/mx11/mailgraph.rrd';                                                       
my $rrd_virus = '/var/lib/mailgraph/mx11/mailgraph_virus.rrd';                                            
my $rrd_grey = '/var/lib/mailgraph/mx11/mailgraph_grey.rrd';                                             
my $rrd_dane = '/var/lib/mailgraph/mx11/mailgraph_dane.rrd';                                              
my $rrd_dmarc = '/var/lib/mailgraph/mx11/mailgraph_dmarc.rrd';                                           
my $rrd_smtpd = '/var/lib/mailgraph/mx11/mailgraph_smtpd.rrd';                                            
my $rrd_queue = '/var/lib/mailgraph/mx11/mailqueues.rrd';                                                 
my $rrd_post = '/var/lib/mailgraph/mx11/mailgraph_post.rrd';                                              
 
my $tmp_dir = '/var/cache/mailgraph';
my @graphs = (                                                                                                                                                                                                    
        { title => 'Letzter Tag',   seconds => 3600*24,        },                                                                                                                                                 
        { title => 'Letzte Woche',  seconds => 3600*24*7,      },                                                                                                                                                 
        { title => 'Letzter Monat', seconds => 3600*24*31,     },                                                                                                                                                 
        { title => 'Letztes Jahr',  seconds => 3600*24*365,    },                                                                                                                                                 
);                                                                                                                                                                                                                
 
my %color = (                                                           # rrggbb in hex
        # n                                                                            
        sent            => '000099',                                                   
        received        => '009900',                                                   
 
        bounced         => '000000',
        virus           => 'DDBB00',
        spam            => '999999',
        rejected        => 'AA0000',
 
        greylisted      => 'CCCCCC',
        delayed         => '006400',
        whitelist       => '00D8FF',
        awl             => 'FF7700',
        early           => 'AA0000',
 
        pswl            => 'E1FFC1', #
        psbl            => 'EBBAD5',  
        passold         => 'BAFF70', #
        veto            => 'EBEBD5',  
        pregreet        => 'EBA8D5',  
        dnsbl           => 'EB75D5',  
        pipelining      => 'B85BA7',  
        nonsmtp         => '793C6E',  
        barenewline     => '793C2E',  
        command         => '47231B',  
        hangup          => 'C12C0A',  
        passnew         => '468700', #
 
        new             => 'FF77EE',
        reconnectok     => '7700DD',
 
        active          => 'EFEF00',
        deferred        => 'DD8800',
 
        untrustedtls    => 'ffebd1',
        anonymoustls    => 'ffcf90',
        trustedtls      => 'ffb24f',
        verifiedtls     => 'ff5800',
 
        untrustedtlsin  => 'ddd1ff',
        anonymoustlsin  => 'a8a8ff', 
        trustedtlsin    => '6767ff', 
 
        spfnone         => '12FF0A',
        spffail         => 'f80b6f',
        spfpass         => '2E5fEC',
 
        dkimnone        => 'E6E27A',
        dkimfail        => 'FF6600',
        dkimpass        => '3013EC',
 
        dmarcnone       => 'F0B166',
        dmarcfail       => 'f11717',
        dmarcpass       => '00FFD5',
);                                  
 
my $url   = "http://mailgraph-ng.nausch.org/mx/";
my $urlg  = "http://mailgraph-ng.nausch.org/mx/#G";
my $url1  = "http://mailgraph-ng.nausch.org/mx11/";
my $url2  = "http://mailgraph-ng.nausch.org/mx12/";
my $url3  = "http://mailgraph-ng.nausch.org/mx13/";
my $url4  = "http://mailgraph-ng.nausch.org/mx14/";
my $url11 = "http://mailgraph-ng.nausch.org/mx11/#G";
my $url12 = "http://mailgraph-ng.nausch.org/mx12/#G";
my $url13 = "http://mailgraph-ng.nausch.org/mx13/#G";
my $url14 = "http://mailgraph-ng.nausch.org/mx14/#G";
 
sub rrd_graph(@)
{               
        my ($range, $file, $ypoints, @rrdargs) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        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\: %15.0lf msgs',           
                'GPRINT:rsent:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmsent:MAX:max\: %11.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\: %15.0lf msgs',                
                'GPRINT:rrecv:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmrecv:MAX:max\: %11.0lf msgs/min\l',           
        );                                                              
}                                                                       
 
sub graph_virus($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "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,*",                      
                "AREA:rrejected#$color{rejected}:Rejected                ",
                'GPRINT:srejected:MAX:total\: %15.0lf msgs',                
                'GPRINT:rrejected:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmrejected:MAX:max\: %11.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,*",                   
                "AREA:rvirus#$color{virus}:Viruses                 ",
                'GPRINT:svirus:MAX:total\: %15.0lf msgs',            
                'GPRINT:rvirus:AVERAGE:avg\: %12.2lf msgs/min',      
                'GPRINT:rmvirus:MAX:max\: %11.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\: %15.0lf msgs',             
                'GPRINT:rspam:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmspam:MAX:max\: %11.0lf msgs/min\l',        
 
                "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,*",                     
                "LINE2:rbounced#$color{bounced}:Bounced                 ",
                'GPRINT:sbounced:MAX:total\: %15.0lf msgs',               
                'GPRINT:rbounced:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmbounced:MAX:max\: %11.0lf msgs/min\l',          
 
        );
}         
 
 
sub graph_greylist($$)
        {             
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:new=$rrd_grey:new:AVERAGE",
                "DEF:mnew=$rrd_grey:new:MAX",   
                "CDEF:rnew=new,60,*",           
                "CDEF:rmnew=mnew,60,*",         
                "CDEF:dnew=new,UN,0,new,IF,$step,*",
                "CDEF:snew=PREV,UN,dnew,PREV,IF,dnew,+",
                "AREA:rnew#$color{new}:New                     ",
                'GPRINT:snew:MAX:total\: %15.0lf msgs',          
                'GPRINT:rnew:AVERAGE:avg\: %12.2lf msgs/min',    
                'GPRINT:rmnew:MAX:max\: %11.0lf msgs/min\l',     
 
                "DEF:reconnectok=$rrd_grey:reconnectok:AVERAGE",
                "DEF:mreconnectok=$rrd_grey:reconnectok:MAX",   
                "CDEF:rreconnectok=reconnectok,60,*",           
                "CDEF:dreconnectok=reconnectok,UN,0,reconnectok,IF,$step,*",
                "CDEF:sreconnectok=PREV,UN,dreconnectok,PREV,IF,dreconnectok,+",
                "CDEF:rmreconnectok=mreconnectok,60,*",                         
                "LINE2:rreconnectok#$color{reconnectok}:Reconnect O.K.          ",
                'GPRINT:sreconnectok:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rreconnectok:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmreconnectok:MAX:max\: %11.0lf msgs/min\l',              
 
        );
}         
 
 
sub graph_greystats($$)
        {              
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:greylisted=$rrd_grey:greylisted:AVERAGE",
                "DEF:mgreylisted=$rrd_grey:greylisted:MAX",   
                "CDEF:rgreylisted=greylisted,60,*",           
                "CDEF:rmgreylisted=mgreylisted,60,*",         
                "CDEF:dgreylisted=greylisted,UN,0,greylisted,IF,$step,*",
                "CDEF:sgreylisted=PREV,UN,dgreylisted,PREV,IF,dgreylisted,+",
                "AREA:rgreylisted#$color{greylisted}:Greylisted              ",
                'GPRINT:sgreylisted:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rgreylisted:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmgreylisted:MAX:max\: %11.0lf msgs/min\l',            
 
                "DEF:delayed=$rrd_grey:delayed:AVERAGE",
                "DEF:mdelayed=$rrd_grey:delayed:MAX",   
                "CDEF:rdelayed=delayed,60,*",           
                "CDEF:rmdelayed=mdelayed,60,*",         
                "CDEF:ddelayed=delayed,UN,0,delayed,IF,$step,*",
                "CDEF:sdelayed=PREV,UN,ddelayed,PREV,IF,ddelayed,+",
                "LINE2:rdelayed#$color{delayed}:Delayed                 ",
                'GPRINT:sdelayed:MAX:total\: %15.0lf msgs',               
                'GPRINT:rdelayed:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmdelayed:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:whitelist=$rrd_grey:whitelist:AVERAGE",
                "DEF:mwhitelist=$rrd_grey:whitelist:MAX",   
                "CDEF:rwhitelist=whitelist,60,*",           
                "CDEF:rmwhitelist=mwhitelist,60,*",         
                "CDEF:dwhitelist=whitelist,UN,0,whitelist,IF,$step,*",
                "CDEF:swhitelist=PREV,UN,dwhitelist,PREV,IF,dwhitelist,+",
                "AREA:rwhitelist#$color{whitelist}:Whitelist               ",
                'GPRINT:swhitelist:MAX:total\: %15.0lf msgs',                
                'GPRINT:rwhitelist:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmwhitelist:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:awl=$rrd_grey:awl:AVERAGE",
                "DEF:mawl=$rrd_grey:awl:MAX",   
                "CDEF:rawl=awl,60,*",           
                "CDEF:dawl=awl,UN,0,awl,IF,$step,*",
                "CDEF:sawl=PREV,UN,dawl,PREV,IF,dawl,+",
                "CDEF:rmawl=mawl,60,*",                 
                "LINE2:rawl#$color{awl}:Auto whitelist          ",
                'GPRINT:sawl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rawl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmawl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:early=$rrd_grey:early:AVERAGE",
                "DEF:mearly=$rrd_grey:early:MAX",   
                "CDEF:rearly=early,60,*",           
                "CDEF:rmearly=mearly,60,*",         
                "CDEF:dearly=early,UN,0,early,IF,$step,*",
                "CDEF:searly=PREV,UN,dearly,PREV,IF,dearly,+",
                "AREA:rearly#$color{early}:Early connect           ",
                'GPRINT:searly:MAX:total\: %15.0lf msgs',             
                'GPRINT:rearly:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmearly:MAX:max\: %11.0lf msgs/min\l',        
 
 
        );
}         
 
sub graph_postscreen($$)
        {               
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:pswl=$rrd_post:pswl:AVERAGE",
                "DEF:mpswl=$rrd_post:pswl:MAX",   
                "CDEF:rpswl=pswl,60,*",           
                "CDEF:rmpswl=mpswl,60,*",         
                "CDEF:dpswl=pswl,UN,0,pswl,IF,$step,*",
                "CDEF:spswl=PREV,UN,dpswl,PREV,IF,dpswl,+",
                "AREA:rpswl#$color{pswl}:WHITELISTED             ",
                'GPRINT:spswl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rpswl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmpswl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:passold=$rrd_post:passold:AVERAGE",
                "DEF:mpassold=$rrd_post:passold:MAX",   
                "CDEF:rpassold=passold,60,*",           
                "CDEF:dpassold=passold,UN,0,passold,IF,$step,*",
                "CDEF:spassold=PREV,UN,dpassold,PREV,IF,dpassold,+",
                "CDEF:rmpassold=mpassold,60,*",                     
                "STACK:rpassold#$color{passold}:PASS OLD                ",
                'GPRINT:spassold:MAX:total\: %15.0lf msgs',               
                'GPRINT:rpassold:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmpassold:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:passnew=$rrd_post:passnew:AVERAGE",
                "DEF:mpassnew=$rrd_post:passnew:MAX",   
                "CDEF:rpassnew=passnew,60,*",           
                "CDEF:dpassnew=passnew,UN,0,passnew,IF,$step,*",
                "CDEF:spassnew=PREV,UN,dpassnew,PREV,IF,dpassnew,+",
                "CDEF:rmpassnew=mpassnew,60,*",                     
                "LINE2:rpassnew#$color{passnew}:PASS NEW                ",
                'GPRINT:spassnew:MAX:total\: %15.0lf msgs',               
                'GPRINT:rpassnew:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmpassnew:MAX:max\: %11.0lf msgs/min\l',          
        );                                                                
}                                                                         
 
sub graph_postscreenstats($$)
{                            
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:psbl=$rrd_post:psbl:AVERAGE",    
                "DEF:mpsbl=$rrd_post:psbl:MAX",       
                "CDEF:rpsbl=psbl,60,*",               
                "CDEF:dpsbl=psbl,UN,0,psbl,IF,$step,*",
                "CDEF:spsbl=PREV,UN,dpsbl,PREV,IF,dpsbl,+",
                "CDEF:rmpsbl=mpsbl,60,*",                  
                "AREA:rpsbl#$color{psbl}:BLACKLISTED             ",
                'GPRINT:spsbl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rpsbl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmpsbl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:veto=$rrd_post:veto:AVERAGE",
                "DEF:mveto=$rrd_post:veto:MAX",   
                "CDEF:rveto=veto,60,*",           
                "CDEF:dveto=veto,UN,0,veto,IF,$step,*",
                "CDEF:sveto=PREV,UN,dveto,PREV,IF,dveto,+",
                "CDEF:rmveto=mveto,60,*",                  
                "STACK:rveto#$color{veto}:WHITELIST VETO          ",
                'GPRINT:sveto:MAX:total\: %15.0lf msgs',            
                'GPRINT:rveto:AVERAGE:avg\: %12.2lf msgs/min',      
                'GPRINT:rmveto:MAX:max\: %11.0lf msgs/min\l',       
 
                "DEF:pregreet=$rrd_post:pregreet:AVERAGE",
                "DEF:mpregreet=$rrd_post:pregreet:MAX",   
                "CDEF:rpregreet=pregreet,60,*",           
                "CDEF:dpregreet=pregreet,UN,0,pregreet,IF,$step,*",
                "CDEF:spregreet=PREV,UN,dpregreet,PREV,IF,dpregreet,+",
                "CDEF:rmpregreet=mpregreet,60,*",                      
                "STACK:rpregreet#$color{pregreet}:PREGREET                ",
                'GPRINT:spregreet:MAX:total\: %15.0lf msgs',                
                'GPRINT:rpregreet:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmpregreet:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dnsbl=$rrd_post:dnsbl:AVERAGE",
                "DEF:mdnsbl=$rrd_post:dnsbl:MAX",   
                "CDEF:rdnsbl=dnsbl,60,*",           
                "CDEF:ddnsbl=dnsbl,UN,0,dnsbl,IF,$step,*",
                "CDEF:sdnsbl=PREV,UN,ddnsbl,PREV,IF,ddnsbl,+",
                "CDEF:rmdnsbl=mdnsbl,60,*",                   
                "STACK:rdnsbl#$color{dnsbl}:DNSBL                   ",
                'GPRINT:sdnsbl:MAX:total\: %15.0lf msgs',             
                'GPRINT:rdnsbl:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmdnsbl:MAX:max\: %11.0lf msgs/min\l',        
 
                "DEF:pipelining=$rrd_post:pipelining:AVERAGE",
                "DEF:mpipelining=$rrd_post:pipelining:MAX",   
                "CDEF:rpipelining=pipelining,60,*",           
                "CDEF:dpipelining=pipelining,UN,0,pipelining,IF,$step,*",
                "CDEF:spipelining=PREV,UN,dpipelining,PREV,IF,dpipelining,+",
                "CDEF:rmpipelining=mpipelining,60,*",                        
                "STACK:rpipelining#$color{pipelining}:PIPELINING              ",
                'GPRINT:spipelining:MAX:total\: %15.0lf msgs',                  
                'GPRINT:rpipelining:AVERAGE:avg\: %12.2lf msgs/min',            
                'GPRINT:rmpipelining:MAX:max\: %11.0lf msgs/min\l',             
 
                "DEF:nonsmtp=$rrd_post:nonsmtp:AVERAGE",
                "DEF:mnonsmtp=$rrd_post:nonsmtp:MAX",   
                "CDEF:rnonsmtp=nonsmtp,60,*",           
                "CDEF:dnonsmtp=nonsmtp,UN,0,nonsmtp,IF,$step,*",
                "CDEF:snonsmtp=PREV,UN,dnonsmtp,PREV,IF,dnonsmtp,+",
                "CDEF:rmnonsmtp=mnonsmtp,60,*",                     
                "STACK:rnonsmtp#$color{nonsmtp}:NON SMTP COMMAND        ",
                'GPRINT:snonsmtp:MAX:total\: %15.0lf msgs',               
                'GPRINT:rnonsmtp:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmnonsmtp:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:barenewline=$rrd_post:barenewline:AVERAGE",
                "DEF:mbarenewline=$rrd_post:barenewline:MAX",   
                "CDEF:rbarenewline=barenewline,60,*",           
                "CDEF:dbarenewline=barenewline,UN,0,barenewline,IF,$step,*",
                "CDEF:sbarenewline=PREV,UN,dbarenewline,PREV,IF,dbarenewline,+",
                "CDEF:rmbarenewline=mbarenewline,60,*",                         
                "STACK:rbarenewline#$color{barenewline}:BARE NEWLINE            ",
                'GPRINT:sbarenewline:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rbarenewline:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmbarenewline:MAX:max\: %11.0lf msgs/min\l',              
 
                "DEF:command=$rrd_post:command:AVERAGE",
                "DEF:mcommand=$rrd_post:command:MAX",   
                "CDEF:rcommand=command,60,*",           
                "CDEF:dcommand=command,UN,0,command,IF,$step,*",
                "CDEF:scommand=PREV,UN,dcommand,PREV,IF,dcommand,+",
                "CDEF:rmcommand=mcommand,60,*",                     
                "STACK:rcommand#$color{command}:COMMAND LIMITS          ",
                'GPRINT:scommand:MAX:total\: %15.0lf msgs',               
                'GPRINT:rcommand:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmcommand:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:hangup=$rrd_post:hangup:AVERAGE",
                "DEF:mhangup=$rrd_post:hangup:MAX",   
                "CDEF:rhangup=hangup,60,*",           
                "CDEF:dhangup=hangup,UN,0,hangup,IF,$step,*",
                "CDEF:shangup=PREV,UN,dhangup,PREV,IF,dhangup,+",
                "CDEF:rmhangup=mhangup,60,*",                    
                "STACK:rhangup#$color{hangup}:HUNGUP                  ",
                'GPRINT:shangup:MAX:total\: %15.0lf msgs',              
                'GPRINT:rhangup:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:rmhangup:MAX:max\: %11.0lf msgs/min\l',         
        );                                                              
}                                                                       
 
 
 
sub graph_dane($$)
{                 
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:untrustedtls=$rrd_dane:untrustedtls:AVERAGE",
                "DEF:muntrustedtls=$rrd_dane:untrustedtls:MAX",   
                "CDEF:runtrustedtls=untrustedtls,60,*",           
                "CDEF:duntrustedtls=untrustedtls,UN,0,untrustedtls,IF,$step,*",
                "CDEF:suntrustedtls=PREV,UN,duntrustedtls,PREV,IF,duntrustedtls,+",
                "CDEF:rmuntrustedtls=muntrustedtls,60,*",                          
                "AREA:runtrustedtls#$color{untrustedtls}:Out Untrusted TLS       ",
                'GPRINT:suntrustedtls:MAX:total\: %15.0lf msgs',                   
                'GPRINT:runtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmuntrustedtls:MAX:max\: %11.0lf msgs/min\l',              
 
                "DEF:anonymoustls=$rrd_dane:anonymoustls:AVERAGE",
                "DEF:manonymoustls=$rrd_dane:anonymoustls:MAX",   
                "CDEF:ranonymoustls=anonymoustls,60,*",           
                "CDEF:danonymoustls=anonymoustls,UN,0,anonymoustls,IF,$step,*",
                "CDEF:sanonymoustls=PREV,UN,danonymoustls,PREV,IF,danonymoustls,+",
                "CDEF:rmanonymoustls=manonymoustls,60,*",                          
                "STACK:ranonymoustls#$color{anonymoustls}:Out Anonymous TLS       ",
                'GPRINT:sanonymoustls:MAX:total\: %15.0lf msgs',                    
                'GPRINT:ranonymoustls:AVERAGE:avg\: %12.2lf msgs/min',              
                'GPRINT:rmanonymoustls:MAX:max\: %11.0lf msgs/min\l',               
 
                "DEF:trustedtls=$rrd_dane:trustedtls:AVERAGE",
                "DEF:mtrustedtls=$rrd_dane:trustedtls:MAX",   
                "CDEF:rtrustedtls=trustedtls,60,*",           
                "CDEF:dtrustedtls=trustedtls,UN,0,trustedtls,IF,$step,*",
                "CDEF:strustedtls=PREV,UN,dtrustedtls,PREV,IF,dtrustedtls,+",
                "CDEF:rmtrustedtls=mtrustedtls,60,*",                        
                "STACK:rtrustedtls#$color{trustedtls}:Out Trusted TLS         ",
                'GPRINT:strustedtls:MAX:total\: %15.0lf msgs',                  
                'GPRINT:rtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',            
                'GPRINT:rmtrustedtls:MAX:max\: %11.0lf msgs/min\l',             
 
                "DEF:verifiedtls=$rrd_dane:verifiedtls:AVERAGE",
                "DEF:mverifiedtls=$rrd_dane:verifiedtls:MAX",   
                "CDEF:rverifiedtls=verifiedtls,60,*",           
                "CDEF:dverifiedtls=verifiedtls,UN,0,verifiedtls,IF,$step,*",
                "CDEF:sverifiedtls=PREV,UN,dverifiedtls,PREV,IF,dverifiedtls,+",
                "CDEF:rmverifiedtls=mverifiedtls,60,*",                         
                "LINE2:rverifiedtls#$color{verifiedtls}:Out Verified TLS        ",
                'GPRINT:sverifiedtls:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rverifiedtls:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmverifiedtls:MAX:max\: %11.0lf msgs/min\l',              
        );                                                                        
}                                                                                 
 
 
 
sub graph_smtpd($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:untrustedtlsin=$rrd_smtpd:untrustedtlsin:AVERAGE",
                "DEF:muntrustedtlsin=$rrd_smtpd:untrustedtlsin:MAX",   
                "CDEF:runtrustedtlsin=untrustedtlsin,60,*",            
                "CDEF:duntrustedtlsin=untrustedtlsin,UN,0,untrustedtlsin,IF,$step,*",
                "CDEF:suntrustedtlsin=PREV,UN,duntrustedtlsin,PREV,IF,duntrustedtlsin,+",
                "CDEF:rmuntrustedtlsin=muntrustedtlsin,60,*",                            
                "AREA:runtrustedtlsin#$color{untrustedtlsin}:IN Untrusted TLS        ",  
                'GPRINT:suntrustedtlsin:MAX:total\: %15.0lf msgs',                       
                'GPRINT:runtrustedtlsin:AVERAGE:avg\: %12.2lf msgs/min',                 
                'GPRINT:rmuntrustedtlsin:MAX:max\: %11.0lf msgs/min\l',                  
 
                "DEF:anonymoustlsin=$rrd_smtpd:anonymoustlsin:AVERAGE",
                "DEF:manonymoustlsin=$rrd_smtpd:anonymoustlsin:MAX",   
                "CDEF:ranonymoustlsin=anonymoustlsin,60,*",            
                "CDEF:danonymoustlsin=anonymoustlsin,UN,0,anonymoustlsin,IF,$step,*",
                "CDEF:sanonymoustlsin=PREV,UN,danonymoustlsin,PREV,IF,danonymoustlsin,+",
                "CDEF:rmanonymoustlsin=manonymoustlsin,60,*",                            
                "STACK:ranonymoustlsin#$color{anonymoustlsin}:IN Anonymous TLS        ", 
                'GPRINT:sanonymoustlsin:MAX:total\: %15.0lf msgs',                       
                'GPRINT:ranonymoustlsin:AVERAGE:avg\: %12.2lf msgs/min',                 
                'GPRINT:rmanonymoustlsin:MAX:max\: %11.0lf msgs/min\l',                  
 
                "DEF:trustedtlsin=$rrd_smtpd:trustedtlsin:AVERAGE",
                "DEF:mtrustedtlsin=$rrd_smtpd:trustedtlsin:MAX",   
                "CDEF:rtrustedtlsin=trustedtlsin,60,*",            
                "CDEF:dtrustedtlsin=trustedtlsin,UN,0,trustedtlsin,IF,$step,*",
                "CDEF:strustedtlsin=PREV,UN,dtrustedtlsin,PREV,IF,dtrustedtlsin,+",
                "CDEF:rmtrustedtlsin=mtrustedtlsin,60,*",                          
                "STACK:rtrustedtlsin#$color{trustedtlsin}:In Trusted TLS          ",
                'GPRINT:strustedtlsin:MAX:total\: %15.0lf msgs',                    
                'GPRINT:rtrustedtlsin:AVERAGE:avg\: %12.2lf msgs/min',              
                'GPRINT:rmtrustedtlsin:MAX:max\: %11.0lf msgs/min\l',               
        );                                                                          
}                                                                                   
 
 
 
sub graph_spf($$)
{                
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:spfpass=$rrd_dmarc:spfpass:AVERAGE",
                "DEF:mspfpass=$rrd_dmarc:spfpass:MAX",   
                "CDEF:rspfpass=spfpass,60,*",            
                "CDEF:dspfpass=spfpass,UN,0,spfpass,IF,$step,*",
                "CDEF:sspfpass=PREV,UN,dspfpass,PREV,IF,dspfpass,+",
                "CDEF:rmspfpass=mspfpass,60,*",                     
                "AREA:rspfpass#$color{spfpass}:SPF pass                ",
                'GPRINT:sspfpass:MAX:total\: %15.0lf msgs',              
                'GPRINT:rspfpass:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:rmspfpass:MAX:max\: %11.0lf msgs/min\l',         
 
                "DEF:spfnone=$rrd_dmarc:spfnone:AVERAGE",
                "DEF:mspfnone=$rrd_dmarc:spfnone:MAX",   
                "CDEF:rspfnone=spfnone,60,*",            
                "CDEF:dspfnone=spfnone,UN,0,spfnone,IF,$step,*",
                "CDEF:sspfnone=PREV,UN,dspfnone,PREV,IF,dspfnone,+",
                "CDEF:rmspfnone=mspfnone,60,*",                     
                "LINE2:rspfnone#$color{spfnone}:SPF none                ",
                'GPRINT:sspfnone:MAX:total\: %15.0lf msgs',               
                'GPRINT:rspfnone:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmspfnone:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:spffail=$rrd_dmarc:spffail:AVERAGE",
                "DEF:mspffail=$rrd_dmarc:spffail:MAX",   
                "CDEF:rspffail=spffail,60,*",            
                "CDEF:dspffail=spffail,UN,0,spffail,IF,$step,*",
                "CDEF:sspffail=PREV,UN,dspffail,PREV,IF,dspffail,+",
                "CDEF:rmspffail=mspffail,60,*",                     
                "LINE2:rspffail#$color{spffail}:SPF fail                ",
                'GPRINT:sspffail:MAX:total\: %15.0lf msgs',               
                'GPRINT:rspffail:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmspffail:MAX:max\: %11.0lf msgs/min\l',          
        );                                                                
}                                                                         
 
sub graph_dkim($$)
{                 
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:dkimpass=$rrd_dmarc:dkimpass:AVERAGE",
                "DEF:mdkimpass=$rrd_dmarc:dkimpass:MAX",   
                "CDEF:rdkimpass=dkimpass,60,*",            
                "CDEF:ddkimpass=dkimpass,UN,0,dkimpass,IF,$step,*",
                "CDEF:sdkimpass=PREV,UN,ddkimpass,PREV,IF,ddkimpass,+",
                "CDEF:rmdkimpass=mdkimpass,60,*",                      
                "AREA:rdkimpass#$color{dkimpass}:DKIM pass               ",
                'GPRINT:sdkimpass:MAX:total\: %15.0lf msgs',               
                'GPRINT:rdkimpass:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmdkimpass:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:dkimnone=$rrd_dmarc:dkimnone:AVERAGE",
                "DEF:mdkimnone=$rrd_dmarc:dkimnone:MAX",   
                "CDEF:rdkimnone=dkimnone,60,*",            
                "CDEF:ddkimnone=dkimnone,UN,0,dkimnone,IF,$step,*",
                "CDEF:sdkimnone=PREV,UN,ddkimnone,PREV,IF,ddkimnone,+",
                "CDEF:rmdkimnone=mdkimnone,60,*",                      
                "LINE2:rdkimnone#$color{dkimnone}:DKIM none               ",
                'GPRINT:sdkimnone:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdkimnone:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdkimnone:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dkimfail=$rrd_dmarc:dkimfail:AVERAGE",
                "DEF:mdkimfail=$rrd_dmarc:dkimfail:MAX",   
                "CDEF:rdkimfail=dkimfail,60,*",            
                "CDEF:ddkimfail=dkimfail,UN,0,dkimfail,IF,$step,*",
                "CDEF:sdkimfail=PREV,UN,ddkimfail,PREV,IF,ddkimfail,+",
                "CDEF:rmdkimfail=mdkimfail,60,*",                      
                "LINE2:rdkimfail#$color{dkimfail}:DKIM fail               ",
                'GPRINT:sdkimfail:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdkimfail:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdkimfail:MAX:max\: %11.0lf msgs/min\l',           
        );                                                                  
}                                                                           
 
sub graph_dmarc($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:dmarcpass=$rrd_dmarc:dmarcpass:AVERAGE",
                "DEF:mdmarcpass=$rrd_dmarc:dmarcpass:MAX",   
                "CDEF:rdmarcpass=dmarcpass,60,*",            
                "CDEF:ddmarcpass=dmarcpass,UN,0,dmarcpass,IF,$step,*",
                "CDEF:sdmarcpass=PREV,UN,ddmarcpass,PREV,IF,ddmarcpass,+",
                "CDEF:rmdmarcpass=mdmarcpass,60,*",                       
                "AREA:rdmarcpass#$color{dmarcpass}:DMARC pass              ",
                'GPRINT:sdmarcpass:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdmarcpass:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdmarcpass:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dmarcnone=$rrd_dmarc:dmarcnone:AVERAGE",
                "DEF:mdmarcnone=$rrd_dmarc:dmarcnone:MAX",   
                "CDEF:rdmarcnone=dmarcnone,60,*",            
                "CDEF:ddmarcnone=dmarcnone,UN,0,dmarcnone,IF,$step,*",
                "CDEF:sdmarcnone=PREV,UN,ddmarcnone,PREV,IF,ddmarcnone,+",
                "CDEF:rmdmarcnone=mdmarcnone,60,*",                       
                "LINE2:rdmarcnone#$color{dmarcnone}:DMARC none              ",
                'GPRINT:sdmarcnone:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rdmarcnone:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmdmarcnone:MAX:max\: %11.0lf msgs/min\l',            
 
                "DEF:dmarcfail=$rrd_dmarc:dmarcfail:AVERAGE",
                "DEF:mdmarcfail=$rrd_dmarc:dmarcfail:MAX",   
                "CDEF:rdmarcfail=dmarcfail,60,*",            
                "CDEF:ddmarcfail=dmarcfail,UN,0,dmarcfail,IF,$step,*",
                "CDEF:sdmarcfail=PREV,UN,ddmarcfail,PREV,IF,ddmarcfail,+",
                "CDEF:rmdmarcfail=mdmarcfail,60,*",                       
                "LINE2:rdmarcfail#$color{dmarcfail}:DMARC fail              ",
                'GPRINT:sdmarcfail:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rdmarcfail:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmdmarcfail:MAX:max\: %11.0lf msgs/min\l',            
        );                                                                    
}                                                                             
 
 
 
sub graph_queue($$)
        {          
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:deferred=$rrd_queue:deferred:AVERAGE",
                "AREA:deferred#$color{deferred}:Deferred                ",
                'GPRINT:deferred:MAX:total\: %15.0lf msgs',               
                'GPRINT:deferred:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:deferred:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:active=$rrd_queue:active:AVERAGE",
                "LINE2:active#$color{active}:Active+Incoming+Maildrop",
                'GPRINT:active:MAX:total\: %15.0lf msgs',              
                'GPRINT:active:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:active:MAX:max\: %11.0lf msgs/min\l',          
        );                                                             
}                                                                      
 
 
 
 
 
sub print_html()
{               
        print "Content-Type: text/html\n\n";
 
        print <<HEADER;
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">                                                                                                     
<html>                                                                                                   
 <head>                                                                                                  
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />                                  
  <title>Mailserver Statistiken auf $host</title>                                                        
  <meta http-equiv="Refresh" content="300" />                                                            
  <meta http-equiv="Pragma" content="no-cache" />                                                        
  <link rel="stylesheet" href="mailgraph.css" type="text/css" />                                         
 </head>                                                                                                 
 <body>                                                                                                  
HEADER                                                                                                   
 
        print "<h1>Mailserver Statistiken f&uuml;r mx11.dmz.nausch.org</h1>\n";
        print "<h3>(<a href='$url'>Summen-</a> und Einzelaufstellungen ";
        #print "<a href='$url'>MX11</a>, ";                              
        print "<a href='$url2'>MX12</a>, ";                              
        print "<a href='$url3'>MX13</a> und ";                           
        print "<a href='$url4'>MX14</a></a>)</h4>\n";                    
 
        print "<ul id=\"jump\">\n";
        for my $n (0..$#graphs) {  
                print "  <li><a href=\"#G$n\">$graphs[$n]{title}</a>&nbsp;</li>\n";
        }                                                                          
        print "</ul>\n";                                                           
 
        for my $n (0..$#graphs) {
                print "<h2 id=\"G$n\">$graphs[$n]{title}</h2>\n";
                print "<h4><center>Mail Ein und -Ausgang</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";      
                print "<a href='$url12$n'>MX12</a>\n";                    
                print "<a href='$url13$n'>MX13</a>\n";                    
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";    
                print "<p><img src=\"$scriptname?${n}-n\" alt=\"mailgraph - received and sent\"/></p>\n";
 
                print "<h4><center>geblockte Nachrichten</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";      
                print "<a href='$url12$n'>MX12</a>\n";                    
                print "<a href='$url13$n'>MX13</a>\n";                    
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";    
                print "<p><img src=\"$scriptname?${n}-e\" alt=\"mailgraph - blocked\"/></p>\n";
 
                print "<h4><center>Greylisting &Uuml;bersicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";           
                print "<a href='$url12$n'>MX12</a>\n";                         
                print "<a href='$url13$n'>MX13</a>\n";                         
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";         
                print "<p><img src=\"$scriptname?${n}-g\" alt=\"mailgraph - greylist\"/></p>\n";
 
                print "<h4><center>Greylisting Detailansicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";          
                print "<a href='$url12$n'>MX12</a>\n";                        
                print "<a href='$url13$n'>MX13</a>\n";                        
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";        
                print "<p><img src=\"$scriptname?${n}-d\" alt=\"mailgraph - greystats\"/></p>\n";
 
                print "<h4><center>Postscreen (positive) &Uuml;bersicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                     
                print "<a href='$url12$n'>MX12</a>\n";                                   
                print "<a href='$url13$n'>MX13</a>\n";                                   
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                   
                print "<p><img src=\"$scriptname?${n}-v\" alt=\"mailgraph - postscreen\"/></p>\n";
 
                print "<h4><center>Postscreen Detailansicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";         
                print "<a href='$url12$n'>MX12</a>\n";                       
                print "<a href='$url13$n'>MX13</a>\n";                       
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";       
                print "<p><img src=\"$scriptname?${n}-w\" alt=\"mailgraph - postscreenstats\"/></p>\n";
 
                print "<h4><center>&Uuml;bersicht Mail-Queues</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";           
                print "<a href='$url12$n'>MX12</a>\n";                         
                print "<a href='$url13$n'>MX13</a>\n";                         
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";         
                print "<p><img src=\"$scriptname?${n}-q\" alt=\"mailgraph - mailqueues\"/></p>\n";
 
                print "<h4><center>ausgehende DANE/TLSA-gesicherte Verbindungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                             
                print "<a href='$url12$n'>MX12</a>\n";                                           
                print "<a href='$url13$n'>MX13</a>\n";                                           
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                           
                print "<p><img src=\"$scriptname?${n}-t\" alt=\"mailgraph - dane checked\"/></p>\n";
 
                print "<h4><center>ankommende TLS-gesicherte Verbindungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                       
                print "<a href='$url12$n'>MX12</a>\n";                                     
                print "<a href='$url13$n'>MX13</a>\n";                                     
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                     
                print "<p><img src=\"$scriptname?${n}-i\" alt=\"mailgraph - smtpd details\"/></p>\n";
 
                print "<h4><center>Sender policy Framework - SPF-Pr&uuml;fungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                             
                print "<a href='$url12$n'>MX12</a>\n";                                           
                print "<a href='$url13$n'>MX13</a>\n";                                           
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                           
                print "<p><img src=\"$scriptname?${n}-f\" alt=\"mailgraph - spf checked\"/></p>\n";
 
                print "<h4><center>DomainKeys Identified Mail - DKIM-Pr&uuml;fungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                                 
                print "<a href='$url12$n'>MX12</a>\n";                                               
                print "<a href='$url13$n'>MX13</a>\n";                                               
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                               
                print "<p><img src=\"$scriptname?${n}-m\" alt=\"mailgraph - dkim checked\"/></p>\n"; 
 
                print "<h4><center>Domain-based Message Authentication, Reporting & Conformance - DMARC-Pr&uuml;fungen</center></h4>\n";                                                                         
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                                    
                print "<a href='$url12$n'>MX12</a>\n";                                                  
                print "<a href='$url13$n'>MX13</a>\n";                                                  
                rint "<a href='$url14$n'>MX14</a> )</center></h5>\n";                                  
                print "<p><img src=\"$scriptname?${n}-c\" alt=\"mailgraph - dmarc checked\"/></p>\n";   
 
        }
 
        print <<FOOTER;
 <hr/>                 
  <table border="0" style="font-size:12px" width="900">
   <colgroup>                                          
    <col width="225">                                  
    <col width="430">                                  
    <col width="125">                                  
   </colgroup>                                         
   <tr class="row0">                                   
    <td class="col0 leftalign">                        
     <a href="http://dokuwiki.nausch.org/doku.php/centos:mail_c7:mta_13?&#mailgraph_nextgeneration">Mailgraph(-ng) </a>$VERSION by                                                                                
     <a href="mailto:django@mailserver.guru?subject=Mailgraph-NG%20for%20my%20Mailserver">Django</a> based on                                                                                                     
    </td>                                                                                                
    <td>                                                                                                 
     <a href="http://david.schweikert.ch/">David Schweikert's</a> <a href="http://mailgraph.schweikert.ch/">Mailgraph</a>,                                                                                        
     <a href="http://www.gichenbacher.de/kontakt">Markus Neubauer's </a>                                 
     <a href="http://www.std-soft.com/bfaq/46-k-faq-server/117-greygraph-mail-statistik.html">Greygraph</a>,                                                                                                      
    </td>                                                                                                
    <td class="col2 rightalign" rowspan="3">                                                             
     <a href="http://oss.oetiker.ch/rrdtool/"><img src="rrdtool-3dlogo.png" alt="" width="135" height="50" align="right" align="middle"/></a>                                                                     
    </td>                                                                                                
   </tr>                                                                                                 
   <tr class="row1">                                                                                     
    <td class="col0 leftalign">                                                                          
    </td>                                                                                                
    <td class="col1 leftalign">                                                                          
     <a href="http://www.arschkrebs.de/">Ralf Hildebrandt's </a><a href="http://www.arschkrebs.de/postfix/queuegraph">Queuegraph</a> and                                                                          
     <a href="https://www.kernel-error.de/">Sebastian van de Meer's </a> <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt">mailgraphpatch 1</a> and                                              
     <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt-2">mailgraphpatch 2</a>           
    </td>                                                                                                
   </tr>                                                                                                 
  </table>                                                                                               
 </body>                                                                                                 
</html>                                                                                                  
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/mailgraph_$1.png";
                        graph($graphs[$1]{seconds}, $file);         
                        send_image($file);                          
                }                                                   
                elsif($img =~ /^(\d+)-e$/) {                        
                        my $file = "$tmp_dir/$uri/mailgraph_$1_err.png";
                        graph_virus($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-g$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_greylist.png";
                        graph_greylist($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-d$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_greystats.png";
                        graph_greystats($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-v$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_postscreen.png";
                        graph_postscreen($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-w$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_postscreenstats.png";
                        graph_postscreenstats($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-q$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_queue.png";
                        graph_queue($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-t$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dane.png";
                        graph_dane($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-i$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_smtpd.png";
                        graph_smtpd($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-f$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_spf.png";
                        graph_spf($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-m$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dkim.png";
                        graph_dkim($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-c$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dmarc.png";
                        graph_dmarc($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                else {
                        die "ERROR: invalid argument\n";
                }
        }
        else {
                print_html;
        }
}
 
main;

/usr/share/mailgraph-ng/mx12/mailgraph.cgi

 # vim /usr/share/mailgraph-ng/mx12/mailgraph.cgi
/usr/share/mailgraph-ng/mx12/mailgraph.cgi
!/usr/bin/perl -w                                                                
 
# mailgraph -- detailed postfix mail traffic statistics
# copyright (c) 2000-2007 ETH Zurich                   
# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
# modified 2011 for queuegraph by Ralf Hildebrandt <Ralf.Hildebrandt@computerbeschimpfung.de>
# modified 2015 for mailgraph-ng by Django <django@mailserver.guru> based on                 
# patches from  Sebastian van de Meer <kernel-error@kernel-error.de>                         
# released under the GNU General Public License                                              
 
use RRDs;
use POSIX qw(uname);
 
my $VERSION = "1.15";
 
my $host = (POSIX::uname())[1];
my $scriptname = $ENV{"SCRIPT_NAME"};
my $xpoints = 800;                   
my $points_per_sample = 3;           
my $ypoints = 160;                   
my $rrd       = '/var/lib/mailgraph/mx12/mailgraph.rrd';
my $rrd_virus = '/var/lib/mailgraph/mx12/mailgraph_virus.rrd';                                            
my $rrd_grey  = '/var/lib/mailgraph/mx12/mailgraph_grey.rrd';                                              
my $rrd_dane  = '/var/lib/mailgraph/mx12/mailgraph_dane.rrd';                                              
my $rrd_dmarc = '/var/lib/mailgraph/mx12/mailgraph_dmarc.rrd';                                            
my $rrd_smtpd = '/var/lib/mailgraph/mx12/mailgraph_smtpd.rrd';                                           
my $rrd_queue = '/var/lib/mailgraph/mx12/mailqueues.rrd';                                                
my $rrd_post  = '/var/lib/mailgraph/mx12/mailgraph_post.rrd';                                              
 
my $tmp_dir = '/var/cache/mailgraph';                                                                   
my @graphs = (                                                                                                                                                                                                    
        { title => 'Letzter Tag',   seconds => 3600*24,        },                                                                                                                                                 
        { title => 'Letzte Woche',  seconds => 3600*24*7,      },                                                                                                                                                 
        { title => 'Letzter Monat', seconds => 3600*24*31,     },                                                                                                                                                 
        { title => 'Letztes Jahr',  seconds => 3600*24*365,    },                                                                                                                                                 
);                                                                                                                                                                                                                
 
my %color = (                                                           # rrggbb in hex
        # n                                                                            
        sent            => '000099',                                                   
        received        => '009900',                                                   
 
        bounced         => '000000',
        virus           => 'DDBB00',
        spam            => '999999',
        rejected        => 'AA0000',
 
        greylisted      => 'CCCCCC',
        delayed         => '006400',
        whitelist       => '00D8FF',
        awl             => 'FF7700',
        early           => 'AA0000',
 
        pswl            => 'E1FFC1', #
        psbl            => 'EBBAD5',  
        passold         => 'BAFF70', #
        veto            => 'EBEBD5',  
        pregreet        => 'EBA8D5',  
        dnsbl           => 'EB75D5',  
        pipelining      => 'B85BA7',  
        nonsmtp         => '793C6E',  
        barenewline     => '793C2E',  
        command         => '47231B',  
        hangup          => 'C12C0A',  
        passnew         => '468700', #
 
        new             => 'FF77EE',
        reconnectok     => '7700DD',
 
        active          => 'EFEF00',
        deferred        => 'DD8800',
 
        untrustedtls    => 'ffebd1',
        anonymoustls    => 'ffcf90',
        trustedtls      => 'ffb24f',
        verifiedtls     => 'ff5800',
 
        untrustedtlsin  => 'ddd1ff',
        anonymoustlsin  => 'a8a8ff', 
        trustedtlsin    => '6767ff', 
 
        spfnone         => '12FF0A',
        spffail         => 'f80b6f',
        spfpass         => '2E5fEC',
 
        dkimnone        => 'E6E27A',
        dkimfail        => 'FF6600',
        dkimpass        => '3013EC',
 
        dmarcnone       => 'F0B166',
        dmarcfail       => 'f11717',
        dmarcpass       => '00FFD5',
);                                  
 
my $url   = "http://mailgraph-ng.nausch.org/mx/";
my $urlg  = "http://mailgraph-ng.nausch.org/mx/#G";
my $url1  = "http://mailgraph-ng.nausch.org/mx11/";
my $url2  = "http://mailgraph-ng.nausch.org/mx12/";
my $url3  = "http://mailgraph-ng.nausch.org/mx13/";
my $url4  = "http://mailgraph-ng.nausch.org/mx14/";
my $url11 = "http://mailgraph-ng.nausch.org/mx11/#G";
my $url12 = "http://mailgraph-ng.nausch.org/mx12/#G";
my $url13 = "http://mailgraph-ng.nausch.org/mx13/#G";
my $url14 = "http://mailgraph-ng.nausch.org/mx14/#G";
 
sub rrd_graph(@)
{               
        my ($range, $file, $ypoints, @rrdargs) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        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\: %15.0lf msgs',           
                'GPRINT:rsent:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmsent:MAX:max\: %11.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\: %15.0lf msgs',                
                'GPRINT:rrecv:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmrecv:MAX:max\: %11.0lf msgs/min\l',           
        );                                                              
}                                                                       
 
sub graph_virus($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "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,*",                      
                "AREA:rrejected#$color{rejected}:Rejected                ",
                'GPRINT:srejected:MAX:total\: %15.0lf msgs',                
                'GPRINT:rrejected:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmrejected:MAX:max\: %11.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,*",                   
                "AREA:rvirus#$color{virus}:Viruses                 ",
                'GPRINT:svirus:MAX:total\: %15.0lf msgs',            
                'GPRINT:rvirus:AVERAGE:avg\: %12.2lf msgs/min',      
                'GPRINT:rmvirus:MAX:max\: %11.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\: %15.0lf msgs',             
                'GPRINT:rspam:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmspam:MAX:max\: %11.0lf msgs/min\l',        
 
                "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,*",                     
                "LINE2:rbounced#$color{bounced}:Bounced                 ",
                'GPRINT:sbounced:MAX:total\: %15.0lf msgs',               
                'GPRINT:rbounced:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmbounced:MAX:max\: %11.0lf msgs/min\l',          
 
        );
}         
 
 
sub graph_greylist($$)
        {             
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:new=$rrd_grey:new:AVERAGE",
                "DEF:mnew=$rrd_grey:new:MAX",   
                "CDEF:rnew=new,60,*",           
                "CDEF:rmnew=mnew,60,*",         
                "CDEF:dnew=new,UN,0,new,IF,$step,*",
                "CDEF:snew=PREV,UN,dnew,PREV,IF,dnew,+",
                "AREA:rnew#$color{new}:New                     ",
                'GPRINT:snew:MAX:total\: %15.0lf msgs',          
                'GPRINT:rnew:AVERAGE:avg\: %12.2lf msgs/min',    
                'GPRINT:rmnew:MAX:max\: %11.0lf msgs/min\l',     
 
                "DEF:reconnectok=$rrd_grey:reconnectok:AVERAGE",
                "DEF:mreconnectok=$rrd_grey:reconnectok:MAX",   
                "CDEF:rreconnectok=reconnectok,60,*",           
                "CDEF:dreconnectok=reconnectok,UN,0,reconnectok,IF,$step,*",
                "CDEF:sreconnectok=PREV,UN,dreconnectok,PREV,IF,dreconnectok,+",
                "CDEF:rmreconnectok=mreconnectok,60,*",                         
                "LINE2:rreconnectok#$color{reconnectok}:Reconnect O.K.          ",
                'GPRINT:sreconnectok:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rreconnectok:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmreconnectok:MAX:max\: %11.0lf msgs/min\l',              
 
        );
}         
 
 
sub graph_greystats($$)
        {              
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:greylisted=$rrd_grey:greylisted:AVERAGE",
                "DEF:mgreylisted=$rrd_grey:greylisted:MAX",   
                "CDEF:rgreylisted=greylisted,60,*",           
                "CDEF:rmgreylisted=mgreylisted,60,*",         
                "CDEF:dgreylisted=greylisted,UN,0,greylisted,IF,$step,*",
                "CDEF:sgreylisted=PREV,UN,dgreylisted,PREV,IF,dgreylisted,+",
                "AREA:rgreylisted#$color{greylisted}:Greylisted              ",
                'GPRINT:sgreylisted:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rgreylisted:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmgreylisted:MAX:max\: %11.0lf msgs/min\l',            
 
                "DEF:delayed=$rrd_grey:delayed:AVERAGE",
                "DEF:mdelayed=$rrd_grey:delayed:MAX",   
                "CDEF:rdelayed=delayed,60,*",           
                "CDEF:rmdelayed=mdelayed,60,*",         
                "CDEF:ddelayed=delayed,UN,0,delayed,IF,$step,*",
                "CDEF:sdelayed=PREV,UN,ddelayed,PREV,IF,ddelayed,+",
                "LINE2:rdelayed#$color{delayed}:Delayed                 ",
                'GPRINT:sdelayed:MAX:total\: %15.0lf msgs',               
                'GPRINT:rdelayed:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmdelayed:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:whitelist=$rrd_grey:whitelist:AVERAGE",
                "DEF:mwhitelist=$rrd_grey:whitelist:MAX",   
                "CDEF:rwhitelist=whitelist,60,*",           
                "CDEF:rmwhitelist=mwhitelist,60,*",         
                "CDEF:dwhitelist=whitelist,UN,0,whitelist,IF,$step,*",
                "CDEF:swhitelist=PREV,UN,dwhitelist,PREV,IF,dwhitelist,+",
                "AREA:rwhitelist#$color{whitelist}:Whitelist               ",
                'GPRINT:swhitelist:MAX:total\: %15.0lf msgs',                
                'GPRINT:rwhitelist:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmwhitelist:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:awl=$rrd_grey:awl:AVERAGE",
                "DEF:mawl=$rrd_grey:awl:MAX",   
                "CDEF:rawl=awl,60,*",           
                "CDEF:dawl=awl,UN,0,awl,IF,$step,*",
                "CDEF:sawl=PREV,UN,dawl,PREV,IF,dawl,+",
                "CDEF:rmawl=mawl,60,*",                 
                "LINE2:rawl#$color{awl}:Auto whitelist          ",
                'GPRINT:sawl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rawl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmawl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:early=$rrd_grey:early:AVERAGE",
                "DEF:mearly=$rrd_grey:early:MAX",   
                "CDEF:rearly=early,60,*",           
                "CDEF:rmearly=mearly,60,*",         
                "CDEF:dearly=early,UN,0,early,IF,$step,*",
                "CDEF:searly=PREV,UN,dearly,PREV,IF,dearly,+",
                "AREA:rearly#$color{early}:Early connect           ",
                'GPRINT:searly:MAX:total\: %15.0lf msgs',             
                'GPRINT:rearly:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmearly:MAX:max\: %11.0lf msgs/min\l',        
 
 
        );
}         
 
sub graph_postscreen($$)
        {               
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:pswl=$rrd_post:pswl:AVERAGE",
                "DEF:mpswl=$rrd_post:pswl:MAX",   
                "CDEF:rpswl=pswl,60,*",           
                "CDEF:rmpswl=mpswl,60,*",         
                "CDEF:dpswl=pswl,UN,0,pswl,IF,$step,*",
                "CDEF:spswl=PREV,UN,dpswl,PREV,IF,dpswl,+",
                "AREA:rpswl#$color{pswl}:WHITELISTED             ",
                'GPRINT:spswl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rpswl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmpswl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:passold=$rrd_post:passold:AVERAGE",
                "DEF:mpassold=$rrd_post:passold:MAX",   
                "CDEF:rpassold=passold,60,*",           
                "CDEF:dpassold=passold,UN,0,passold,IF,$step,*",
                "CDEF:spassold=PREV,UN,dpassold,PREV,IF,dpassold,+",
                "CDEF:rmpassold=mpassold,60,*",                     
                "STACK:rpassold#$color{passold}:PASS OLD                ",
                'GPRINT:spassold:MAX:total\: %15.0lf msgs',               
                'GPRINT:rpassold:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmpassold:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:passnew=$rrd_post:passnew:AVERAGE",
                "DEF:mpassnew=$rrd_post:passnew:MAX",   
                "CDEF:rpassnew=passnew,60,*",           
                "CDEF:dpassnew=passnew,UN,0,passnew,IF,$step,*",
                "CDEF:spassnew=PREV,UN,dpassnew,PREV,IF,dpassnew,+",
                "CDEF:rmpassnew=mpassnew,60,*",                     
                "LINE2:rpassnew#$color{passnew}:PASS NEW                ",
                'GPRINT:spassnew:MAX:total\: %15.0lf msgs',               
                'GPRINT:rpassnew:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmpassnew:MAX:max\: %11.0lf msgs/min\l',          
        );                                                                
}                                                                         
 
sub graph_postscreenstats($$)
{                            
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:psbl=$rrd_post:psbl:AVERAGE",    
                "DEF:mpsbl=$rrd_post:psbl:MAX",       
                "CDEF:rpsbl=psbl,60,*",               
                "CDEF:dpsbl=psbl,UN,0,psbl,IF,$step,*",
                "CDEF:spsbl=PREV,UN,dpsbl,PREV,IF,dpsbl,+",
                "CDEF:rmpsbl=mpsbl,60,*",                  
                "AREA:rpsbl#$color{psbl}:BLACKLISTED             ",
                'GPRINT:spsbl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rpsbl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmpsbl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:veto=$rrd_post:veto:AVERAGE",
                "DEF:mveto=$rrd_post:veto:MAX",   
                "CDEF:rveto=veto,60,*",           
                "CDEF:dveto=veto,UN,0,veto,IF,$step,*",
                "CDEF:sveto=PREV,UN,dveto,PREV,IF,dveto,+",
                "CDEF:rmveto=mveto,60,*",                  
                "STACK:rveto#$color{veto}:WHITELIST VETO          ",
                'GPRINT:sveto:MAX:total\: %15.0lf msgs',            
                'GPRINT:rveto:AVERAGE:avg\: %12.2lf msgs/min',      
                'GPRINT:rmveto:MAX:max\: %11.0lf msgs/min\l',       
 
                "DEF:pregreet=$rrd_post:pregreet:AVERAGE",
                "DEF:mpregreet=$rrd_post:pregreet:MAX",   
                "CDEF:rpregreet=pregreet,60,*",           
                "CDEF:dpregreet=pregreet,UN,0,pregreet,IF,$step,*",
                "CDEF:spregreet=PREV,UN,dpregreet,PREV,IF,dpregreet,+",
                "CDEF:rmpregreet=mpregreet,60,*",                      
                "STACK:rpregreet#$color{pregreet}:PREGREET                ",
                'GPRINT:spregreet:MAX:total\: %15.0lf msgs',                
                'GPRINT:rpregreet:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmpregreet:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dnsbl=$rrd_post:dnsbl:AVERAGE",
                "DEF:mdnsbl=$rrd_post:dnsbl:MAX",   
                "CDEF:rdnsbl=dnsbl,60,*",           
                "CDEF:ddnsbl=dnsbl,UN,0,dnsbl,IF,$step,*",
                "CDEF:sdnsbl=PREV,UN,ddnsbl,PREV,IF,ddnsbl,+",
                "CDEF:rmdnsbl=mdnsbl,60,*",                   
                "STACK:rdnsbl#$color{dnsbl}:DNSBL                   ",
                'GPRINT:sdnsbl:MAX:total\: %15.0lf msgs',             
                'GPRINT:rdnsbl:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmdnsbl:MAX:max\: %11.0lf msgs/min\l',        
 
                "DEF:pipelining=$rrd_post:pipelining:AVERAGE",
                "DEF:mpipelining=$rrd_post:pipelining:MAX",   
                "CDEF:rpipelining=pipelining,60,*",           
                "CDEF:dpipelining=pipelining,UN,0,pipelining,IF,$step,*",
                "CDEF:spipelining=PREV,UN,dpipelining,PREV,IF,dpipelining,+",
                "CDEF:rmpipelining=mpipelining,60,*",                        
                "STACK:rpipelining#$color{pipelining}:PIPELINING              ",
                'GPRINT:spipelining:MAX:total\: %15.0lf msgs',                  
                'GPRINT:rpipelining:AVERAGE:avg\: %12.2lf msgs/min',            
                'GPRINT:rmpipelining:MAX:max\: %11.0lf msgs/min\l',             
 
                "DEF:nonsmtp=$rrd_post:nonsmtp:AVERAGE",
                "DEF:mnonsmtp=$rrd_post:nonsmtp:MAX",   
                "CDEF:rnonsmtp=nonsmtp,60,*",           
                "CDEF:dnonsmtp=nonsmtp,UN,0,nonsmtp,IF,$step,*",
                "CDEF:snonsmtp=PREV,UN,dnonsmtp,PREV,IF,dnonsmtp,+",
                "CDEF:rmnonsmtp=mnonsmtp,60,*",                     
                "STACK:rnonsmtp#$color{nonsmtp}:NON SMTP COMMAND        ",
                'GPRINT:snonsmtp:MAX:total\: %15.0lf msgs',               
                'GPRINT:rnonsmtp:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmnonsmtp:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:barenewline=$rrd_post:barenewline:AVERAGE",
                "DEF:mbarenewline=$rrd_post:barenewline:MAX",   
                "CDEF:rbarenewline=barenewline,60,*",           
                "CDEF:dbarenewline=barenewline,UN,0,barenewline,IF,$step,*",
                "CDEF:sbarenewline=PREV,UN,dbarenewline,PREV,IF,dbarenewline,+",
                "CDEF:rmbarenewline=mbarenewline,60,*",                         
                "STACK:rbarenewline#$color{barenewline}:BARE NEWLINE            ",
                'GPRINT:sbarenewline:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rbarenewline:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmbarenewline:MAX:max\: %11.0lf msgs/min\l',              
 
                "DEF:command=$rrd_post:command:AVERAGE",
                "DEF:mcommand=$rrd_post:command:MAX",   
                "CDEF:rcommand=command,60,*",           
                "CDEF:dcommand=command,UN,0,command,IF,$step,*",
                "CDEF:scommand=PREV,UN,dcommand,PREV,IF,dcommand,+",
                "CDEF:rmcommand=mcommand,60,*",                     
                "STACK:rcommand#$color{command}:COMMAND LIMITS          ",
                'GPRINT:scommand:MAX:total\: %15.0lf msgs',               
                'GPRINT:rcommand:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmcommand:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:hangup=$rrd_post:hangup:AVERAGE",
                "DEF:mhangup=$rrd_post:hangup:MAX",   
                "CDEF:rhangup=hangup,60,*",           
                "CDEF:dhangup=hangup,UN,0,hangup,IF,$step,*",
                "CDEF:shangup=PREV,UN,dhangup,PREV,IF,dhangup,+",
                "CDEF:rmhangup=mhangup,60,*",                    
                "STACK:rhangup#$color{hangup}:HUNGUP                  ",
                'GPRINT:shangup:MAX:total\: %15.0lf msgs',              
                'GPRINT:rhangup:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:rmhangup:MAX:max\: %11.0lf msgs/min\l',         
        );                                                              
}                                                                       
 
 
 
sub graph_dane($$)
{                 
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:untrustedtls=$rrd_dane:untrustedtls:AVERAGE",
                "DEF:muntrustedtls=$rrd_dane:untrustedtls:MAX",   
                "CDEF:runtrustedtls=untrustedtls,60,*",           
                "CDEF:duntrustedtls=untrustedtls,UN,0,untrustedtls,IF,$step,*",
                "CDEF:suntrustedtls=PREV,UN,duntrustedtls,PREV,IF,duntrustedtls,+",
                "CDEF:rmuntrustedtls=muntrustedtls,60,*",                          
                "AREA:runtrustedtls#$color{untrustedtls}:Out Untrusted TLS       ",
                'GPRINT:suntrustedtls:MAX:total\: %15.0lf msgs',                   
                'GPRINT:runtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmuntrustedtls:MAX:max\: %11.0lf msgs/min\l',              
 
                "DEF:anonymoustls=$rrd_dane:anonymoustls:AVERAGE",
                "DEF:manonymoustls=$rrd_dane:anonymoustls:MAX",   
                "CDEF:ranonymoustls=anonymoustls,60,*",           
                "CDEF:danonymoustls=anonymoustls,UN,0,anonymoustls,IF,$step,*",
                "CDEF:sanonymoustls=PREV,UN,danonymoustls,PREV,IF,danonymoustls,+",
                "CDEF:rmanonymoustls=manonymoustls,60,*",                          
                "STACK:ranonymoustls#$color{anonymoustls}:Out Anonymous TLS       ",
                'GPRINT:sanonymoustls:MAX:total\: %15.0lf msgs',                    
                'GPRINT:ranonymoustls:AVERAGE:avg\: %12.2lf msgs/min',              
                'GPRINT:rmanonymoustls:MAX:max\: %11.0lf msgs/min\l',               
 
                "DEF:trustedtls=$rrd_dane:trustedtls:AVERAGE",
                "DEF:mtrustedtls=$rrd_dane:trustedtls:MAX",   
                "CDEF:rtrustedtls=trustedtls,60,*",           
                "CDEF:dtrustedtls=trustedtls,UN,0,trustedtls,IF,$step,*",
                "CDEF:strustedtls=PREV,UN,dtrustedtls,PREV,IF,dtrustedtls,+",
                "CDEF:rmtrustedtls=mtrustedtls,60,*",                        
                "STACK:rtrustedtls#$color{trustedtls}:Out Trusted TLS         ",
                'GPRINT:strustedtls:MAX:total\: %15.0lf msgs',                  
                'GPRINT:rtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',            
                'GPRINT:rmtrustedtls:MAX:max\: %11.0lf msgs/min\l',             
 
                "DEF:verifiedtls=$rrd_dane:verifiedtls:AVERAGE",
                "DEF:mverifiedtls=$rrd_dane:verifiedtls:MAX",   
                "CDEF:rverifiedtls=verifiedtls,60,*",           
                "CDEF:dverifiedtls=verifiedtls,UN,0,verifiedtls,IF,$step,*",
                "CDEF:sverifiedtls=PREV,UN,dverifiedtls,PREV,IF,dverifiedtls,+",
                "CDEF:rmverifiedtls=mverifiedtls,60,*",                         
                "LINE2:rverifiedtls#$color{verifiedtls}:Out Verified TLS        ",
                'GPRINT:sverifiedtls:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rverifiedtls:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmverifiedtls:MAX:max\: %11.0lf msgs/min\l',              
        );                                                                        
}                                                                                 
 
 
 
sub graph_smtpd($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:untrustedtlsin=$rrd_smtpd:untrustedtlsin:AVERAGE",
                "DEF:muntrustedtlsin=$rrd_smtpd:untrustedtlsin:MAX",   
                "CDEF:runtrustedtlsin=untrustedtlsin,60,*",            
                "CDEF:duntrustedtlsin=untrustedtlsin,UN,0,untrustedtlsin,IF,$step,*",
                "CDEF:suntrustedtlsin=PREV,UN,duntrustedtlsin,PREV,IF,duntrustedtlsin,+",
                "CDEF:rmuntrustedtlsin=muntrustedtlsin,60,*",                            
                "AREA:runtrustedtlsin#$color{untrustedtlsin}:IN Untrusted TLS        ",  
                'GPRINT:suntrustedtlsin:MAX:total\: %15.0lf msgs',                       
                'GPRINT:runtrustedtlsin:AVERAGE:avg\: %12.2lf msgs/min',                 
                'GPRINT:rmuntrustedtlsin:MAX:max\: %11.0lf msgs/min\l',                  
 
                "DEF:anonymoustlsin=$rrd_smtpd:anonymoustlsin:AVERAGE",
                "DEF:manonymoustlsin=$rrd_smtpd:anonymoustlsin:MAX",   
                "CDEF:ranonymoustlsin=anonymoustlsin,60,*",            
                "CDEF:danonymoustlsin=anonymoustlsin,UN,0,anonymoustlsin,IF,$step,*",
                "CDEF:sanonymoustlsin=PREV,UN,danonymoustlsin,PREV,IF,danonymoustlsin,+",
                "CDEF:rmanonymoustlsin=manonymoustlsin,60,*",                            
                "STACK:ranonymoustlsin#$color{anonymoustlsin}:IN Anonymous TLS        ", 
                'GPRINT:sanonymoustlsin:MAX:total\: %15.0lf msgs',                       
                'GPRINT:ranonymoustlsin:AVERAGE:avg\: %12.2lf msgs/min',                 
                'GPRINT:rmanonymoustlsin:MAX:max\: %11.0lf msgs/min\l',                  
 
                "DEF:trustedtlsin=$rrd_smtpd:trustedtlsin:AVERAGE",
                "DEF:mtrustedtlsin=$rrd_smtpd:trustedtlsin:MAX",   
                "CDEF:rtrustedtlsin=trustedtlsin,60,*",            
                "CDEF:dtrustedtlsin=trustedtlsin,UN,0,trustedtlsin,IF,$step,*",
                "CDEF:strustedtlsin=PREV,UN,dtrustedtlsin,PREV,IF,dtrustedtlsin,+",
                "CDEF:rmtrustedtlsin=mtrustedtlsin,60,*",                          
                "STACK:rtrustedtlsin#$color{trustedtlsin}:In Trusted TLS          ",
                'GPRINT:strustedtlsin:MAX:total\: %15.0lf msgs',                    
                'GPRINT:rtrustedtlsin:AVERAGE:avg\: %12.2lf msgs/min',              
                'GPRINT:rmtrustedtlsin:MAX:max\: %11.0lf msgs/min\l',               
        );                                                                          
}                                                                                   
 
 
 
sub graph_spf($$)
{                
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:spfpass=$rrd_dmarc:spfpass:AVERAGE",
                "DEF:mspfpass=$rrd_dmarc:spfpass:MAX",   
                "CDEF:rspfpass=spfpass,60,*",            
                "CDEF:dspfpass=spfpass,UN,0,spfpass,IF,$step,*",
                "CDEF:sspfpass=PREV,UN,dspfpass,PREV,IF,dspfpass,+",
                "CDEF:rmspfpass=mspfpass,60,*",                     
                "AREA:rspfpass#$color{spfpass}:SPF pass                ",
                'GPRINT:sspfpass:MAX:total\: %15.0lf msgs',              
                'GPRINT:rspfpass:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:rmspfpass:MAX:max\: %11.0lf msgs/min\l',         
 
                "DEF:spfnone=$rrd_dmarc:spfnone:AVERAGE",
                "DEF:mspfnone=$rrd_dmarc:spfnone:MAX",   
                "CDEF:rspfnone=spfnone,60,*",            
                "CDEF:dspfnone=spfnone,UN,0,spfnone,IF,$step,*",
                "CDEF:sspfnone=PREV,UN,dspfnone,PREV,IF,dspfnone,+",
                "CDEF:rmspfnone=mspfnone,60,*",                     
                "LINE2:rspfnone#$color{spfnone}:SPF none                ",
                'GPRINT:sspfnone:MAX:total\: %15.0lf msgs',               
                'GPRINT:rspfnone:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmspfnone:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:spffail=$rrd_dmarc:spffail:AVERAGE",
                "DEF:mspffail=$rrd_dmarc:spffail:MAX",   
                "CDEF:rspffail=spffail,60,*",            
                "CDEF:dspffail=spffail,UN,0,spffail,IF,$step,*",
                "CDEF:sspffail=PREV,UN,dspffail,PREV,IF,dspffail,+",
                "CDEF:rmspffail=mspffail,60,*",                     
                "LINE2:rspffail#$color{spffail}:SPF fail                ",
                'GPRINT:sspffail:MAX:total\: %15.0lf msgs',               
                'GPRINT:rspffail:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmspffail:MAX:max\: %11.0lf msgs/min\l',          
        );                                                                
}                                                                         
 
sub graph_dkim($$)
{                 
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:dkimpass=$rrd_dmarc:dkimpass:AVERAGE",
                "DEF:mdkimpass=$rrd_dmarc:dkimpass:MAX",   
                "CDEF:rdkimpass=dkimpass,60,*",            
                "CDEF:ddkimpass=dkimpass,UN,0,dkimpass,IF,$step,*",
                "CDEF:sdkimpass=PREV,UN,ddkimpass,PREV,IF,ddkimpass,+",
                "CDEF:rmdkimpass=mdkimpass,60,*",                      
                "AREA:rdkimpass#$color{dkimpass}:DKIM pass               ",
                'GPRINT:sdkimpass:MAX:total\: %15.0lf msgs',               
                'GPRINT:rdkimpass:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmdkimpass:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:dkimnone=$rrd_dmarc:dkimnone:AVERAGE",
                "DEF:mdkimnone=$rrd_dmarc:dkimnone:MAX",   
                "CDEF:rdkimnone=dkimnone,60,*",            
                "CDEF:ddkimnone=dkimnone,UN,0,dkimnone,IF,$step,*",
                "CDEF:sdkimnone=PREV,UN,ddkimnone,PREV,IF,ddkimnone,+",
                "CDEF:rmdkimnone=mdkimnone,60,*",                      
                "LINE2:rdkimnone#$color{dkimnone}:DKIM none               ",
                'GPRINT:sdkimnone:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdkimnone:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdkimnone:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dkimfail=$rrd_dmarc:dkimfail:AVERAGE",
                "DEF:mdkimfail=$rrd_dmarc:dkimfail:MAX",   
                "CDEF:rdkimfail=dkimfail,60,*",            
                "CDEF:ddkimfail=dkimfail,UN,0,dkimfail,IF,$step,*",
                "CDEF:sdkimfail=PREV,UN,ddkimfail,PREV,IF,ddkimfail,+",
                "CDEF:rmdkimfail=mdkimfail,60,*",                      
                "LINE2:rdkimfail#$color{dkimfail}:DKIM fail               ",
                'GPRINT:sdkimfail:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdkimfail:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdkimfail:MAX:max\: %11.0lf msgs/min\l',           
        );                                                                  
}                                                                           
 
sub graph_dmarc($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:dmarcpass=$rrd_dmarc:dmarcpass:AVERAGE",
                "DEF:mdmarcpass=$rrd_dmarc:dmarcpass:MAX",   
                "CDEF:rdmarcpass=dmarcpass,60,*",            
                "CDEF:ddmarcpass=dmarcpass,UN,0,dmarcpass,IF,$step,*",
                "CDEF:sdmarcpass=PREV,UN,ddmarcpass,PREV,IF,ddmarcpass,+",
                "CDEF:rmdmarcpass=mdmarcpass,60,*",                       
                "AREA:rdmarcpass#$color{dmarcpass}:DMARC pass              ",
                'GPRINT:sdmarcpass:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdmarcpass:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdmarcpass:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dmarcnone=$rrd_dmarc:dmarcnone:AVERAGE",
                "DEF:mdmarcnone=$rrd_dmarc:dmarcnone:MAX",   
                "CDEF:rdmarcnone=dmarcnone,60,*",            
                "CDEF:ddmarcnone=dmarcnone,UN,0,dmarcnone,IF,$step,*",
                "CDEF:sdmarcnone=PREV,UN,ddmarcnone,PREV,IF,ddmarcnone,+",
                "CDEF:rmdmarcnone=mdmarcnone,60,*",                       
                "LINE2:rdmarcnone#$color{dmarcnone}:DMARC none              ",
                'GPRINT:sdmarcnone:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rdmarcnone:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmdmarcnone:MAX:max\: %11.0lf msgs/min\l',            
 
                "DEF:dmarcfail=$rrd_dmarc:dmarcfail:AVERAGE",
                "DEF:mdmarcfail=$rrd_dmarc:dmarcfail:MAX",   
                "CDEF:rdmarcfail=dmarcfail,60,*",            
                "CDEF:ddmarcfail=dmarcfail,UN,0,dmarcfail,IF,$step,*",
                "CDEF:sdmarcfail=PREV,UN,ddmarcfail,PREV,IF,ddmarcfail,+",
                "CDEF:rmdmarcfail=mdmarcfail,60,*",                       
                "LINE2:rdmarcfail#$color{dmarcfail}:DMARC fail              ",
                'GPRINT:sdmarcfail:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rdmarcfail:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmdmarcfail:MAX:max\: %11.0lf msgs/min\l',            
        );                                                                    
}                                                                             
 
 
 
sub graph_queue($$)
        {          
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:deferred=$rrd_queue:deferred:AVERAGE",
                "AREA:deferred#$color{deferred}:Deferred                ",
                'GPRINT:deferred:MAX:total\: %15.0lf msgs',               
                'GPRINT:deferred:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:deferred:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:active=$rrd_queue:active:AVERAGE",
                "LINE2:active#$color{active}:Active+Incoming+Maildrop",
                'GPRINT:active:MAX:total\: %15.0lf msgs',              
                'GPRINT:active:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:active:MAX:max\: %11.0lf msgs/min\l',          
        );                                                             
}                                                                      
 
 
 
 
 
sub print_html()
{               
        print "Content-Type: text/html\n\n";
 
        print <<HEADER;
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">                                                                                                     
<html>                                                                                                   
 <head>                                                                                                  
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />                                  
  <title>Mailserver Statistiken auf $host</title>                                                        
  <meta http-equiv="Refresh" content="300" />                                                            
  <meta http-equiv="Pragma" content="no-cache" />                                                        
  <link rel="stylesheet" href="mailgraph.css" type="text/css" />                                         
 </head>                                                                                                 
 <body>                                                                                                  
HEADER                                                                                                   
 
        print "<h1>Mailserver Statistiken f&uuml;r mx12.dmz.nausch.org</h1>\n";
        print "<h3>(<a href='$url'>Summen-</a> und Einzelaufstellungen ";
        print "<a href='$url1'>MX11</a>, ";                              
        #print "<a href='$url2'>MX12</a>, ";                             
        print "<a href='$url3'>MX13</a> und ";                           
        print "<a href='$url4'>MX14</a></a>)</h4>\n";                    
 
        print "<ul id=\"jump\">\n";
        for my $n (0..$#graphs) {  
                print "  <li><a href=\"#G$n\">$graphs[$n]{title}</a>&nbsp;</li>\n";
        }                                                                          
        print "</ul>\n";                                                           
 
        for my $n (0..$#graphs) {
                print "<h2 id=\"G$n\">$graphs[$n]{title}</h2>\n";
                print "<h4><center>Mail Ein und -Ausgang</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";      
                print "<a href='$url11$n'>MX11</a>\n";                    
                print "<a href='$url13$n'>MX13</a>\n";                    
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";    
                print "<p><img src=\"$scriptname?${n}-n\" alt=\"mailgraph - received and sent\"/></p>\n";
 
                print "<h4><center>geblockte Nachrichten</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";      
                print "<a href='$url11$n'>MX11</a>\n";                    
                print "<a href='$url13$n'>MX13</a>\n";                    
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";    
                print "<p><img src=\"$scriptname?${n}-e\" alt=\"mailgraph - blocked\"/></p>\n";
 
                print "<h4><center>Greylisting &Uuml;bersicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";           
                print "<a href='$url11$n'>MX11</a>\n";                         
                print "<a href='$url13$n'>MX13</a>\n";                         
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";         
                print "<p><img src=\"$scriptname?${n}-g\" alt=\"mailgraph - greylist\"/></p>\n";
 
                print "<h4><center>Greylisting Detailansicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";          
                print "<a href='$url11$n'>MX11</a>\n";                        
                print "<a href='$url13$n'>MX13</a>\n";                        
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";        
                print "<p><img src=\"$scriptname?${n}-d\" alt=\"mailgraph - greystats\"/></p>\n";
 
                print "<h4><center>Postscreen (positive) &Uuml;bersicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                     
                print "<a href='$url11$n'>MX11</a>\n";                                   
                print "<a href='$url13$n'>MX13</a>\n";                                   
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                   
                print "<p><img src=\"$scriptname?${n}-v\" alt=\"mailgraph - postscreen\"/></p>\n";
 
                print "<h4><center>Postscreen Detailansicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";         
                print "<a href='$url11$n'>MX11</a>\n";                       
                print "<a href='$url13$n'>MX13</a>\n";                       
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";       
                print "<p><img src=\"$scriptname?${n}-w\" alt=\"mailgraph - postscreenstats\"/></p>\n";
 
                print "<h4><center>&Uuml;bersicht Mail-Queues</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";           
                print "<a href='$url11$n'>MX11</a>\n";                         
                print "<a href='$url13$n'>MX13</a>\n";                         
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";         
                print "<p><img src=\"$scriptname?${n}-q\" alt=\"mailgraph - mailqueues\"/></p>\n";
 
                print "<h4><center>ausgehende DANE/TLSA-gesicherte Verbindungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                             
                print "<a href='$url11$n'>MX11</a>\n";                                           
                print "<a href='$url13$n'>MX13</a>\n";                                           
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                           
                print "<p><img src=\"$scriptname?${n}-t\" alt=\"mailgraph - dane checked\"/></p>\n";
 
                print "<h4><center>ankommende TLS-gesicherte Verbindungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                       
                print "<a href='$url11$n'>MX11</a>\n";                                     
                print "<a href='$url13$n'>MX13</a>\n";                                     
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                     
                print "<p><img src=\"$scriptname?${n}-i\" alt=\"mailgraph - smtpd details\"/></p>\n";
 
                print "<h4><center>Sender policy Framework - SPF-Pr&uuml;fungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                             
                print "<a href='$url11$n'>MX11</a>\n";                                           
                print "<a href='$url13$n'>MX13</a>\n";                                           
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                           
                print "<p><img src=\"$scriptname?${n}-f\" alt=\"mailgraph - spf checked\"/></p>\n";
 
                print "<h4><center>DomainKeys Identified Mail - DKIM-Pr&uuml;fungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                                 
                print "<a href='$url11$n'>MX11</a>\n";                                               
                print "<a href='$url13$n'>MX13</a>\n";                                               
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                               
                print "<p><img src=\"$scriptname?${n}-m\" alt=\"mailgraph - dkim checked\"/></p>\n"; 
 
                print "<h4><center>Domain-based Message Authentication, Reporting & Conformance - DMARC-Pr&uuml;fungen</center></h4>\n";                                                                         
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                                    
                print "<a href='$url11$n'>MX11</a>\n";                                                  
                print "<a href='$url13$n'>MX13</a>\n";                                                  
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                                  
                print "<p><img src=\"$scriptname?${n}-c\" alt=\"mailgraph - dmarc checked\"/></p>\n";   
 
        }
 
        print <<FOOTER;
 <hr/>                 
  <table border="0" style="font-size:12px" width="900">
   <colgroup>                                          
    <col width="225">                                  
    <col width="430">                                  
    <col width="125">                                  
   </colgroup>                                         
   <tr class="row0">                                   
    <td class="col0 leftalign">                        
     <a href="http://dokuwiki.nausch.org/doku.php/centos:mail_c7:mta_13?&#mailgraph_nextgeneration">Mailgraph(-ng) </a>$VERSION by                                                                                
     <a href="mailto:django@mailserver.guru?subject=Mailgraph-NG%20for%20my%20Mailserver">Django</a> based on                                                                                                     
    </td>                                                                                                
    <td>                                                                                                 
     <a href="http://david.schweikert.ch/">David Schweikert's</a> <a href="http://mailgraph.schweikert.ch/">Mailgraph</a>,                                                                                        
     <a href="http://www.gichenbacher.de/kontakt">Markus Neubauer's </a>                                 
     <a href="http://www.std-soft.com/bfaq/46-k-faq-server/117-greygraph-mail-statistik.html">Greygraph</a>,                                                                                                      
    </td>                                                                                                
    <td class="col2 rightalign" rowspan="3">                                                             
     <a href="http://oss.oetiker.ch/rrdtool/"><img src="rrdtool-3dlogo.png" alt="" width="135" height="50" align="right" align="middle"/></a>                                                                     
    </td>                                                                                                
   </tr>                                                                                                 
   <tr class="row1">                                                                                     
    <td class="col0 leftalign">                                                                          
    </td>                                                                                                
    <td class="col1 leftalign">                                                                          
     <a href="http://www.arschkrebs.de/">Ralf Hildebrandt's </a><a href="http://www.arschkrebs.de/postfix/queuegraph">Queuegraph</a> and                                                                          
     <a href="https://www.kernel-error.de/">Sebastian van de Meer's </a> <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt">mailgraphpatch 1</a> and                                              
     <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt-2">mailgraphpatch 2</a>           
    </td>                                                                                                
   </tr>                                                                                                 
  </table>                                                                                               
 </body>                                                                                                 
</html>                                                                                                  
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/mailgraph_$1.png";
                        graph($graphs[$1]{seconds}, $file);         
                        send_image($file);                          
                }                                                   
                elsif($img =~ /^(\d+)-e$/) {                        
                        my $file = "$tmp_dir/$uri/mailgraph_$1_err.png";
                        graph_virus($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-g$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_greylist.png";
                        graph_greylist($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-d$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_greystats.png";
                        graph_greystats($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-v$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_postscreen.png";
                        graph_postscreen($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-w$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_postscreenstats.png";
                        graph_postscreenstats($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-q$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_queue.png";
                        graph_queue($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-t$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dane.png";
                        graph_dane($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-i$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_smtpd.png";
                        graph_smtpd($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-f$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_spf.png";
                        graph_spf($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-m$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dkim.png";
                        graph_dkim($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-c$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dmarc.png";
                        graph_dmarc($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                else {
                        die "ERROR: invalid argument\n";
                }
        }
        else {
                print_html;
        }
}
 
main;

/usr/share/mailgraph-ng/mx13/mailgraph.cgi

 # vim /usr/share/mailgraph-ng/mx13/mailgraph.cgi
/usr/share/mailgraph-ng/mx13/mailgraph.cgi
#!/usr/bin/perl -w                                                                
 
# mailgraph -- detailed postfix mail traffic statistics
# copyright (c) 2000-2007 ETH Zurich                   
# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
# modified 2011 for queuegraph by Ralf Hildebrandt <Ralf.Hildebrandt@computerbeschimpfung.de>
# modified 2015 for mailgraph-ng by Django <django@mailserver.guru> based on                 
# patches from  Sebastian van de Meer <kernel-error@kernel-error.de>                         
# released under the GNU General Public License                                              
 
use RRDs;
use POSIX qw(uname);
 
my $VERSION = "1.15";
 
my $host = (POSIX::uname())[1];
my $scriptname = $ENV{"SCRIPT_NAME"};
my $xpoints = 800;                   
my $points_per_sample = 3;           
my $ypoints = 160;                   
my $rrd       = '/var/lib/mailgraph/mx13/mailgraph.rrd';
my $rrd_virus = '/var/lib/mailgraph/mx13/mailgraph_virus.rrd';
my $rrd_grey  = '/var/lib/mailgraph/mx13/mailgraph_grey.rrd';
my $rrd_dane  = '/var/lib/mailgraph/mx13/mailgraph_dane.rrd';
my $rrd_dmarc = '/var/lib/mailgraph/mx13/mailgraph_dmarc.rrd';
my $rrd_smtpd = '/var/lib/mailgraph/mx13/mailgraph_smtpd.rrd';
my $rrd_queue = '/var/lib/mailgraph/mx13/mailqueues.rrd';
my $rrd_post  = '/var/lib/mailgraph/mx13/mailgraph_post.rrd';
 
my $tmp_dir = '/var/cache/mailgraph';
my @graphs = (                                                                                                                                                                                                    
        { title => 'Letzter Tag',   seconds => 3600*24,        },                                                                                                                                                 
        { title => 'Letzte Woche',  seconds => 3600*24*7,      },                                                                                                                                                 
        { title => 'Letzter Monat', seconds => 3600*24*31,     },                                                                                                                                                 
        { title => 'Letztes Jahr',  seconds => 3600*24*365,    },                                                                                                                                                 
);                                                                                                                                                                                                                
 
my %color = (                                                           # rrggbb in hex
        # n                                                                            
        sent            => '000099',                                                   
        received        => '009900',                                                   
 
        bounced         => '000000',
        virus           => 'DDBB00',
        spam            => '999999',
        rejected        => 'AA0000',
 
        greylisted      => 'CCCCCC',
        delayed         => '006400',
        whitelist       => '00D8FF',
        awl             => 'FF7700',
        early           => 'AA0000',
 
        pswl            => 'E1FFC1', #
        psbl            => 'EBBAD5',  
        passold         => 'BAFF70', #
        veto            => 'EBEBD5',  
        pregreet        => 'EBA8D5',  
        dnsbl           => 'EB75D5',  
        pipelining      => 'B85BA7',  
        nonsmtp         => '793C6E',  
        barenewline     => '793C2E',  
        command         => '47231B',  
        hangup          => 'C12C0A',  
        passnew         => '468700', #
 
        new             => 'FF77EE',
        reconnectok     => '7700DD',
 
        active          => 'EFEF00',
        deferred        => 'DD8800',
 
        untrustedtls    => 'ffebd1',
        anonymoustls    => 'ffcf90',
        trustedtls      => 'ffb24f',
        verifiedtls     => 'ff5800',
 
        untrustedtlsin  => 'ddd1ff',
        anonymoustlsin  => 'a8a8ff', 
        trustedtlsin    => '6767ff', 
 
        spfnone         => '12FF0A',
        spffail         => 'f80b6f',
        spfpass         => '2E5fEC',
 
        dkimnone        => 'E6E27A',
        dkimfail        => 'FF6600',
        dkimpass        => '3013EC',
 
        dmarcnone       => 'F0B166',
        dmarcfail       => 'f11717',
        dmarcpass       => '00FFD5',
);                                  
 
my $url   = "http://mailgraph-ng.nausch.org/mx/";
my $urlg  = "http://mailgraph-ng.nausch.org/mx/#G";
my $url1  = "http://mailgraph-ng.nausch.org/mx11/";
my $url2  = "http://mailgraph-ng.nausch.org/mx12/";
my $url3  = "http://mailgraph-ng.nausch.org/mx13/";
my $url4  = "http://mailgraph-ng.nausch.org/mx14/";
my $url11 = "http://mailgraph-ng.nausch.org/mx11/#G";
my $url12 = "http://mailgraph-ng.nausch.org/mx12/#G";
my $url13 = "http://mailgraph-ng.nausch.org/mx13/#G";
my $url14 = "http://mailgraph-ng.nausch.org/mx14/#G";
 
sub rrd_graph(@)
{               
        my ($range, $file, $ypoints, @rrdargs) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        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\: %15.0lf msgs',           
                'GPRINT:rsent:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmsent:MAX:max\: %11.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\: %15.0lf msgs',                
                'GPRINT:rrecv:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmrecv:MAX:max\: %11.0lf msgs/min\l',           
        );                                                              
}                                                                       
 
sub graph_virus($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "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,*",                      
                "AREA:rrejected#$color{rejected}:Rejected                ",
                'GPRINT:srejected:MAX:total\: %15.0lf msgs',                
                'GPRINT:rrejected:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmrejected:MAX:max\: %11.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,*",                   
                "AREA:rvirus#$color{virus}:Viruses                 ",
                'GPRINT:svirus:MAX:total\: %15.0lf msgs',            
                'GPRINT:rvirus:AVERAGE:avg\: %12.2lf msgs/min',      
                'GPRINT:rmvirus:MAX:max\: %11.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\: %15.0lf msgs',             
                'GPRINT:rspam:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmspam:MAX:max\: %11.0lf msgs/min\l',        
 
                "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,*",                     
                "LINE2:rbounced#$color{bounced}:Bounced                 ",
                'GPRINT:sbounced:MAX:total\: %15.0lf msgs',               
                'GPRINT:rbounced:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmbounced:MAX:max\: %11.0lf msgs/min\l',          
 
        );
}         
 
 
sub graph_greylist($$)
        {             
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:new=$rrd_grey:new:AVERAGE",
                "DEF:mnew=$rrd_grey:new:MAX",   
                "CDEF:rnew=new,60,*",           
                "CDEF:rmnew=mnew,60,*",         
                "CDEF:dnew=new,UN,0,new,IF,$step,*",
                "CDEF:snew=PREV,UN,dnew,PREV,IF,dnew,+",
                "AREA:rnew#$color{new}:New                     ",
                'GPRINT:snew:MAX:total\: %15.0lf msgs',          
                'GPRINT:rnew:AVERAGE:avg\: %12.2lf msgs/min',    
                'GPRINT:rmnew:MAX:max\: %11.0lf msgs/min\l',     
 
                "DEF:reconnectok=$rrd_grey:reconnectok:AVERAGE",
                "DEF:mreconnectok=$rrd_grey:reconnectok:MAX",   
                "CDEF:rreconnectok=reconnectok,60,*",           
                "CDEF:dreconnectok=reconnectok,UN,0,reconnectok,IF,$step,*",
                "CDEF:sreconnectok=PREV,UN,dreconnectok,PREV,IF,dreconnectok,+",
                "CDEF:rmreconnectok=mreconnectok,60,*",                         
                "LINE2:rreconnectok#$color{reconnectok}:Reconnect O.K.          ",
                'GPRINT:sreconnectok:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rreconnectok:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmreconnectok:MAX:max\: %11.0lf msgs/min\l',              
 
        );
}         
 
 
sub graph_greystats($$)
        {              
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:greylisted=$rrd_grey:greylisted:AVERAGE",
                "DEF:mgreylisted=$rrd_grey:greylisted:MAX",   
                "CDEF:rgreylisted=greylisted,60,*",           
                "CDEF:rmgreylisted=mgreylisted,60,*",         
                "CDEF:dgreylisted=greylisted,UN,0,greylisted,IF,$step,*",
                "CDEF:sgreylisted=PREV,UN,dgreylisted,PREV,IF,dgreylisted,+",
                "AREA:rgreylisted#$color{greylisted}:Greylisted              ",
                'GPRINT:sgreylisted:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rgreylisted:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmgreylisted:MAX:max\: %11.0lf msgs/min\l',            
 
                "DEF:delayed=$rrd_grey:delayed:AVERAGE",
                "DEF:mdelayed=$rrd_grey:delayed:MAX",   
                "CDEF:rdelayed=delayed,60,*",           
                "CDEF:rmdelayed=mdelayed,60,*",         
                "CDEF:ddelayed=delayed,UN,0,delayed,IF,$step,*",
                "CDEF:sdelayed=PREV,UN,ddelayed,PREV,IF,ddelayed,+",
                "LINE2:rdelayed#$color{delayed}:Delayed                 ",
                'GPRINT:sdelayed:MAX:total\: %15.0lf msgs',               
                'GPRINT:rdelayed:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmdelayed:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:whitelist=$rrd_grey:whitelist:AVERAGE",
                "DEF:mwhitelist=$rrd_grey:whitelist:MAX",   
                "CDEF:rwhitelist=whitelist,60,*",           
                "CDEF:rmwhitelist=mwhitelist,60,*",         
                "CDEF:dwhitelist=whitelist,UN,0,whitelist,IF,$step,*",
                "CDEF:swhitelist=PREV,UN,dwhitelist,PREV,IF,dwhitelist,+",
                "AREA:rwhitelist#$color{whitelist}:Whitelist               ",
                'GPRINT:swhitelist:MAX:total\: %15.0lf msgs',                
                'GPRINT:rwhitelist:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmwhitelist:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:awl=$rrd_grey:awl:AVERAGE",
                "DEF:mawl=$rrd_grey:awl:MAX",   
                "CDEF:rawl=awl,60,*",           
                "CDEF:dawl=awl,UN,0,awl,IF,$step,*",
                "CDEF:sawl=PREV,UN,dawl,PREV,IF,dawl,+",
                "CDEF:rmawl=mawl,60,*",                 
                "LINE2:rawl#$color{awl}:Auto whitelist          ",
                'GPRINT:sawl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rawl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmawl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:early=$rrd_grey:early:AVERAGE",
                "DEF:mearly=$rrd_grey:early:MAX",   
                "CDEF:rearly=early,60,*",           
                "CDEF:rmearly=mearly,60,*",         
                "CDEF:dearly=early,UN,0,early,IF,$step,*",
                "CDEF:searly=PREV,UN,dearly,PREV,IF,dearly,+",
                "AREA:rearly#$color{early}:Early connect           ",
                'GPRINT:searly:MAX:total\: %15.0lf msgs',             
                'GPRINT:rearly:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmearly:MAX:max\: %11.0lf msgs/min\l',        
 
 
        );
}         
 
sub graph_postscreen($$)
        {               
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:pswl=$rrd_post:pswl:AVERAGE",
                "DEF:mpswl=$rrd_post:pswl:MAX",   
                "CDEF:rpswl=pswl,60,*",           
                "CDEF:rmpswl=mpswl,60,*",         
                "CDEF:dpswl=pswl,UN,0,pswl,IF,$step,*",
                "CDEF:spswl=PREV,UN,dpswl,PREV,IF,dpswl,+",
                "AREA:rpswl#$color{pswl}:WHITELISTED             ",
                'GPRINT:spswl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rpswl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmpswl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:passold=$rrd_post:passold:AVERAGE",
                "DEF:mpassold=$rrd_post:passold:MAX",   
                "CDEF:rpassold=passold,60,*",           
                "CDEF:dpassold=passold,UN,0,passold,IF,$step,*",
                "CDEF:spassold=PREV,UN,dpassold,PREV,IF,dpassold,+",
                "CDEF:rmpassold=mpassold,60,*",                     
                "STACK:rpassold#$color{passold}:PASS OLD                ",
                'GPRINT:spassold:MAX:total\: %15.0lf msgs',               
                'GPRINT:rpassold:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmpassold:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:passnew=$rrd_post:passnew:AVERAGE",
                "DEF:mpassnew=$rrd_post:passnew:MAX",   
                "CDEF:rpassnew=passnew,60,*",           
                "CDEF:dpassnew=passnew,UN,0,passnew,IF,$step,*",
                "CDEF:spassnew=PREV,UN,dpassnew,PREV,IF,dpassnew,+",
                "CDEF:rmpassnew=mpassnew,60,*",                     
                "LINE2:rpassnew#$color{passnew}:PASS NEW                ",
                'GPRINT:spassnew:MAX:total\: %15.0lf msgs',               
                'GPRINT:rpassnew:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmpassnew:MAX:max\: %11.0lf msgs/min\l',          
        );                                                                
}                                                                         
 
sub graph_postscreenstats($$)
{                            
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:psbl=$rrd_post:psbl:AVERAGE",    
                "DEF:mpsbl=$rrd_post:psbl:MAX",       
                "CDEF:rpsbl=psbl,60,*",               
                "CDEF:dpsbl=psbl,UN,0,psbl,IF,$step,*",
                "CDEF:spsbl=PREV,UN,dpsbl,PREV,IF,dpsbl,+",
                "CDEF:rmpsbl=mpsbl,60,*",                  
                "AREA:rpsbl#$color{psbl}:BLACKLISTED             ",
                'GPRINT:spsbl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rpsbl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmpsbl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:veto=$rrd_post:veto:AVERAGE",
                "DEF:mveto=$rrd_post:veto:MAX",   
                "CDEF:rveto=veto,60,*",           
                "CDEF:dveto=veto,UN,0,veto,IF,$step,*",
                "CDEF:sveto=PREV,UN,dveto,PREV,IF,dveto,+",
                "CDEF:rmveto=mveto,60,*",                  
                "STACK:rveto#$color{veto}:WHITELIST VETO          ",
                'GPRINT:sveto:MAX:total\: %15.0lf msgs',            
                'GPRINT:rveto:AVERAGE:avg\: %12.2lf msgs/min',      
                'GPRINT:rmveto:MAX:max\: %11.0lf msgs/min\l',       
 
                "DEF:pregreet=$rrd_post:pregreet:AVERAGE",
                "DEF:mpregreet=$rrd_post:pregreet:MAX",   
                "CDEF:rpregreet=pregreet,60,*",           
                "CDEF:dpregreet=pregreet,UN,0,pregreet,IF,$step,*",
                "CDEF:spregreet=PREV,UN,dpregreet,PREV,IF,dpregreet,+",
                "CDEF:rmpregreet=mpregreet,60,*",                      
                "STACK:rpregreet#$color{pregreet}:PREGREET                ",
                'GPRINT:spregreet:MAX:total\: %15.0lf msgs',                
                'GPRINT:rpregreet:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmpregreet:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dnsbl=$rrd_post:dnsbl:AVERAGE",
                "DEF:mdnsbl=$rrd_post:dnsbl:MAX",   
                "CDEF:rdnsbl=dnsbl,60,*",           
                "CDEF:ddnsbl=dnsbl,UN,0,dnsbl,IF,$step,*",
                "CDEF:sdnsbl=PREV,UN,ddnsbl,PREV,IF,ddnsbl,+",
                "CDEF:rmdnsbl=mdnsbl,60,*",                   
                "STACK:rdnsbl#$color{dnsbl}:DNSBL                   ",
                'GPRINT:sdnsbl:MAX:total\: %15.0lf msgs',             
                'GPRINT:rdnsbl:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmdnsbl:MAX:max\: %11.0lf msgs/min\l',        
 
                "DEF:pipelining=$rrd_post:pipelining:AVERAGE",
                "DEF:mpipelining=$rrd_post:pipelining:MAX",   
                "CDEF:rpipelining=pipelining,60,*",           
                "CDEF:dpipelining=pipelining,UN,0,pipelining,IF,$step,*",
                "CDEF:spipelining=PREV,UN,dpipelining,PREV,IF,dpipelining,+",
                "CDEF:rmpipelining=mpipelining,60,*",                        
                "STACK:rpipelining#$color{pipelining}:PIPELINING              ",
                'GPRINT:spipelining:MAX:total\: %15.0lf msgs',                  
                'GPRINT:rpipelining:AVERAGE:avg\: %12.2lf msgs/min',            
                'GPRINT:rmpipelining:MAX:max\: %11.0lf msgs/min\l',             
 
                "DEF:nonsmtp=$rrd_post:nonsmtp:AVERAGE",
                "DEF:mnonsmtp=$rrd_post:nonsmtp:MAX",   
                "CDEF:rnonsmtp=nonsmtp,60,*",           
                "CDEF:dnonsmtp=nonsmtp,UN,0,nonsmtp,IF,$step,*",
                "CDEF:snonsmtp=PREV,UN,dnonsmtp,PREV,IF,dnonsmtp,+",
                "CDEF:rmnonsmtp=mnonsmtp,60,*",                     
                "STACK:rnonsmtp#$color{nonsmtp}:NON SMTP COMMAND        ",
                'GPRINT:snonsmtp:MAX:total\: %15.0lf msgs',               
                'GPRINT:rnonsmtp:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmnonsmtp:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:barenewline=$rrd_post:barenewline:AVERAGE",
                "DEF:mbarenewline=$rrd_post:barenewline:MAX",   
                "CDEF:rbarenewline=barenewline,60,*",           
                "CDEF:dbarenewline=barenewline,UN,0,barenewline,IF,$step,*",
                "CDEF:sbarenewline=PREV,UN,dbarenewline,PREV,IF,dbarenewline,+",
                "CDEF:rmbarenewline=mbarenewline,60,*",                         
                "STACK:rbarenewline#$color{barenewline}:BARE NEWLINE            ",
                'GPRINT:sbarenewline:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rbarenewline:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmbarenewline:MAX:max\: %11.0lf msgs/min\l',              
 
                "DEF:command=$rrd_post:command:AVERAGE",
                "DEF:mcommand=$rrd_post:command:MAX",   
                "CDEF:rcommand=command,60,*",           
                "CDEF:dcommand=command,UN,0,command,IF,$step,*",
                "CDEF:scommand=PREV,UN,dcommand,PREV,IF,dcommand,+",
                "CDEF:rmcommand=mcommand,60,*",                     
                "STACK:rcommand#$color{command}:COMMAND LIMITS          ",
                'GPRINT:scommand:MAX:total\: %15.0lf msgs',               
                'GPRINT:rcommand:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmcommand:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:hangup=$rrd_post:hangup:AVERAGE",
                "DEF:mhangup=$rrd_post:hangup:MAX",   
                "CDEF:rhangup=hangup,60,*",           
                "CDEF:dhangup=hangup,UN,0,hangup,IF,$step,*",
                "CDEF:shangup=PREV,UN,dhangup,PREV,IF,dhangup,+",
                "CDEF:rmhangup=mhangup,60,*",                    
                "STACK:rhangup#$color{hangup}:HUNGUP                  ",
                'GPRINT:shangup:MAX:total\: %15.0lf msgs',              
                'GPRINT:rhangup:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:rmhangup:MAX:max\: %11.0lf msgs/min\l',         
        );                                                              
}                                                                       
 
 
 
sub graph_dane($$)
{                 
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:untrustedtls=$rrd_dane:untrustedtls:AVERAGE",
                "DEF:muntrustedtls=$rrd_dane:untrustedtls:MAX",   
                "CDEF:runtrustedtls=untrustedtls,60,*",           
                "CDEF:duntrustedtls=untrustedtls,UN,0,untrustedtls,IF,$step,*",
                "CDEF:suntrustedtls=PREV,UN,duntrustedtls,PREV,IF,duntrustedtls,+",
                "CDEF:rmuntrustedtls=muntrustedtls,60,*",                          
                "AREA:runtrustedtls#$color{untrustedtls}:Out Untrusted TLS       ",
                'GPRINT:suntrustedtls:MAX:total\: %15.0lf msgs',                   
                'GPRINT:runtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmuntrustedtls:MAX:max\: %11.0lf msgs/min\l',              
 
                "DEF:anonymoustls=$rrd_dane:anonymoustls:AVERAGE",
                "DEF:manonymoustls=$rrd_dane:anonymoustls:MAX",   
                "CDEF:ranonymoustls=anonymoustls,60,*",           
                "CDEF:danonymoustls=anonymoustls,UN,0,anonymoustls,IF,$step,*",
                "CDEF:sanonymoustls=PREV,UN,danonymoustls,PREV,IF,danonymoustls,+",
                "CDEF:rmanonymoustls=manonymoustls,60,*",                          
                "STACK:ranonymoustls#$color{anonymoustls}:Out Anonymous TLS       ",
                'GPRINT:sanonymoustls:MAX:total\: %15.0lf msgs',                    
                'GPRINT:ranonymoustls:AVERAGE:avg\: %12.2lf msgs/min',              
                'GPRINT:rmanonymoustls:MAX:max\: %11.0lf msgs/min\l',               
 
                "DEF:trustedtls=$rrd_dane:trustedtls:AVERAGE",
                "DEF:mtrustedtls=$rrd_dane:trustedtls:MAX",   
                "CDEF:rtrustedtls=trustedtls,60,*",           
                "CDEF:dtrustedtls=trustedtls,UN,0,trustedtls,IF,$step,*",
                "CDEF:strustedtls=PREV,UN,dtrustedtls,PREV,IF,dtrustedtls,+",
                "CDEF:rmtrustedtls=mtrustedtls,60,*",                        
                "STACK:rtrustedtls#$color{trustedtls}:Out Trusted TLS         ",
                'GPRINT:strustedtls:MAX:total\: %15.0lf msgs',                  
                'GPRINT:rtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',            
                'GPRINT:rmtrustedtls:MAX:max\: %11.0lf msgs/min\l',             
 
                "DEF:verifiedtls=$rrd_dane:verifiedtls:AVERAGE",
                "DEF:mverifiedtls=$rrd_dane:verifiedtls:MAX",   
                "CDEF:rverifiedtls=verifiedtls,60,*",           
                "CDEF:dverifiedtls=verifiedtls,UN,0,verifiedtls,IF,$step,*",
                "CDEF:sverifiedtls=PREV,UN,dverifiedtls,PREV,IF,dverifiedtls,+",
                "CDEF:rmverifiedtls=mverifiedtls,60,*",                         
                "LINE2:rverifiedtls#$color{verifiedtls}:Out Verified TLS        ",
                'GPRINT:sverifiedtls:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rverifiedtls:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmverifiedtls:MAX:max\: %11.0lf msgs/min\l',              
        );                                                                        
}                                                                                 
 
 
 
sub graph_smtpd($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:untrustedtlsin=$rrd_smtpd:untrustedtlsin:AVERAGE",
                "DEF:muntrustedtlsin=$rrd_smtpd:untrustedtlsin:MAX",   
                "CDEF:runtrustedtlsin=untrustedtlsin,60,*",            
                "CDEF:duntrustedtlsin=untrustedtlsin,UN,0,untrustedtlsin,IF,$step,*",
                "CDEF:suntrustedtlsin=PREV,UN,duntrustedtlsin,PREV,IF,duntrustedtlsin,+",
                "CDEF:rmuntrustedtlsin=muntrustedtlsin,60,*",                            
                "AREA:runtrustedtlsin#$color{untrustedtlsin}:IN Untrusted TLS        ",  
                'GPRINT:suntrustedtlsin:MAX:total\: %15.0lf msgs',                       
                'GPRINT:runtrustedtlsin:AVERAGE:avg\: %12.2lf msgs/min',                 
                'GPRINT:rmuntrustedtlsin:MAX:max\: %11.0lf msgs/min\l',                  
 
                "DEF:anonymoustlsin=$rrd_smtpd:anonymoustlsin:AVERAGE",
                "DEF:manonymoustlsin=$rrd_smtpd:anonymoustlsin:MAX",   
                "CDEF:ranonymoustlsin=anonymoustlsin,60,*",            
                "CDEF:danonymoustlsin=anonymoustlsin,UN,0,anonymoustlsin,IF,$step,*",
                "CDEF:sanonymoustlsin=PREV,UN,danonymoustlsin,PREV,IF,danonymoustlsin,+",
                "CDEF:rmanonymoustlsin=manonymoustlsin,60,*",                            
                "STACK:ranonymoustlsin#$color{anonymoustlsin}:IN Anonymous TLS        ", 
                'GPRINT:sanonymoustlsin:MAX:total\: %15.0lf msgs',                       
                'GPRINT:ranonymoustlsin:AVERAGE:avg\: %12.2lf msgs/min',                 
                'GPRINT:rmanonymoustlsin:MAX:max\: %11.0lf msgs/min\l',                  
 
                "DEF:trustedtlsin=$rrd_smtpd:trustedtlsin:AVERAGE",
                "DEF:mtrustedtlsin=$rrd_smtpd:trustedtlsin:MAX",   
                "CDEF:rtrustedtlsin=trustedtlsin,60,*",            
                "CDEF:dtrustedtlsin=trustedtlsin,UN,0,trustedtlsin,IF,$step,*",
                "CDEF:strustedtlsin=PREV,UN,dtrustedtlsin,PREV,IF,dtrustedtlsin,+",
                "CDEF:rmtrustedtlsin=mtrustedtlsin,60,*",                          
                "STACK:rtrustedtlsin#$color{trustedtlsin}:In Trusted TLS          ",
                'GPRINT:strustedtlsin:MAX:total\: %15.0lf msgs',                    
                'GPRINT:rtrustedtlsin:AVERAGE:avg\: %12.2lf msgs/min',              
                'GPRINT:rmtrustedtlsin:MAX:max\: %11.0lf msgs/min\l',               
        );                                                                          
}                                                                                   
 
 
 
sub graph_spf($$)
{                
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:spfpass=$rrd_dmarc:spfpass:AVERAGE",
                "DEF:mspfpass=$rrd_dmarc:spfpass:MAX",   
                "CDEF:rspfpass=spfpass,60,*",            
                "CDEF:dspfpass=spfpass,UN,0,spfpass,IF,$step,*",
                "CDEF:sspfpass=PREV,UN,dspfpass,PREV,IF,dspfpass,+",
                "CDEF:rmspfpass=mspfpass,60,*",                     
                "AREA:rspfpass#$color{spfpass}:SPF pass                ",
                'GPRINT:sspfpass:MAX:total\: %15.0lf msgs',              
                'GPRINT:rspfpass:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:rmspfpass:MAX:max\: %11.0lf msgs/min\l',         
 
                "DEF:spfnone=$rrd_dmarc:spfnone:AVERAGE",
                "DEF:mspfnone=$rrd_dmarc:spfnone:MAX",   
                "CDEF:rspfnone=spfnone,60,*",            
                "CDEF:dspfnone=spfnone,UN,0,spfnone,IF,$step,*",
                "CDEF:sspfnone=PREV,UN,dspfnone,PREV,IF,dspfnone,+",
                "CDEF:rmspfnone=mspfnone,60,*",                     
                "LINE2:rspfnone#$color{spfnone}:SPF none                ",
                'GPRINT:sspfnone:MAX:total\: %15.0lf msgs',               
                'GPRINT:rspfnone:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmspfnone:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:spffail=$rrd_dmarc:spffail:AVERAGE",
                "DEF:mspffail=$rrd_dmarc:spffail:MAX",   
                "CDEF:rspffail=spffail,60,*",            
                "CDEF:dspffail=spffail,UN,0,spffail,IF,$step,*",
                "CDEF:sspffail=PREV,UN,dspffail,PREV,IF,dspffail,+",
                "CDEF:rmspffail=mspffail,60,*",                     
                "LINE2:rspffail#$color{spffail}:SPF fail                ",
                'GPRINT:sspffail:MAX:total\: %15.0lf msgs',               
                'GPRINT:rspffail:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmspffail:MAX:max\: %11.0lf msgs/min\l',          
        );                                                                
}                                                                         
 
sub graph_dkim($$)
{                 
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:dkimpass=$rrd_dmarc:dkimpass:AVERAGE",
                "DEF:mdkimpass=$rrd_dmarc:dkimpass:MAX",   
                "CDEF:rdkimpass=dkimpass,60,*",            
                "CDEF:ddkimpass=dkimpass,UN,0,dkimpass,IF,$step,*",
                "CDEF:sdkimpass=PREV,UN,ddkimpass,PREV,IF,ddkimpass,+",
                "CDEF:rmdkimpass=mdkimpass,60,*",                      
                "AREA:rdkimpass#$color{dkimpass}:DKIM pass               ",
                'GPRINT:sdkimpass:MAX:total\: %15.0lf msgs',               
                'GPRINT:rdkimpass:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmdkimpass:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:dkimnone=$rrd_dmarc:dkimnone:AVERAGE",
                "DEF:mdkimnone=$rrd_dmarc:dkimnone:MAX",   
                "CDEF:rdkimnone=dkimnone,60,*",            
                "CDEF:ddkimnone=dkimnone,UN,0,dkimnone,IF,$step,*",
                "CDEF:sdkimnone=PREV,UN,ddkimnone,PREV,IF,ddkimnone,+",
                "CDEF:rmdkimnone=mdkimnone,60,*",                      
                "LINE2:rdkimnone#$color{dkimnone}:DKIM none               ",
                'GPRINT:sdkimnone:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdkimnone:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdkimnone:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dkimfail=$rrd_dmarc:dkimfail:AVERAGE",
                "DEF:mdkimfail=$rrd_dmarc:dkimfail:MAX",   
                "CDEF:rdkimfail=dkimfail,60,*",            
                "CDEF:ddkimfail=dkimfail,UN,0,dkimfail,IF,$step,*",
                "CDEF:sdkimfail=PREV,UN,ddkimfail,PREV,IF,ddkimfail,+",
                "CDEF:rmdkimfail=mdkimfail,60,*",                      
                "LINE2:rdkimfail#$color{dkimfail}:DKIM fail               ",
                'GPRINT:sdkimfail:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdkimfail:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdkimfail:MAX:max\: %11.0lf msgs/min\l',           
        );                                                                  
}                                                                           
 
sub graph_dmarc($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:dmarcpass=$rrd_dmarc:dmarcpass:AVERAGE",
                "DEF:mdmarcpass=$rrd_dmarc:dmarcpass:MAX",   
                "CDEF:rdmarcpass=dmarcpass,60,*",            
                "CDEF:ddmarcpass=dmarcpass,UN,0,dmarcpass,IF,$step,*",
                "CDEF:sdmarcpass=PREV,UN,ddmarcpass,PREV,IF,ddmarcpass,+",
                "CDEF:rmdmarcpass=mdmarcpass,60,*",                       
                "AREA:rdmarcpass#$color{dmarcpass}:DMARC pass              ",
                'GPRINT:sdmarcpass:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdmarcpass:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdmarcpass:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dmarcnone=$rrd_dmarc:dmarcnone:AVERAGE",
                "DEF:mdmarcnone=$rrd_dmarc:dmarcnone:MAX",   
                "CDEF:rdmarcnone=dmarcnone,60,*",            
                "CDEF:ddmarcnone=dmarcnone,UN,0,dmarcnone,IF,$step,*",
                "CDEF:sdmarcnone=PREV,UN,ddmarcnone,PREV,IF,ddmarcnone,+",
                "CDEF:rmdmarcnone=mdmarcnone,60,*",                       
                "LINE2:rdmarcnone#$color{dmarcnone}:DMARC none              ",
                'GPRINT:sdmarcnone:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rdmarcnone:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmdmarcnone:MAX:max\: %11.0lf msgs/min\l',            
 
                "DEF:dmarcfail=$rrd_dmarc:dmarcfail:AVERAGE",
                "DEF:mdmarcfail=$rrd_dmarc:dmarcfail:MAX",   
                "CDEF:rdmarcfail=dmarcfail,60,*",            
                "CDEF:ddmarcfail=dmarcfail,UN,0,dmarcfail,IF,$step,*",
                "CDEF:sdmarcfail=PREV,UN,ddmarcfail,PREV,IF,ddmarcfail,+",
                "CDEF:rmdmarcfail=mdmarcfail,60,*",                       
                "LINE2:rdmarcfail#$color{dmarcfail}:DMARC fail              ",
                'GPRINT:sdmarcfail:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rdmarcfail:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmdmarcfail:MAX:max\: %11.0lf msgs/min\l',            
        );                                                                    
}                                                                             
 
 
 
sub graph_queue($$)
        {          
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:deferred=$rrd_queue:deferred:AVERAGE",
                "AREA:deferred#$color{deferred}:Deferred                ",
                'GPRINT:deferred:MAX:total\: %15.0lf msgs',               
                'GPRINT:deferred:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:deferred:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:active=$rrd_queue:active:AVERAGE",
                "LINE2:active#$color{active}:Active+Incoming+Maildrop",
                'GPRINT:active:MAX:total\: %15.0lf msgs',              
                'GPRINT:active:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:active:MAX:max\: %11.0lf msgs/min\l',          
        );                                                             
}                                                                      
 
 
 
 
 
sub print_html()
{               
        print "Content-Type: text/html\n\n";
 
        print <<HEADER;
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">                                                                                                     
<html>                                                                                                   
 <head>                                                                                                  
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />                                  
  <title>Mailserver Statistiken auf $host</title>                                                        
  <meta http-equiv="Refresh" content="300" />                                                            
  <meta http-equiv="Pragma" content="no-cache" />                                                        
  <link rel="stylesheet" href="mailgraph.css" type="text/css" />                                         
 </head>                                                                                                 
 <body>                                                                                                  
HEADER                                                                                                   
 
        print "<h1>Mailserver Statistiken f&uuml;r mx13.dmz.nausch.org</h1>\n";
        print "<h3>(<a href='$url'>Summen-</a> und Einzelaufstellungen ";
        print "<a href='$url1'>MX11</a>, ";                              
        print "<a href='$url2'>MX12</a> und ";                           
        #print "<a href='$url3'>MX13</a> und ";                          
        print "<a href='$url4'>MX14</a></a>)</h4>\n";                    
 
        print "<ul id=\"jump\">\n";
        for my $n (0..$#graphs) {  
                print "  <li><a href=\"#G$n\">$graphs[$n]{title}</a>&nbsp;</li>\n";
        }                                                                          
        print "</ul>\n";                                                           
 
        for my $n (0..$#graphs) {
                print "<h2 id=\"G$n\">$graphs[$n]{title}</h2>\n";
                print "<h4><center>Mail Ein und -Ausgang</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";      
                print "<a href='$url11$n'>MX11</a>\n";                    
                print "<a href='$url12$n'>MX12</a>\n";                    
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";    
                print "<p><img src=\"$scriptname?${n}-n\" alt=\"mailgraph - received and sent\"/></p>\n";
 
                print "<h4><center>geblockte Nachrichten</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";      
                print "<a href='$url11$n'>MX11</a>\n";                    
                print "<a href='$url12$n'>MX12</a>\n";                    
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";    
                print "<p><img src=\"$scriptname?${n}-e\" alt=\"mailgraph - blocked\"/></p>\n";
 
                print "<h4><center>Greylisting &Uuml;bersicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";           
                print "<a href='$url11$n'>MX11</a>\n";                         
                print "<a href='$url12$n'>MX12</a>\n";                         
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";         
                print "<p><img src=\"$scriptname?${n}-g\" alt=\"mailgraph - greylist\"/></p>\n";
 
                print "<h4><center>Greylisting Detailansicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";          
                print "<a href='$url11$n'>MX11</a>\n";                        
                print "<a href='$url12$n'>MX12</a>\n";                        
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";        
                print "<p><img src=\"$scriptname?${n}-d\" alt=\"mailgraph - greystats\"/></p>\n";
 
                print "<h4><center>Postscreen (positive) &Uuml;bersicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                     
                print "<a href='$url11$n'>MX11</a>\n";                                   
                print "<a href='$url12$n'>MX12</a>\n";                                   
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                   
                print "<p><img src=\"$scriptname?${n}-v\" alt=\"mailgraph - postscreen\"/></p>\n";
 
                print "<h4><center>Postscreen Detailansicht</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";         
                print "<a href='$url11$n'>MX11</a>\n";                       
                print "<a href='$url12$n'>MX12</a>\n";                       
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";       
                print "<p><img src=\"$scriptname?${n}-w\" alt=\"mailgraph - postscreenstats\"/></p>\n";
 
                print "<h4><center>&Uuml;bersicht Mail-Queues</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";           
                print "<a href='$url11$n'>MX11</a>\n";                         
                print "<a href='$url12$n'>MX12</a>\n";                         
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";         
                print "<p><img src=\"$scriptname?${n}-q\" alt=\"mailgraph - mailqueues\"/></p>\n";
 
                print "<h4><center>ausgehende DANE/TLSA-gesicherte Verbindungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                             
                print "<a href='$url11$n'>MX11</a>\n";                                           
                print "<a href='$url12$n'>MX12</a>\n";                                           
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                           
                print "<p><img src=\"$scriptname?${n}-t\" alt=\"mailgraph - dane checked\"/></p>\n";
 
                print "<h4><center>ankommende TLS-gesicherte Verbindungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                       
                print "<a href='$url11$n'>MX11</a>\n";                                     
                print "<a href='$url12$n'>MX12</a>\n";                                     
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                     
                print "<p><img src=\"$scriptname?${n}-i\" alt=\"mailgraph - smtpd details\"/></p>\n";
 
                print "<h4><center>Sender policy Framework - SPF-Pr&uuml;fungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                             
                print "<a href='$url11$n'>MX11</a>\n";                                           
                print "<a href='$url12$n'>MX12</a>\n";                                           
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                           
                print "<p><img src=\"$scriptname?${n}-f\" alt=\"mailgraph - spf checked\"/></p>\n";
 
                print "<h4><center>DomainKeys Identified Mail - DKIM-Pr&uuml;fungen</center></h4>\n";
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                                 
                print "<a href='$url11$n'>MX11</a>\n";                                               
                print "<a href='$url12$n'>MX12</a>\n";                                               
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                               
                print "<p><img src=\"$scriptname?${n}-m\" alt=\"mailgraph - dkim checked\"/></p>\n"; 
 
                print "<h4><center>Domain-based Message Authentication, Reporting & Conformance - DMARC-Pr&uuml;fungen</center></h4>\n";                                                                         
                print "<h5><center>( <a href='$urlg$n'>Summe</a>\n";                                    
                print "<a href='$url11$n'>MX11</a>\n";                                                  
                print "<a href='$url12$n'>MX12</a>\n";                                                  
                print "<a href='$url14$n'>MX14</a> )</center></h5>\n";                                  
                print "<p><img src=\"$scriptname?${n}-c\" alt=\"mailgraph - dmarc checked\"/></p>\n";   
 
        }
 
        print <<FOOTER;
 <hr/>                 
  <table border="0" style="font-size:12px" width="900">
   <colgroup>                                          
    <col width="225">                                  
    <col width="430">                                  
    <col width="125">                                  
   </colgroup>                                         
   <tr class="row0">                                   
    <td class="col0 leftalign">                        
     <a href="http://dokuwiki.nausch.org/doku.php/centos:mail_c7:mta_13?&#mailgraph_nextgeneration">Mailgraph(-ng) </a>$VERSION by                                                                                
     <a href="mailto:django@mailserver.guru?subject=Mailgraph-NG%20for%20my%20Mailserver">Django</a> based on                                                                                                     
    </td>                                                                                                
    <td>                                                                                                 
     <a href="http://david.schweikert.ch/">David Schweikert's</a> <a href="http://mailgraph.schweikert.ch/">Mailgraph</a>,                                                                                        
     <a href="http://www.gichenbacher.de/kontakt">Markus Neubauer's </a>                                 
     <a href="http://www.std-soft.com/bfaq/46-k-faq-server/117-greygraph-mail-statistik.html">Greygraph</a>,                                                                                                      
    </td>                                                                                                
    <td class="col2 rightalign" rowspan="3">                                                             
     <a href="http://oss.oetiker.ch/rrdtool/"><img src="rrdtool-3dlogo.png" alt="" width="135" height="50" align="right" align="middle"/></a>                                                                     
    </td>                                                                                                
   </tr>                                                                                                 
   <tr class="row1">                                                                                     
    <td class="col0 leftalign">                                                                          
    </td>                                                                                                
    <td class="col1 leftalign">                                                                          
     <a href="http://www.arschkrebs.de/">Ralf Hildebrandt's </a><a href="http://www.arschkrebs.de/postfix/queuegraph">Queuegraph</a> and                                                                          
     <a href="https://www.kernel-error.de/">Sebastian van de Meer's </a> <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt">mailgraphpatch 1</a> and                                              
     <a href="https://www.kernel-error.de/postfix/mailgraph-aufgebohrt-2">mailgraphpatch 2</a>           
    </td>                                                                                                
   </tr>                                                                                                 
  </table>                                                                                               
 </body>                                                                                                 
</html>                                                                                                  
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/mailgraph_$1.png";
                        graph($graphs[$1]{seconds}, $file);         
                        send_image($file);                          
                }                                                   
                elsif($img =~ /^(\d+)-e$/) {                        
                        my $file = "$tmp_dir/$uri/mailgraph_$1_err.png";
                        graph_virus($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-g$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_greylist.png";
                        graph_greylist($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-d$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_greystats.png";
                        graph_greystats($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-v$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_postscreen.png";
                        graph_postscreen($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-w$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_postscreenstats.png";
                        graph_postscreenstats($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-q$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_queue.png";
                        graph_queue($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-t$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dane.png";
                        graph_dane($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-i$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_smtpd.png";
                        graph_smtpd($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-f$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_spf.png";
                        graph_spf($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-m$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dkim.png";
                        graph_dkim($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                elsif($img =~ /^(\d+)-c$/) {
                        my $file = "$tmp_dir/$uri/mailgraph_$1_dmarc.png";
                        graph_dmarc($graphs[$1]{seconds}, $file);
                        send_image($file);
                }
                else {
                        die "ERROR: invalid argument\n";
                }
        }
        else {
                print_html;
        }
}
 
main;

/usr/share/mailgraph-ng/mx14/mailgraph.cgi

 # vim /usr/share/mailgraph-ng/mx14/mailgraph.cgi
/usr/share/mailgraph-ng/mx14/mailgraph.cgi
#!/usr/bin/perl -w                                                                
 
# mailgraph -- detailed postfix mail traffic statistics
# copyright (c) 2000-2007 ETH Zurich                   
# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
# modified 2011 for queuegraph by Ralf Hildebrandt <Ralf.Hildebrandt@computerbeschimpfung.de>
# modified 2015 for mailgraph-ng by Django <django@mailserver.guru> based on                 
# patches from  Sebastian van de Meer <kernel-error@kernel-error.de>                         
# released under the GNU General Public License                                              
 
use RRDs;
use POSIX qw(uname);
 
my $VERSION = "1.15";
 
my $host = (POSIX::uname())[1];
my $scriptname = $ENV{"SCRIPT_NAME"};
my $xpoints = 800;                   
my $points_per_sample = 3;           
my $ypoints = 160;                   
my $rrd       = '/var/lib/mailgraph/mx14/mailgraph.rrd';
my $rrd_virus = '/var/lib/mailgraph/mx14/mailgraph_virus.rrd';
my $rrd_grey  = '/var/lib/mailgraph/mx14/mailgraph_grey.rrd';
my $rrd_dane  = '/var/lib/mailgraph/mx14/mailgraph_dane.rrd';
my $rrd_dmarc = '/var/lib/mailgraph/mx14/mailgraph_dmarc.rrd';
my $rrd_smtpd = '/var/lib/mailgraph/mx14/mailgraph_smtpd.rrd';
my $rrd_queue = '/var/lib/mailgraph/mx14/mailqueues.rrd';
my $rrd_post  = '/var/lib/mailgraph/mx14/mailgraph_post.rrd';
 
my $tmp_dir = '/var/cache/mailgraph';
my @graphs = (                                                                                                                                                                                                    
        { title => 'Letzter Tag',   seconds => 3600*24,        },                                                                                                                                                 
        { title => 'Letzte Woche',  seconds => 3600*24*7,      },                                                                                                                                                 
        { title => 'Letzter Monat', seconds => 3600*24*31,     },                                                                                                                                                 
        { title => 'Letztes Jahr',  seconds => 3600*24*365,    },                                                                                                                                                 
);                                                                                                                                                                                                                
 
my %color = (                                                           # rrggbb in hex
        # n                                                                            
        sent            => '000099',                                                   
        received        => '009900',                                                   
 
        bounced         => '000000',
        virus           => 'DDBB00',
        spam            => '999999',
        rejected        => 'AA0000',
 
        greylisted      => 'CCCCCC',
        delayed         => '006400',
        whitelist       => '00D8FF',
        awl             => 'FF7700',
        early           => 'AA0000',
 
        pswl            => 'E1FFC1', #
        psbl            => 'EBBAD5',  
        passold         => 'BAFF70', #
        veto            => 'EBEBD5',  
        pregreet        => 'EBA8D5',  
        dnsbl           => 'EB75D5',  
        pipelining      => 'B85BA7',  
        nonsmtp         => '793C6E',  
        barenewline     => '793C2E',  
        command         => '47231B',  
        hangup          => 'C12C0A',  
        passnew         => '468700', #
 
        new             => 'FF77EE',
        reconnectok     => '7700DD',
 
        active          => 'EFEF00',
        deferred        => 'DD8800',
 
        untrustedtls    => 'ffebd1',
        anonymoustls    => 'ffcf90',
        trustedtls      => 'ffb24f',
        verifiedtls     => 'ff5800',
 
        untrustedtlsin  => 'ddd1ff',
        anonymoustlsin  => 'a8a8ff', 
        trustedtlsin    => '6767ff', 
 
        spfnone         => '12FF0A',
        spffail         => 'f80b6f',
        spfpass         => '2E5fEC',
 
        dkimnone        => 'E6E27A',
        dkimfail        => 'FF6600',
        dkimpass        => '3013EC',
 
        dmarcnone       => 'F0B166',
        dmarcfail       => 'f11717',
        dmarcpass       => '00FFD5',
);                                  
 
my $url   = "http://mailgraph-ng.nausch.org/mx/";
my $urlg  = "http://mailgraph-ng.nausch.org/mx/#G";
my $url1  = "http://mailgraph-ng.nausch.org/mx11/";
my $url2  = "http://mailgraph-ng.nausch.org/mx12/";
my $url3  = "http://mailgraph-ng.nausch.org/mx13/";
my $url4  = "http://mailgraph-ng.nausch.org/mx14/";
my $url11 = "http://mailgraph-ng.nausch.org/mx11/#G";
my $url12 = "http://mailgraph-ng.nausch.org/mx12/#G";
my $url13 = "http://mailgraph-ng.nausch.org/mx13/#G";
my $url14 = "http://mailgraph-ng.nausch.org/mx14/#G";
 
sub rrd_graph(@)
{               
        my ($range, $file, $ypoints, @rrdargs) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        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\: %15.0lf msgs',           
                'GPRINT:rsent:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmsent:MAX:max\: %11.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\: %15.0lf msgs',                
                'GPRINT:rrecv:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmrecv:MAX:max\: %11.0lf msgs/min\l',           
        );                                                              
}                                                                       
 
sub graph_virus($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "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,*",                      
                "AREA:rrejected#$color{rejected}:Rejected                ",
                'GPRINT:srejected:MAX:total\: %15.0lf msgs',                
                'GPRINT:rrejected:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmrejected:MAX:max\: %11.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,*",                   
                "AREA:rvirus#$color{virus}:Viruses                 ",
                'GPRINT:svirus:MAX:total\: %15.0lf msgs',            
                'GPRINT:rvirus:AVERAGE:avg\: %12.2lf msgs/min',      
                'GPRINT:rmvirus:MAX:max\: %11.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\: %15.0lf msgs',             
                'GPRINT:rspam:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmspam:MAX:max\: %11.0lf msgs/min\l',        
 
                "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,*",                     
                "LINE2:rbounced#$color{bounced}:Bounced                 ",
                'GPRINT:sbounced:MAX:total\: %15.0lf msgs',               
                'GPRINT:rbounced:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmbounced:MAX:max\: %11.0lf msgs/min\l',          
 
        );
}         
 
 
sub graph_greylist($$)
        {             
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:new=$rrd_grey:new:AVERAGE",
                "DEF:mnew=$rrd_grey:new:MAX",   
                "CDEF:rnew=new,60,*",           
                "CDEF:rmnew=mnew,60,*",         
                "CDEF:dnew=new,UN,0,new,IF,$step,*",
                "CDEF:snew=PREV,UN,dnew,PREV,IF,dnew,+",
                "AREA:rnew#$color{new}:New                     ",
                'GPRINT:snew:MAX:total\: %15.0lf msgs',          
                'GPRINT:rnew:AVERAGE:avg\: %12.2lf msgs/min',    
                'GPRINT:rmnew:MAX:max\: %11.0lf msgs/min\l',     
 
                "DEF:reconnectok=$rrd_grey:reconnectok:AVERAGE",
                "DEF:mreconnectok=$rrd_grey:reconnectok:MAX",   
                "CDEF:rreconnectok=reconnectok,60,*",           
                "CDEF:dreconnectok=reconnectok,UN,0,reconnectok,IF,$step,*",
                "CDEF:sreconnectok=PREV,UN,dreconnectok,PREV,IF,dreconnectok,+",
                "CDEF:rmreconnectok=mreconnectok,60,*",                         
                "LINE2:rreconnectok#$color{reconnectok}:Reconnect O.K.          ",
                'GPRINT:sreconnectok:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rreconnectok:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmreconnectok:MAX:max\: %11.0lf msgs/min\l',              
 
        );
}         
 
 
sub graph_greystats($$)
        {              
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:greylisted=$rrd_grey:greylisted:AVERAGE",
                "DEF:mgreylisted=$rrd_grey:greylisted:MAX",   
                "CDEF:rgreylisted=greylisted,60,*",           
                "CDEF:rmgreylisted=mgreylisted,60,*",         
                "CDEF:dgreylisted=greylisted,UN,0,greylisted,IF,$step,*",
                "CDEF:sgreylisted=PREV,UN,dgreylisted,PREV,IF,dgreylisted,+",
                "AREA:rgreylisted#$color{greylisted}:Greylisted              ",
                'GPRINT:sgreylisted:MAX:total\: %15.0lf msgs',                 
                'GPRINT:rgreylisted:AVERAGE:avg\: %12.2lf msgs/min',           
                'GPRINT:rmgreylisted:MAX:max\: %11.0lf msgs/min\l',            
 
                "DEF:delayed=$rrd_grey:delayed:AVERAGE",
                "DEF:mdelayed=$rrd_grey:delayed:MAX",   
                "CDEF:rdelayed=delayed,60,*",           
                "CDEF:rmdelayed=mdelayed,60,*",         
                "CDEF:ddelayed=delayed,UN,0,delayed,IF,$step,*",
                "CDEF:sdelayed=PREV,UN,ddelayed,PREV,IF,ddelayed,+",
                "LINE2:rdelayed#$color{delayed}:Delayed                 ",
                'GPRINT:sdelayed:MAX:total\: %15.0lf msgs',               
                'GPRINT:rdelayed:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmdelayed:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:whitelist=$rrd_grey:whitelist:AVERAGE",
                "DEF:mwhitelist=$rrd_grey:whitelist:MAX",   
                "CDEF:rwhitelist=whitelist,60,*",           
                "CDEF:rmwhitelist=mwhitelist,60,*",         
                "CDEF:dwhitelist=whitelist,UN,0,whitelist,IF,$step,*",
                "CDEF:swhitelist=PREV,UN,dwhitelist,PREV,IF,dwhitelist,+",
                "AREA:rwhitelist#$color{whitelist}:Whitelist               ",
                'GPRINT:swhitelist:MAX:total\: %15.0lf msgs',                
                'GPRINT:rwhitelist:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmwhitelist:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:awl=$rrd_grey:awl:AVERAGE",
                "DEF:mawl=$rrd_grey:awl:MAX",   
                "CDEF:rawl=awl,60,*",           
                "CDEF:dawl=awl,UN,0,awl,IF,$step,*",
                "CDEF:sawl=PREV,UN,dawl,PREV,IF,dawl,+",
                "CDEF:rmawl=mawl,60,*",                 
                "LINE2:rawl#$color{awl}:Auto whitelist          ",
                'GPRINT:sawl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rawl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmawl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:early=$rrd_grey:early:AVERAGE",
                "DEF:mearly=$rrd_grey:early:MAX",   
                "CDEF:rearly=early,60,*",           
                "CDEF:rmearly=mearly,60,*",         
                "CDEF:dearly=early,UN,0,early,IF,$step,*",
                "CDEF:searly=PREV,UN,dearly,PREV,IF,dearly,+",
                "AREA:rearly#$color{early}:Early connect           ",
                'GPRINT:searly:MAX:total\: %15.0lf msgs',             
                'GPRINT:rearly:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmearly:MAX:max\: %11.0lf msgs/min\l',        
 
 
        );
}         
 
sub graph_postscreen($$)
        {               
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
 
                "DEF:pswl=$rrd_post:pswl:AVERAGE",
                "DEF:mpswl=$rrd_post:pswl:MAX",   
                "CDEF:rpswl=pswl,60,*",           
                "CDEF:rmpswl=mpswl,60,*",         
                "CDEF:dpswl=pswl,UN,0,pswl,IF,$step,*",
                "CDEF:spswl=PREV,UN,dpswl,PREV,IF,dpswl,+",
                "AREA:rpswl#$color{pswl}:WHITELISTED             ",
                'GPRINT:spswl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rpswl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmpswl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:passold=$rrd_post:passold:AVERAGE",
                "DEF:mpassold=$rrd_post:passold:MAX",   
                "CDEF:rpassold=passold,60,*",           
                "CDEF:dpassold=passold,UN,0,passold,IF,$step,*",
                "CDEF:spassold=PREV,UN,dpassold,PREV,IF,dpassold,+",
                "CDEF:rmpassold=mpassold,60,*",                     
                "STACK:rpassold#$color{passold}:PASS OLD                ",
                'GPRINT:spassold:MAX:total\: %15.0lf msgs',               
                'GPRINT:rpassold:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmpassold:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:passnew=$rrd_post:passnew:AVERAGE",
                "DEF:mpassnew=$rrd_post:passnew:MAX",   
                "CDEF:rpassnew=passnew,60,*",           
                "CDEF:dpassnew=passnew,UN,0,passnew,IF,$step,*",
                "CDEF:spassnew=PREV,UN,dpassnew,PREV,IF,dpassnew,+",
                "CDEF:rmpassnew=mpassnew,60,*",                     
                "LINE2:rpassnew#$color{passnew}:PASS NEW                ",
                'GPRINT:spassnew:MAX:total\: %15.0lf msgs',               
                'GPRINT:rpassnew:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmpassnew:MAX:max\: %11.0lf msgs/min\l',          
        );                                                                
}                                                                         
 
sub graph_postscreenstats($$)
{                            
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:psbl=$rrd_post:psbl:AVERAGE",    
                "DEF:mpsbl=$rrd_post:psbl:MAX",       
                "CDEF:rpsbl=psbl,60,*",               
                "CDEF:dpsbl=psbl,UN,0,psbl,IF,$step,*",
                "CDEF:spsbl=PREV,UN,dpsbl,PREV,IF,dpsbl,+",
                "CDEF:rmpsbl=mpsbl,60,*",                  
                "AREA:rpsbl#$color{psbl}:BLACKLISTED             ",
                'GPRINT:spsbl:MAX:total\: %15.0lf msgs',           
                'GPRINT:rpsbl:AVERAGE:avg\: %12.2lf msgs/min',     
                'GPRINT:rmpsbl:MAX:max\: %11.0lf msgs/min\l',      
 
                "DEF:veto=$rrd_post:veto:AVERAGE",
                "DEF:mveto=$rrd_post:veto:MAX",   
                "CDEF:rveto=veto,60,*",           
                "CDEF:dveto=veto,UN,0,veto,IF,$step,*",
                "CDEF:sveto=PREV,UN,dveto,PREV,IF,dveto,+",
                "CDEF:rmveto=mveto,60,*",                  
                "STACK:rveto#$color{veto}:WHITELIST VETO          ",
                'GPRINT:sveto:MAX:total\: %15.0lf msgs',            
                'GPRINT:rveto:AVERAGE:avg\: %12.2lf msgs/min',      
                'GPRINT:rmveto:MAX:max\: %11.0lf msgs/min\l',       
 
                "DEF:pregreet=$rrd_post:pregreet:AVERAGE",
                "DEF:mpregreet=$rrd_post:pregreet:MAX",   
                "CDEF:rpregreet=pregreet,60,*",           
                "CDEF:dpregreet=pregreet,UN,0,pregreet,IF,$step,*",
                "CDEF:spregreet=PREV,UN,dpregreet,PREV,IF,dpregreet,+",
                "CDEF:rmpregreet=mpregreet,60,*",                      
                "STACK:rpregreet#$color{pregreet}:PREGREET                ",
                'GPRINT:spregreet:MAX:total\: %15.0lf msgs',                
                'GPRINT:rpregreet:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmpregreet:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dnsbl=$rrd_post:dnsbl:AVERAGE",
                "DEF:mdnsbl=$rrd_post:dnsbl:MAX",   
                "CDEF:rdnsbl=dnsbl,60,*",           
                "CDEF:ddnsbl=dnsbl,UN,0,dnsbl,IF,$step,*",
                "CDEF:sdnsbl=PREV,UN,ddnsbl,PREV,IF,ddnsbl,+",
                "CDEF:rmdnsbl=mdnsbl,60,*",                   
                "STACK:rdnsbl#$color{dnsbl}:DNSBL                   ",
                'GPRINT:sdnsbl:MAX:total\: %15.0lf msgs',             
                'GPRINT:rdnsbl:AVERAGE:avg\: %12.2lf msgs/min',       
                'GPRINT:rmdnsbl:MAX:max\: %11.0lf msgs/min\l',        
 
                "DEF:pipelining=$rrd_post:pipelining:AVERAGE",
                "DEF:mpipelining=$rrd_post:pipelining:MAX",   
                "CDEF:rpipelining=pipelining,60,*",           
                "CDEF:dpipelining=pipelining,UN,0,pipelining,IF,$step,*",
                "CDEF:spipelining=PREV,UN,dpipelining,PREV,IF,dpipelining,+",
                "CDEF:rmpipelining=mpipelining,60,*",                        
                "STACK:rpipelining#$color{pipelining}:PIPELINING              ",
                'GPRINT:spipelining:MAX:total\: %15.0lf msgs',                  
                'GPRINT:rpipelining:AVERAGE:avg\: %12.2lf msgs/min',            
                'GPRINT:rmpipelining:MAX:max\: %11.0lf msgs/min\l',             
 
                "DEF:nonsmtp=$rrd_post:nonsmtp:AVERAGE",
                "DEF:mnonsmtp=$rrd_post:nonsmtp:MAX",   
                "CDEF:rnonsmtp=nonsmtp,60,*",           
                "CDEF:dnonsmtp=nonsmtp,UN,0,nonsmtp,IF,$step,*",
                "CDEF:snonsmtp=PREV,UN,dnonsmtp,PREV,IF,dnonsmtp,+",
                "CDEF:rmnonsmtp=mnonsmtp,60,*",                     
                "STACK:rnonsmtp#$color{nonsmtp}:NON SMTP COMMAND        ",
                'GPRINT:snonsmtp:MAX:total\: %15.0lf msgs',               
                'GPRINT:rnonsmtp:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmnonsmtp:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:barenewline=$rrd_post:barenewline:AVERAGE",
                "DEF:mbarenewline=$rrd_post:barenewline:MAX",   
                "CDEF:rbarenewline=barenewline,60,*",           
                "CDEF:dbarenewline=barenewline,UN,0,barenewline,IF,$step,*",
                "CDEF:sbarenewline=PREV,UN,dbarenewline,PREV,IF,dbarenewline,+",
                "CDEF:rmbarenewline=mbarenewline,60,*",                         
                "STACK:rbarenewline#$color{barenewline}:BARE NEWLINE            ",
                'GPRINT:sbarenewline:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rbarenewline:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmbarenewline:MAX:max\: %11.0lf msgs/min\l',              
 
                "DEF:command=$rrd_post:command:AVERAGE",
                "DEF:mcommand=$rrd_post:command:MAX",   
                "CDEF:rcommand=command,60,*",           
                "CDEF:dcommand=command,UN,0,command,IF,$step,*",
                "CDEF:scommand=PREV,UN,dcommand,PREV,IF,dcommand,+",
                "CDEF:rmcommand=mcommand,60,*",                     
                "STACK:rcommand#$color{command}:COMMAND LIMITS          ",
                'GPRINT:scommand:MAX:total\: %15.0lf msgs',               
                'GPRINT:rcommand:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmcommand:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:hangup=$rrd_post:hangup:AVERAGE",
                "DEF:mhangup=$rrd_post:hangup:MAX",   
                "CDEF:rhangup=hangup,60,*",           
                "CDEF:dhangup=hangup,UN,0,hangup,IF,$step,*",
                "CDEF:shangup=PREV,UN,dhangup,PREV,IF,dhangup,+",
                "CDEF:rmhangup=mhangup,60,*",                    
                "STACK:rhangup#$color{hangup}:HUNGUP                  ",
                'GPRINT:shangup:MAX:total\: %15.0lf msgs',              
                'GPRINT:rhangup:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:rmhangup:MAX:max\: %11.0lf msgs/min\l',         
        );                                                              
}                                                                       
 
 
 
sub graph_dane($$)
{                 
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:untrustedtls=$rrd_dane:untrustedtls:AVERAGE",
                "DEF:muntrustedtls=$rrd_dane:untrustedtls:MAX",   
                "CDEF:runtrustedtls=untrustedtls,60,*",           
                "CDEF:duntrustedtls=untrustedtls,UN,0,untrustedtls,IF,$step,*",
                "CDEF:suntrustedtls=PREV,UN,duntrustedtls,PREV,IF,duntrustedtls,+",
                "CDEF:rmuntrustedtls=muntrustedtls,60,*",                          
                "AREA:runtrustedtls#$color{untrustedtls}:Out Untrusted TLS       ",
                'GPRINT:suntrustedtls:MAX:total\: %15.0lf msgs',                   
                'GPRINT:runtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmuntrustedtls:MAX:max\: %11.0lf msgs/min\l',              
 
                "DEF:anonymoustls=$rrd_dane:anonymoustls:AVERAGE",
                "DEF:manonymoustls=$rrd_dane:anonymoustls:MAX",   
                "CDEF:ranonymoustls=anonymoustls,60,*",           
                "CDEF:danonymoustls=anonymoustls,UN,0,anonymoustls,IF,$step,*",
                "CDEF:sanonymoustls=PREV,UN,danonymoustls,PREV,IF,danonymoustls,+",
                "CDEF:rmanonymoustls=manonymoustls,60,*",                          
                "STACK:ranonymoustls#$color{anonymoustls}:Out Anonymous TLS       ",
                'GPRINT:sanonymoustls:MAX:total\: %15.0lf msgs',                    
                'GPRINT:ranonymoustls:AVERAGE:avg\: %12.2lf msgs/min',              
                'GPRINT:rmanonymoustls:MAX:max\: %11.0lf msgs/min\l',               
 
                "DEF:trustedtls=$rrd_dane:trustedtls:AVERAGE",
                "DEF:mtrustedtls=$rrd_dane:trustedtls:MAX",   
                "CDEF:rtrustedtls=trustedtls,60,*",           
                "CDEF:dtrustedtls=trustedtls,UN,0,trustedtls,IF,$step,*",
                "CDEF:strustedtls=PREV,UN,dtrustedtls,PREV,IF,dtrustedtls,+",
                "CDEF:rmtrustedtls=mtrustedtls,60,*",                        
                "STACK:rtrustedtls#$color{trustedtls}:Out Trusted TLS         ",
                'GPRINT:strustedtls:MAX:total\: %15.0lf msgs',                  
                'GPRINT:rtrustedtls:AVERAGE:avg\: %12.2lf msgs/min',            
                'GPRINT:rmtrustedtls:MAX:max\: %11.0lf msgs/min\l',             
 
                "DEF:verifiedtls=$rrd_dane:verifiedtls:AVERAGE",
                "DEF:mverifiedtls=$rrd_dane:verifiedtls:MAX",   
                "CDEF:rverifiedtls=verifiedtls,60,*",           
                "CDEF:dverifiedtls=verifiedtls,UN,0,verifiedtls,IF,$step,*",
                "CDEF:sverifiedtls=PREV,UN,dverifiedtls,PREV,IF,dverifiedtls,+",
                "CDEF:rmverifiedtls=mverifiedtls,60,*",                         
                "LINE2:rverifiedtls#$color{verifiedtls}:Out Verified TLS        ",
                'GPRINT:sverifiedtls:MAX:total\: %15.0lf msgs',                   
                'GPRINT:rverifiedtls:AVERAGE:avg\: %12.2lf msgs/min',             
                'GPRINT:rmverifiedtls:MAX:max\: %11.0lf msgs/min\l',              
        );                                                                        
}                                                                                 
 
 
 
sub graph_smtpd($$)
{                  
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:untrustedtlsin=$rrd_smtpd:untrustedtlsin:AVERAGE",
                "DEF:muntrustedtlsin=$rrd_smtpd:untrustedtlsin:MAX",   
                "CDEF:runtrustedtlsin=untrustedtlsin,60,*",            
                "CDEF:duntrustedtlsin=untrustedtlsin,UN,0,untrustedtlsin,IF,$step,*",
                "CDEF:suntrustedtlsin=PREV,UN,duntrustedtlsin,PREV,IF,duntrustedtlsin,+",
                "CDEF:rmuntrustedtlsin=muntrustedtlsin,60,*",                            
                "AREA:runtrustedtlsin#$color{untrustedtlsin}:IN Untrusted TLS        ",  
                'GPRINT:suntrustedtlsin:MAX:total\: %15.0lf msgs',                       
                'GPRINT:runtrustedtlsin:AVERAGE:avg\: %12.2lf msgs/min',                 
                'GPRINT:rmuntrustedtlsin:MAX:max\: %11.0lf msgs/min\l',                  
 
                "DEF:anonymoustlsin=$rrd_smtpd:anonymoustlsin:AVERAGE",
                "DEF:manonymoustlsin=$rrd_smtpd:anonymoustlsin:MAX",   
                "CDEF:ranonymoustlsin=anonymoustlsin,60,*",            
                "CDEF:danonymoustlsin=anonymoustlsin,UN,0,anonymoustlsin,IF,$step,*",
                "CDEF:sanonymoustlsin=PREV,UN,danonymoustlsin,PREV,IF,danonymoustlsin,+",
                "CDEF:rmanonymoustlsin=manonymoustlsin,60,*",                            
                "STACK:ranonymoustlsin#$color{anonymoustlsin}:IN Anonymous TLS        ", 
                'GPRINT:sanonymoustlsin:MAX:total\: %15.0lf msgs',                       
                'GPRINT:ranonymoustlsin:AVERAGE:avg\: %12.2lf msgs/min',                 
                'GPRINT:rmanonymoustlsin:MAX:max\: %11.0lf msgs/min\l',                  
 
                "DEF:trustedtlsin=$rrd_smtpd:trustedtlsin:AVERAGE",
                "DEF:mtrustedtlsin=$rrd_smtpd:trustedtlsin:MAX",   
                "CDEF:rtrustedtlsin=trustedtlsin,60,*",            
                "CDEF:dtrustedtlsin=trustedtlsin,UN,0,trustedtlsin,IF,$step,*",
                "CDEF:strustedtlsin=PREV,UN,dtrustedtlsin,PREV,IF,dtrustedtlsin,+",
                "CDEF:rmtrustedtlsin=mtrustedtlsin,60,*",                          
                "STACK:rtrustedtlsin#$color{trustedtlsin}:In Trusted TLS          ",
                'GPRINT:strustedtlsin:MAX:total\: %15.0lf msgs',                    
                'GPRINT:rtrustedtlsin:AVERAGE:avg\: %12.2lf msgs/min',              
                'GPRINT:rmtrustedtlsin:MAX:max\: %11.0lf msgs/min\l',               
        );                                                                          
}                                                                                   
 
 
 
sub graph_spf($$)
{                
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:spfpass=$rrd_dmarc:spfpass:AVERAGE",
                "DEF:mspfpass=$rrd_dmarc:spfpass:MAX",   
                "CDEF:rspfpass=spfpass,60,*",            
                "CDEF:dspfpass=spfpass,UN,0,spfpass,IF,$step,*",
                "CDEF:sspfpass=PREV,UN,dspfpass,PREV,IF,dspfpass,+",
                "CDEF:rmspfpass=mspfpass,60,*",                     
                "AREA:rspfpass#$color{spfpass}:SPF pass                ",
                'GPRINT:sspfpass:MAX:total\: %15.0lf msgs',              
                'GPRINT:rspfpass:AVERAGE:avg\: %12.2lf msgs/min',        
                'GPRINT:rmspfpass:MAX:max\: %11.0lf msgs/min\l',         
 
                "DEF:spfnone=$rrd_dmarc:spfnone:AVERAGE",
                "DEF:mspfnone=$rrd_dmarc:spfnone:MAX",   
                "CDEF:rspfnone=spfnone,60,*",            
                "CDEF:dspfnone=spfnone,UN,0,spfnone,IF,$step,*",
                "CDEF:sspfnone=PREV,UN,dspfnone,PREV,IF,dspfnone,+",
                "CDEF:rmspfnone=mspfnone,60,*",                     
                "LINE2:rspfnone#$color{spfnone}:SPF none                ",
                'GPRINT:sspfnone:MAX:total\: %15.0lf msgs',               
                'GPRINT:rspfnone:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmspfnone:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:spffail=$rrd_dmarc:spffail:AVERAGE",
                "DEF:mspffail=$rrd_dmarc:spffail:MAX",   
                "CDEF:rspffail=spffail,60,*",            
                "CDEF:dspffail=spffail,UN,0,spffail,IF,$step,*",
                "CDEF:sspffail=PREV,UN,dspffail,PREV,IF,dspffail,+",
                "CDEF:rmspffail=mspffail,60,*",                     
                "LINE2:rspffail#$color{spffail}:SPF fail                ",
                'GPRINT:sspffail:MAX:total\: %15.0lf msgs',               
                'GPRINT:rspffail:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmspffail:MAX:max\: %11.0lf msgs/min\l',          
        );                                                                
}                                                                         
 
sub graph_dkim($$)
{                 
        my ($range, $file) = @_;
        my $step = $range*$points_per_sample/$xpoints;
        rrd_graph($range, $file, $ypoints,            
                "DEF:dkimpass=$rrd_dmarc:dkimpass:AVERAGE",
                "DEF:mdkimpass=$rrd_dmarc:dkimpass:MAX",   
                "CDEF:rdkimpass=dkimpass,60,*",            
                "CDEF:ddkimpass=dkimpass,UN,0,dkimpass,IF,$step,*",
                "CDEF:sdkimpass=PREV,UN,ddkimpass,PREV,IF,ddkimpass,+",
                "CDEF:rmdkimpass=mdkimpass,60,*",                      
                "AREA:rdkimpass#$color{dkimpass}:DKIM pass               ",
                'GPRINT:sdkimpass:MAX:total\: %15.0lf msgs',               
                'GPRINT:rdkimpass:AVERAGE:avg\: %12.2lf msgs/min',         
                'GPRINT:rmdkimpass:MAX:max\: %11.0lf msgs/min\l',          
 
                "DEF:dkimnone=$rrd_dmarc:dkimnone:AVERAGE",
                "DEF:mdkimnone=$rrd_dmarc:dkimnone:MAX",   
                "CDEF:rdkimnone=dkimnone,60,*",            
                "CDEF:ddkimnone=dkimnone,UN,0,dkimnone,IF,$step,*",
                "CDEF:sdkimnone=PREV,UN,ddkimnone,PREV,IF,ddkimnone,+",
                "CDEF:rmdkimnone=mdkimnone,60,*",                      
                "LINE2:rdkimnone#$color{dkimnone}:DKIM none               ",
                'GPRINT:sdkimnone:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdkimnone:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdkimnone:MAX:max\: %11.0lf msgs/min\l',           
 
                "DEF:dkimfail=$rrd_dmarc:dkimfail:AVERAGE",
                "DEF:mdkimfail=$rrd_dmarc:dkimfail:MAX",   
                "CDEF:rdkimfail=dkimfail,60,*",            
                "CDEF:ddkimfail=dkimfail,UN,0,dkimfail,IF,$step,*",
                "CDEF:sdkimfail=PREV,UN,ddkimfail,PREV,IF,ddkimfail,+",
                "CDEF:rmdkimfail=mdkimfail,60,*",                      
                "LINE2:rdkimfail#$color{dkimfail}:DKIM fail               ",
                'GPRINT:sdkimfail:MAX:total\: %15.0lf msgs',                
                'GPRINT:rdkimfail:AVERAGE:avg\: %12.2lf msgs/min',          
                'GPRINT:rmdkimfail:MAX:max\: %11.0lf msgs/min\l',           
        );                                                                  
}                                                                           
 
sub graph_dmarc($$)
{                  
        my ($range,