#! /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});

    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;
	  };

	  /server socket closed|bytes|no server for user|client socket IO error|unable to establish LDAP|LDAP Error:/ && do {
	    # We skip these 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
message-store DLF

=head1 SYNOPSIS

B<nmsmmp2dlf> STDIN STDOUT

=head1 DESCRIPTION

This program converts Netscape Messaging Server Mail Multi Plexor log
files generated by the IMAP or POP services to the msgstore DLF.

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

=head1 VERSION

$Id: nmsmmp2dlf.in,v 1.1 2002/08/05 02:44:40 flacoste 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 or write to the Free Software 
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.

=cut

# Local Variables:
# mode: cperl
# End:
