#! /usr/bin/perl -w

# vim:syntax=perl
use strict;
use lib '/usr/local/share/perl5';

use Lire::DlfSchema;
use Lire::Program qw/ :msg :dlf /;
use Time::Local;

use vars qw/ $dlf_maker $dlflines $debug %msg_sessions @atts/;

sub BEGIN {
    @atts = ('time',
             'localserver',
             'client_ip',
             'user',
             'protocol',
             'prot_cmd',
             'session',
             'status'
            );
}


#-----------------------------------------------------------------------
#  Function Print_Server_Messages 
#  A function to dump server messages

sub Print_Server_Messages {
    my($list) = @_;

    lr_debug( <<EOT );
Server Messages
---------------

EOT

    foreach my $e (@$list) {
        lr_debug( $e );
    }

    lr_debug( "" );
}

#------------------------------------------------------------------------
# Function Process_Session_Start
sub Process_Session_Start {
    my($log) = @_;
    my($entry);

    $log->{content} =~ /\/(\w+)Proxy \(sid (.*)\) session start, client IP (.*), server IP (.*)$/;

    $entry->{time}         = $log->{timestamp};
    $entry->{localserver}  = $4;
    $entry->{client_ip}    = $3;
    $entry->{protocol}     = lc $1;
    $entry->{session}      = $2;

    Update_Message_Store_Entry($entry);
}

#------------------------------------------------------------------------
# Function Process_User_Login
sub Process_User_Login {
    my($log) = @_;

    my($entry);

    $log->{content} =~ /\/(\w+)Proxy \(sid (.*)\) .* login$/;

    $entry->{session}  = $2;
    $entry->{prot_cmd} = 'login';

    Update_Message_Store_Entry($entry);
}

#------------------------------------------------------------------------
# Function Process_Redirected
sub Process_Redirected {
    my($log) = @_;

    my($entry);

    $log->{content} =~ /\/(\w+)Proxy \(sid (.*)\) user (.*) redirected to (.*)/;

    $entry->{session}  = $2;
    $entry->{user}     = $3;
    $entry->{prot_cmd} = "login";
    $entry->{status}   = "redirected to $4";
    
    Update_Message_Store_Entry($entry);
}

#------------------------------------------------------------------------
# Function Process_Session_End
sub Process_Session_End {
    my($log) = @_;

    my($entry);

    $log->{content} =~ /\/(\w+)Proxy \(sid (.*)\) session end/;

    $entry = Find_Session($2);

    print_dlf($entry);
    Remove_Session($2);
}


#------------------------------------------------------------------------
# Function Process_Connection_Closed
sub Process_Connection_Closed {
    my($log) = @_;

    my($entry);

    $log->{content} =~ /\/(\w+)Proxy \(sid (.*)\) (.*)/;

    $entry->{session} = $2;
    $entry->{status}  = $3;

    Update_Message_Store_Entry($entry);
}

#------------------------------------------------------------------------
# Function Process_No_LDAP
sub Process_No_LDAP {
    my($log) = @_;

    my($entry);

    $log->{content} =~ /\/(\w+)Proxy \(sid (.*)\) no LDAP matches for search \(uid=(.*)\)/;

    $entry->{session} = $2;
    $entry->{status}  = 'no LDAP matches for search on (uid=pattern)';
    $entry->{user}    = $3;

    Update_Message_Store_Entry($entry);
}


#------------------------------------------------------------------------
# Function Find_Session
sub Find_Session {
    my($session) = @_;

    return $msg_sessions{$session};
}

#------------------------------------------------------------------------
# Function Update_Message_Store_Entry
sub Update_Message_Store_Entry {
    my($entry) = @_;

    my($attribute);
    foreach $attribute (@atts) {
        $msg_sessions{$entry->{session}}{$attribute} = $entry->{$attribute} 
          if defined $entry->{$attribute};
    }
}

#------------------------------------------------------------------------
# Function Remove_Session
sub Remove_Session {
    my($session) = @_;

    delete $msg_sessions{$session};
}

#------------------------------------------------------------------------
# Function print_dlf
sub print_dlf {
    my($entry) = @_;

    #$entry->{status} =~ s/\s+/_/g if (defined $entry->{status});

    #Workaround to avoid broken session    
    if (!defined $entry->{time}) {
        lr_err( "Invalid session discarding dlf line");}

    my $dlf = $dlf_maker->($entry);
    if ($#$dlf < 0) {
        # FIXME: When can this happen?
        lr_err( "*** ERROR in PRINT DLF 0 fields" )
    } else {
        print join( " ", @$dlf ), "\n";
    }
    $dlflines++;
}


#=============================================================================
# Here we start the main of the program
#=============================================================================

my $schema = eval { Lire::DlfSchema::load_schema( "msgstore" ) };
lr_err( "failed to load msgstore schema: $@" ) if $@;
$dlf_maker = $schema->make_hashref2asciidlf_func( @atts );

my $lines       = 0;
$dlflines       = 0;
my $errorlines  = 0;
my @server_msg  = ();
$debug          = 0;

init_dlf_converter( "msgstore" );
my $failed_line = undef;
while ( <> ) {
    chomp;
    $lines++;
  
    # Look for ^M in the log file which fooled the logging system
    if ( /\r$/ ) {
        $failed_line .= $_;
        next;
    } elsif ( defined $failed_line) {
        $_ = $failed_line . $_;
        $failed_line = undef;
    }

    # Let's eliminate the (-8174) case in Netscape logs.
    # This pattern:
    #   (-8174)
    # Happens when there is the line:
    #  ... SSL initialization error: couldn't open certdb /mailserv1fs/netscape/server4/alias/msg-amail1-cert7.db
    # An old Netscape log stupidity! A forgotten \n or a bogus missing 'chomp'
    if (/^\s+\(\-\d+\)$/) {
        push(@server_msg, $_);
        next;
    }

    eval {
        my ( $year, $month, $day, $hour, $min, $sec, $content ) =
          m/(\d\d\d\d)(\d\d)(\d\d) (\d\d)(\d\d)(\d\d)(.*)/
            or die "invalid MMP timestamp: $_\n";

        my $log = {
                   timestamp => timelocal($sec ,$min, $hour, $day, 
                                          $month-1, $year-1900),
                   content   => $content,
                  };

        # A typical session looks like this
        #   PopProxy (sid 0x16dec8) session start, client IP 127.0.0.1, server IP 127.0.0.1
        #   PopProxy (sid 0x16dec8) USER login
        #   PopProxy (sid 0x16dec8) user can redirected to amail2.iorange.ch
        #   PopProxy (sid 0x16dec8) server socket closed
        #   PopProxy (sid 0x16dec8) 6 C->S bytes, 25 S->C bytes in 0 seconds
        #   PopProxy (sid 0x16dec8) session end

        my $line = $_;
      SWITCH:
        for ( $log->{content} ) {
          /session start/ && do {
              Process_Session_Start($log);
              last SWITCH;
          };

          /USER login|LOGIN login/ && do {
              Process_User_Login($log);
              last SWITCH;
          };

          /redirected/ && do {
              Process_Redirected($log);
              last SWITCH;
          };

          /session end/ && do {
              Process_Session_End($log);
              last SWITCH;
          };

          /connection closed\?\?\?|got QUIT|server socket read error 0|session timeout|client socket read error 0/ && do {
              Process_Connection_Closed($log);
              last SWITCH;
          };

          /no LDAP/ && do {
              Process_No_LDAP($log);
              last SWITCH;
          };

          /AUTH|unable to connect|server socket closed|bytes|no server for user|client socket IO error|unable to establish LDAP|LDAP Error:/ && do {
            # We skip those messages
            last SWITCH;
          };

          /Multiplexor started|Multiplexor stopped/ && do {
              push(@server_msg, $line);
              last SWITCH;
          };

          # Unknown message
          die "unknown content: $_\n";
        };
    };
    if ($@) {
        lr_warn( $@ );
        lr_warn( "failed to parse line $. '$_'. Skipping." );
        $errorlines++;
    }
}

Print_Server_Messages(\@server_msg);

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

__END__


=pod

=head1 NAME

nmsmmp2dlf - convert Netscape Messaging Server MMP log files to the Lire
msgstore DLF

=head1 SYNOPSIS

B<nmsmmp2dlf>

=head1 DESCRIPTION

This program reads Netscape Messaging Server Mail Multi Plexor log files
generated by the IMAP or POP services on STDIN, and prints the corresponding
Lire msgstore DLF to STDOUT.

It supports the following fields of the message store schema: time,
localserver, client_ip, user, session, protocol, prot_cmd and status.

=head1 EXAMPLES

To process a log as produced by  Netscape Messaging Server:

 $ nmsmmp2dlf < ns-smmp.log

nmsmmp2dlf will be rarely used on its own, but is more likely
called by lr_log2report:

 $ lr_log2report nmsmmp < /var/log/ns-smmp.log > report

=head1 VERSION

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

=head1 AUTHORS

Arnaud Taddei <Arnaud.Taddei@sun.com>, Arnaud Gaillard
<Arnaud.Gaillard@orange.ch>, Elie Dufraiche <Elie.Dufraiche@sun.com>

=head1 COPYRIGHT

Copyright (C) 2002 Arnaud Taddei <Arnaud.Taddei@sun.com>, Arnaud Gaillard
<Arnaud.Gaillard@orange.ch>, Elie Dufraiche <Elie.Dufraiche@sun.com>

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. 

=cut

# Local Variables:
# mode: cperl
# End:
