#! /usr/bin/perl -w

# vim:syntax=perl

use strict;
use lib '/usr/share/perl5';
use Lire::DlfSchema;
use Lire::Program qw( :msg :dlf );
use Lire::Firewall qw/ firewall_number2names /;
use Lire::WELF;

init_dlf_converter( "firewall" );
my $schema      = Lire::DlfSchema::load_schema( "firewall" );
my $dlf_maker   =
  $schema->make_hashref2asciidlf_func( qw/time rule action protocol
                                          rcv_intf from_ip from_port
                                          to_ip to_port snt_intf
                                          length count msg/);

my %wtproto2proto = 
  ( 
   http         => [ "tcp", 80  ],
   https        => [ "tcp", 443 ],
   dns          => [ "udp", 53  ],
   ftp          => [ "tcp", 80  ],
   telnet       => [ "tcp", 23  ],
   smtp         => [ "tcp", 25  ],
   pop3         => [ "tcp", 110 ],
   realaudio    => [ "udp", "Real" ], # FIXME: Does this makes sense
  );

my $lines      = 0;
my $dlflines   = 0;
my $errorlines = 0;
my $parser = new Lire::WELF;
while (<>) {
    lire_chomp();
    $lines++;

    # Skip empty lines
    next if /^\s*$/;

    eval {
        my $welf = $parser->parse( $_ );

        die "not a firewall WELF record: id=", $welf->{id}, "\n"
          unless $welf->{id} eq 'firewall';

        # Skip messages without src or dst.
        return unless $welf->{src} && $welf->{dst};

        my %dlf = ( time    => $welf->{time},
                    action  => "permitted",
                    count   => 1,
                    rule    => $welf->{rule},
                    msg     => $welf->{msg},
                    from_ip   => $welf->{src},
                    from_port => $welf->{src_port},
                    rcv_intf  => $welf->{src_if},
                    to_ip     => $welf->{dst},
                    to_port   => $welf->{dst_port},
                    snt_intf  => $welf->{dst_if},
                  );

        # When there is a proto, it is usually an accepted packet flow
        if ( $welf->{proto}) {
            my $proto = lc $welf->{proto};

            # Guess destination port based on that value
            if ( exists $wtproto2proto{$proto}) {
                $dlf{protocol} = $wtproto2proto{$proto}[0];
                $dlf{to_port}  ||= $wtproto2proto{$proto}[1];
            } elsif ( $proto =~ m,(.*)/(.*),) {
                $dlf{protocol} = $1;
                $dlf{to_port}  ||= $2;
            } else {
                # Try some sane(?) defaults
                $dlf{protocol} = "tcp";
                $dlf{to_port}  ||= $proto;
            }
            # Netscreen ScreenOS 3.0.1. WELF logs features stuff like
            # Jul 18 19:02:49 10.0.0.1 id=firewall time="2002-07-18 19:01:06"
            #  fw=17010041 pri=7 rule=320001 proto=udp/port:1900
            #  src=10.0.0.2 dst=10.0.0.1 sent=0 rcvd=161 duration=0
            #  msg="Action:Deny" module=system level=information type=08668
            # ; this breaks heuristic.  fix it here.
            if ( defined $welf->{msg} ) {
                if ( $welf->{msg} eq 'Action:Deny' ) {
                    $dlf{action} = "denied";
                } elsif ( $welf->{msg} eq 'Action:Permit' ) {
                    $dlf{action} = "permitted";
                }
            }
            # fyi: accepted packets have Action:Permit
            # normal Netscreen logs sent via syslog look different (and more
            # useful), btw.
        } elsif ( exists $welf->{msg} ) {
            # Dropped packet usually don't contains a 
            # proto= field and the msg will contains dropped
            # with the protocol
            $dlf{action}   = "denied"
              if $welf->{msg} =~ /dropped|rejected|blocked|attack/i;
            if ( $welf->{msg} =~ /^(tcp|udp|icmp)/i ) {
                $dlf{protocol} = lc $1;
            }
        }

        # Length should be computed from rcvd and sent, will be 0 on dropped
        # packet :(
        if ( $welf->{rcvd} || $welf->{sent}) {
            $dlf{length} = 0;
            $dlf{length} += $welf->{rcvd} if $welf->{rcvd};
            $dlf{length} += $welf->{sent} if $welf->{sent};
        }

        firewall_number2names( \%dlf );

        my $dlf = $dlf_maker->( \%dlf );

        print join( " ", @$dlf), "\n";
        $dlflines++;
    };
    if ($@) {
        lr_warn( $@ );
        lr_notice( qq{cannot convert line $. "$_" to firewall dlf, skipping} );
        $errorlines++;
    }
}

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

__END__

=pod

=head1 NAME

welf2dlf - convert firewall logs in WebTrends Enhanced Log Format to DLF

=head1 SYNOPSIS

B<welf2dlf> I<file>

=head1 DESCRIPTION

B<welf2dlf> converts firewall logs in the WebTrends Enhanced Log
Format into the firewall DLF.

That format is defined at the following URL:
http://www.netiq.com/partners/technology/welf.asp

This converter also supports the SonicWall extensions.

A list of firewall products that supports that format can be found
at the following URL:
http://www.netiq.com/products/fwr/compatible.asp

=head1 IMPLEMENTATION NOTES

Since the firewall DLF only supports packet filters, not all records
will be mapped to DLF.

Dropped packets messages (those with a field msg="XXX packet dropped")
will be mapped to denied DLF packet. (As well as all msg="TCP
connection dropped" records). Those packets will have a length of 0
(that's a limitation of the format which don't log that information on
dropped packet).

Messages with a proto= field set will be interpretted as "permitted"
"packet". That's not exactly "right" because this message really
represent a "packets flow" and not a single packet.

Other records will be ignored.

You may use the welf_proxy service in the proxy superservice to
extract proxy level information from WELF logs.

=head1 EXAMPLES

To process a log as produced by WebTrends:

 $ welf2dlf < welf.log

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

 $ lr_log2report welf < /var/log/welf.log

=head1 THANKS

Mark D. Nagel, for giving feedback and supplying patches.

=head1 SEE ALSO

Lire::WELF(3) welf_proxy2dlf(1)

=head1 AUTHORS

Francis J. Lacoste <flacoste@logreport.org>

=head1 VERSION

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

=head1 COPYRIGHT

Copyright (C) 2001 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.

=cut

# Local Variables:
# mode: cperl
# End:
