Parse::Syslog::Mail - Parse mailer logs from syslog


Parse-Syslog-Mail documentation Contained in the Parse-Syslog-Mail distribution.

Index


Code Index:

NAME

Top

Parse::Syslog::Mail - Parse mailer logs from syslog

VERSION

Top

Version 0.17

SYNOPSIS

Top

    use Parse::Syslog::Mail;

    my $maillog = Parse::Syslog::Mail->new('/var/log/syslog');

    while(my $log = $maillog->next) {
	# do something with $log
        # ...
    }

DESCRIPTION

Top

As its names implies, Parse::Syslog::Mail presents a simple interface to gather mail information from a syslog file. It uses Parse::Syslog for reading the syslog, and offer the same simple interface. Currently supported log formats are: Sendmail, Postfix, Qmail.

METHODS

Top

new()

Creates and returns a new Parse::Syslog::Mail object. A file path or a File::Tail object is expected as first argument. Options can follow as a hash. Most are the same as for Parse::Syslog->new().

Options

  • type - Format of the syslog stream. Can be one of "syslog" (traditional syslog format) or "metalog" (Metalog format).
  • year - Syslog files usually do store the time of the event without year. With this option you can specify the start-year of this log. If not specified, it will be set to the current year.
  • GMT - If this option is set, the time in the syslog will be converted assuming it is GMT time instead of local time.
  • repeat - Parse::Syslog will by default repeat xx times events that are followed by messages like "last message repeated xx times". If you set this option to false, it won't do that.
  • locale - Specifies an additional locale name or the array of locale names for the parsing of log files with national characters.
  • allow_future - If true will allow for timestamps in the future. Otherwise timestamps of one day in the future and more will not be returned (as a safety measure against wrong configurations, bogus year arguments, etc.)

Example

    my $syslog = new Parse::Syslog::Mail '/var/log/syslog', allow_future => 1;

next()

Returns the next line of the syslog as a hashref, or undef when there is no more lines. The hashref contains at least the following keys:

  • host - hostname of the machine.
  • program - name of the program.
  • timestamp - Unix timestamp for the event.
  • id - Local transient mail identifier.
  • text - text description.

Other available keys:

  • from - Email address of the sender.
  • to - Email addresses of the recipients, coma-separated.
  • msgid - Message ID.
  • relay - MTA host used for relaying the mail.
  • status - Status of the transaction.
  • delivery_type - (Qmail only) type of the delivery: "local" or "remote".
  • delivery_id - (Qmail only) id number of the delivery.

Example

    while(my $log = $syslog->next) {
        # do something with $log
    }

DIAGNOSTICS

Top

Can't create new %s object: %s

(F) Occurs in new(). As the message says, we were unable to create a new object of the given class. The rest of the error may give more information.

Expected an argument

(F) You tried to call new() with no argument.

SEE ALSO

Top

Parse::Syslog

Inspecter /var/log/mail.log avec Parse::Syslog::Mail, by Philippe Bruhat, published in GNU/Linux Magazine France #92, March 2007

TODO

Top

Add support for other mailer daemons (Exim, Courier, Qpsmtpd). Send me logs or, even better, patches, if you want support for your favorite mailer daemon.

AUTHOR

Top

Sébastien Aperghis-Tramoni <sebastien (at) aperghis.net>

BUGS

Top

Please report any bugs or feature requests to bug-parse-syslog-mail (at) rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/Bugs.html?Dist=Parse-Syslog-Mail. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

CAVEATS

Top

Most probably the same as Parse::Syslog, see "BUGS" in Parse::Syslog

COPYRIGHT & LICENSE

Top


Parse-Syslog-Mail documentation Contained in the Parse-Syslog-Mail distribution.
package Parse::Syslog::Mail;
use strict;
use warnings;
use Carp;
use Parse::Syslog;

{
    no strict;
    $VERSION = '0.17';
}

sub new {
    my $self = {
        syslog => undef, 
    };
    my $class = ref $_[0] ? ref shift : shift;
    bless $self, $class;

    my $file = shift;
    my %args = @_;

    croak "fatal: Expected an argument" unless defined $file;

    $self->{syslog} = eval { Parse::Syslog->new($file, %args) } or do {
        $@ =~ s/ at .*$//;
        croak "fatal: Can't create new Parse::Syslog object: $@";
    };

    return $self
}

sub next {
    my $self = shift;
    my %mail = ();
    my @fields = qw(host program timestamp text);
    my %delivery2id = ();  # used to map delivery id with msg id (Qmail)

    LINE: {
        my $log = $self->{syslog}->next;
        return undef unless defined $log;
        @mail{@fields} = @$log{@fields};
        my $text = $log->{text};

        # Sendmail & Postfix format parsing ------------------------------------
        if ($log->{program} =~ /^(?:sendmail|sm-mta|postfix)/) {
            redo LINE if $text =~ /^(?:NOQUEUE|STARTTLS|TLS:)/;
            redo LINE if $text =~ /prescan: (?:token too long|too many tokens|null leading token) *$/;
            redo LINE if $text =~ /possible SMTP attack/;

            $text =~ s/^(\w+): *// and my $id = $1;         # gather the MTA transient id
            redo LINE unless $id;

            redo LINE if $text =~ /^\s*(?:[<-]--|[Mm]ilter|SYSERR)/;   # we don't treat these

            $text =~ s/^(\w+): *clone:/clone=$1/;           # handle clone messages
            $text =~ s/stat=/status=/;                      # renaming 'stat' field to 'status'
            $text =~ s/message-id=/msgid=/;                 # renaming 'message-id' field to 'msgid' (Postfix)
            $text =~ s/^\s*([^=]+)\s*$/status=$1/;          # format other status messages

            # format other status messages (2)
            if ($text =~ s/^\s*([^=]+)\s*;\s*/status=$1, /) {
                $text =~ s/(\S+)\s+([\w-]+)=/$1, $2=/g;
            }

            $text =~ s/collect: /collect=/;                 # treat collect messages as field identifiers
            $text =~ s/(\S+),\s+([\w-]+)=/$1\t$2=/g;        # replace fields seperator with tab character

            %mail = (%mail, map {
                    s/,$//;  s/^ +//;  s/ +$//; # cleaning spaces
                    s/^\s+([\w-]+=)/$1/;        # cleaning up field names
                    split /=/, $_, 2            # no more than 2 elements
                 } split /\t/, $text);

            if (exists $mail{ruleset} and exists $mail{arg1}) {
                $mail{ruleset} eq 'check_mail'  and $mail{from}  = $mail{arg1};
                $mail{ruleset} eq 'check_rcpt'  and $mail{to}    = $mail{arg1};
                $mail{ruleset} eq 'check_relay' and $mail{relay} = $mail{arg1};

                unless (exists $mail{status}) {
                    $mail{reject}     and $mail{status} = "reject: $mail{reject}";
                    $mail{quarantine} and $mail{status} = "quarantine: $mail{quarantine}";
                }
            }

            $mail{id} = $id;

        # Courier ESMTP -------------------------------------------------------
        } elsif ($log->{program} =~ /^courier/) {
            redo LINE if $text =~ /^(?:NOQUEUE|STARTTLS|TLS:)/;

            $text =~ s/,status: /,status=/;     # treat status as a field
            $text =~ s/,(\w+)=/\t$1=/g;         # replace fields separator with tab character

            %mail = (%mail, map { split /=/, $_, 2 } split /\t/, $text);

        # Qmail format parsing -------------------------------------------------
        } elsif ($log->{program} =~ /^qmail/) {
            $text =~ s/^(\d+\.\d+) // and $mail{qmail_timestamp} = $1;   # Qmail timestamp
            # use Time::TAI64 to parse that timestamp?
            redo LINE if $text =~ /^(?:status|bounce|warning)/;

            # record 'new' and 'end' events in the status
            $text =~ s/^(new|end) msg (\d+)$// 
                and $mail{status} = "$1 message" and $mail{id} = $2 and last;

            # record 'triple bounce' events in the status
            $text =~ s/^(triple bounce: discarding bounce)\/(\d+)$// 
                and $mail{status} = $1 and $mail{id} = $2 and last;

            # mail id and its size
            $text =~ s/^info msg (\d+): bytes (\d+) from (<[^>]*>) // 
                and $mail{id} = $1 and $mail{size} = $2 and $mail{from} = $3;
            
            # begining of the delivery
            $text =~ s/^(starting delivery (\d+)): msg (\d+) to (local|remote) (.+)$// 
                and $mail{status} = $1 and $mail{id} = $3 and $delivery2id{$2} = $3 
                and $mail{delivery_id} = $2 and $mail{delivery_type} = $4 and $mail{to} = $5;

            $text =~ s/^delivery (\d+): +// 
                and $mail{delivery_id} = $1 and $mail{id} = $delivery2id{$1} || '';
            
            # status of the delivery
            $text =~ s/^(success|deferral|failure): +(\S+)// 
                and $mail{status} = "$1: $2" and $mail{status} =~ tr/_/ /;

            # in case of missing MTA transient id, generate one
            $mail{id} ||= 'psm' . time;

        # Exim format parsing --------------------------------------------------
        } elsif ($log->{program} =~ /^exim/) {
            # format seems to be DATE TIME TID DIR ADDRESS ?
            # where DIR is
            #   => for outgoing email, recipient follows in <>
            #   <= for incoming email
            #   == for informational message
            #   s= for ???
            # 
            # possible errors/warnings:
            #   cancelled by system filter:

        } else {
            redo LINE
        }
    }

    return \%mail
}

1; # End of Parse::Syslog::Mail