#! @PERL@ -w # vim:syntax=perl use strict; use lib '@LR_PERL5LIBDIR@'; 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( <{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 =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 Gaillard , Elie Dufraiche =head1 COPYRIGHT Copyright (C) 2002 Arnaud Taddei , Arnaud Gaillard , Elie Dufraiche 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: