#! /usr/bin/perl -w

# vim:syntax=perl

use strict;
use DB_File;

use lib '/usr/local/share/perl5';
use Lire::Email;
use Lire::Program qw( :msg :dlf );

my $dbfile = shift or die "give dumpfilestem as arg\n";

#
# should be merged with &Email::print_dlf
#
sub Print_dlf {
    my ($c, $dlf_maker) = @_;

    my $dlflines = 0;

    my %dlf = map { $_ => $c->{$_} } 
      qw/time from_user from_domain from_relay_host from_relay_ip
         logrelay queueid msgid size stat xstat/;

    if (! defined $c->{deliveries}) {
        # Message rejected before the sender was known
        my $dlf = $dlf_maker->( \%dlf );
        print join( " ", @$dlf ), "\n";
        $dlflines++;
    } else {
        foreach my $to ( keys %{$c->{deliveries}} ) {

            my $to_infos = $c->{deliveries}{$to};
            foreach my $f ( qw/delay to_user to_domain to_relay_host
                               to_relay_ip stat xstat/ )
            {
                $dlf{$f} = $to_infos->{$f};
            }
            my $dlf = $dlf_maker->( \%dlf );
            print join( " ", @$dlf ), "\n";
            $dlflines++;

            $c->{nrcpts}--;
            delete $c->{deliveries}{$to};
        }
    }
    return $dlflines;
}

my %dlfids; # maps dlfids to nof occurrences in log

# default is , O_CREAT|O_RDWR,  , we only need readaccess
tie %dlfids, "DB_File", "$dbfile", O_RDONLY, 0666, $DB_HASH or
    die "cannot tie to $dbfile\n";

my $schema = eval { Lire::DlfSchema::load_schema( "email" ) };
lr_err( "failed to load email schema: $@" ) if $@;
my $dlf_maker = 
  $schema->make_hashref2asciidlf_func( qw/time logrelay queueid msgid 
                      from_user from_domain from_relay_host from_relay_ip 
                      size delay xdelay
                      to_user to_domain to_relay_host to_relay_ip 
                      stat xstat
                                       /);

my ($dlflines, $errorlines) = (0, 0);
my %msg;

init_dlf_converter( "email" );

while (<>) {
    chomp;
    s/^ //;

    my @a = split / /;

    # [27-Mar:16:07 Fruit] if(foo&1) bit-is-odd;
    unless ($#a & 1) {
        # @a has odd nof elements
        lr_err "'$_' does not look like postfix2dlf_pre generated line: " .
        "spaces don't match\n";
    }

    my %line = @a;

    my $dlfid = $line{'dlfid'} or lr_err "no dlfid in line $_\n";

    $msg{$dlfid} ||= {};

    my $cur = $msg{$dlfid};
    $cur->{'dlfids'}++;    # count nof dlfid's processed

    for my $k (
      'logrelay', 'queueid', 'time', 'from_user', 'from_domain',
      'from_relay_host', 'from_relay_ip', 'size', 'msgid'
    ) {
        $cur->{$k} ||= $line{$k};
    }

    if (defined $line{'to_user'}) {
        my $to = $line{'to_user'} . '@' . $line{'to_domain'};
        for my $k (
          'to_user', 'to_domain', 'to_relay_host', 'to_relay_ip', 'delay',
          'xdelay', 'stat', 'xstat'
        ) {
          $cur->{deliveries}{$to}->{$k} ||= $line{$k};
        }
    }

    if ($cur->{'dlfids'} >= $dlfids{$dlfid}) {
        # last minute fix-up:
        # Mar  3 00:09:36 ohno.example.net postfix/nqmgr[15927]: [ID 197553
        #   mail.info] A87CEAB774: from=<joe@example.org>, status=expired,
        #   returned to sender
        # would yield a size-field '-'
        (!defined $cur->{size} or $cur->{size} !~ /^\d+$/) and
          $cur->{size} = 0;

        # print it ...
        $dlflines += Print_dlf( $cur, $dlf_maker );
        # ... and flush it
        delete $msg{$dlfid};
    }

}

# add errorlines found by lr_postfix2dlf_pre
unless (defined $dlfids{'errorlines'}) {
  lr_err "no errorlines key in $dbfile, was it created by " .
    "lr_postfix2dlf_pre?\n";
}

$errorlines += $dlfids{'errorlines'};

# only lr_postfix2dlf_pre knows about the nof raw loglines
defined $dlfids{'loglines'} or
  lr_err "no loglines key in $dbfile, was it created by " .
    "lr_postfix2dlf_pre?\n";
my $loglines = $dlfids{'loglines'};

untie %dlfids;

end_dlf_converter( $loglines, $dlflines, $errorlines );


__END__

=pod 

=head1 NAME

postfix2dlf_main - convert postfix2dlf_pre output to email dlf

=head1 SYNOPSIS

B<postfix2dlf_main> I<dumpfile>

=head1 DESCRIPTION

B<postfix2dlf_main> expect a preprocessed postfix logfile, as written by
postfix2dlf_pre(1) on STDIN.  It prints a Lire email DLF file on stdout, using
a Berkeley DB in I<dumpfile>, holding a mapping from hostname-queueid to
number-of-lines-with-this-id.

=head1 DEVELOPERS

Datastructures are the same as the ones in sendmail2dlf.

%msg stores all currently being processed information from the log.  In
sendmail2dlf, this hash is indexed by dlfid's:

 $dlfid = $log->{'hostname'} . $log->{'queueid'}

.  In postfix2dlf we use just the queueid, for now.

Scalar values:

 $msg{$dlfid} = {
   logrelay        => ...,
   queueid         => ...,
   time            => ...,
   nrcpts          => ...,
   from_user       => ...,
   from_domain     => ...,
   from_relay_host => ...,
   from_relay_ip   => ...,
   size            => ...,
   msgid           => ...,
 };

Furthermore:

 $del = $msg{$dlfid}->{deliveries}{$to};

$to is an email address as returned by &Lire::Email::sanitize_tos .

 $del->{to_user}   = ....;
 $del->{to_domain} = ....;

Other $del keys with scalar values:

 to_relay_host
 to_relay_ip
 delay
 xdelay
 stat
 xstat

.

The hash reference $msg{$dlfid} is often assigned to `$cur'.

=head1 EXAMPLES

postfix2dlf_main will be rarely used on its own: it is called by
postfix2dlf(1).  Refer to the postfix2dlf manpage for examples and usage
information.

=head1 SEE ALSO

postfix2dlf(1), postfix2dlf_pre(1), sendmail2dlf(1)

=head1 VERSION

$Id: postfix2dlf_main.in,v 1.10 2006/07/23 13:16:34 vanbaal Exp $

=head1 COPYRIGHT

Copyright (C) 2002 Stichting LogReport Foundation LogReport@LogReport.org

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 (see COPYING); if not, check with
http://www.gnu.org/copyleft/gpl.html.

=head1 AUTHOR

Joost van Baal

=cut

# Local Variables:
# mode: cperl
# End:

