#! /usr/bin/perl -w

# vim:syntax=perl

use strict;
use lib '/usr/local/share/perl5';
use Lire::Syslog;
use Lire::DlfSchema;
use Lire::Program qw/ :msg :dlf /;
use Lire::Firewall qw/ firewall_number2names /;

init_dlf_converter('firewall');

my $schema  = eval { Lire::DlfSchema::load_schema( "firewall" ) };
lr_err( "failed to load firewall schema: $@" ) if $@;
my $dlf_maker = $schema->make_hashref2asciidlf_func( qw/time rule action
    protocol from_ip from_port rcv_intf rcv_hwaddr to_ip to_port msg count/ );

my ($lines, $dlflines, $errorlines) = (0, 0, 0);

my $parser = new Lire::Syslog();
while (<>) {
    lire_chomp();
    $lines++;

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

    my $log = eval { $parser->parse( $_ ) };
    if ( $@ ) {
        lr_warn( $@ );
        lr_warn( "failed to parse line $. as a syslog line" );
        $errorlines++;
        next;
    }

    # Skip entries missing a process
    next unless defined $log->{process};

# Apr 24 10:55:35 router 113613: 5d23h: %SEC-6-IPACCESSLOGP: list bigcompany 
#  permitted tcp 10.2.108.1(0) -> 10.72.251.5(0), 80 packets
# Apr 29 03:28:17 192.168.1.1 39724: 2w2d: %SEC-6-IPACCESSLOGP: list 102 
#  denied udp 192.168.1.169(13991) (Ethernet0 0004.ac38.4d3e) -> 
#  192.168.3.250(13991), 1 packet
# Apr 29 03:30:17 192.168.1.1 39725: 2w2d: %SEC-6-IPACCESSLOGP: list 102 
#  denied udp 192.168.1.163(13991) (Ethernet0 ) -> 192.168.3.200(13991), 1 
#  packet
# router 113613: 5d23h: %SEC-6-IPACCESSLOGP: list bigcompany permitted 
#  tcp 10.2.108.1(0) -> 10.72.251.5(0), 80 packets
# 192.168.1.1 39724: 2w2d: %SEC-6-IPACCESSLOGP: list 102 denied udp 
#  192.168.1.169(13991) (Ethernet0 0004.ac38.4d3e) -> 192.168.3.250(13991), 
#  1 packet
# 192.168.1.1 39725: 2w2d: %SEC-6-IPACCESSLOGP: list 102 denied udp 
#  192.168.1.163(13991) (Ethernet0 ) -> 192.168.3.200(13991), 1 packet
# %SEC-6-IPACCESSLOGP: list bigcompany permitted tcp 10.2.108.1(0) -> 
#  10.72.251.5(0), 80 packets
# %SEC-6-IPACCESSLOGP: list 102 denied udp 192.168.1.169(13991) 
#  (Ethernet0 0004.ac38.4d3e) -> 192.168.3.250(13991), 1 packet
# %SEC-6-IPACCESSLOGP: list 102 denied udp 192.168.1.163(13991) 
#  (Ethernet0 ) -> 192.168.3.200(13991), 1 packet

    my %dlf;
    if ( $log->{process} eq '%SEC-6-IPACCESSLOGP' ) {
        ( $dlf{'rule'},
          $dlf{'action'},
          $dlf{'protocol'},
          $dlf{'from_ip'},
          $dlf{'from_port'},
          $dlf{'rcv_intf'},
          $dlf{'rcv_hwaddr'},
          $dlf{'to_ip'},
          $dlf{'to_port'},
          $dlf{'count'}
        ) = $log->{content} =~ m!^
                                list\s      # list
                                ([\w-]+)\s  # access list name
                                (\w+)\s     # action
                                (\w+)\s     # protocol
                                ([\d.]+)    # Source address
                                \((\d+)\)   # Source port
                                (?:\s\(     # Optional
                                  ([^ \)]+)\s? # Receiving interface
                                  ([^\)]*)  # MAC Address
                                  \))?
                                \s->\s      
                                ([\d.]+)    # Dest address
                                \((\d+)\)   # Dest port
                                ,\s
                                (\d+)       # Number of packets
                                \spackets?
                               \s*$!x;
        unless (defined $dlf{'rule'} ) {
            $errorlines++;
            lr_warn( "failed to parse %SEC-6-IPACCESSLOGP record at line $." );
            next;
        }
    } elsif ( $log->{process} eq '%SEC-6-IPACCESSLOGDP' ) {

# %SEC-6-IPACCESSLOGDP: list bigcompany permitted icmp 10.2.108.1 -> 
#  10.68.187.5 (0/0), 2 packets
# router 113614: 5d23h: %SEC-6-IPACCESSLOGDP: list bigcompany permitted 
#  icmp 10.2.108.1 -> 10.68.187.5 (0/0), 2 packets
# Apr 24 10:55:35 router 113614: 5d23h: %SEC-6-IPACCESSLOGDP: list bigcompany 
#  permitted icmp 10.2.108.1 -> 10.68.187.5 (0/0), 2 packets

        ( $dlf{'rule'},
          $dlf{'action'},
          $dlf{'protocol'},
          $dlf{'from_ip'},
          $dlf{'rcv_intf'},
          $dlf{'rcv_hwaddr'},
          $dlf{'to_ip'},
          $dlf{'from_port'},
          $dlf{'to_port'},
          $dlf{'count'}
        ) = $log->{content} =~ m!^
                                list\s      # list
                                ([\w-]+)\s  # access list name
                                (\w+)\s     # action
                                (\w+)\s     # protocol
                                ([\d.]+)    # Source address
                                (?:\s\(     # Optional
                                  ([^ \)]+)\s? # Receiving interface
                                  ([^\)]*)  # MAC Address
                                  \))?
                                \s->\s      
                                ([\d.]+)\s  # Dest address
                                \((\d+)/(\d+)\) # ICMP Type/Code
                                ,\s
                                (\d+)       # Number of packets
                                \spackets?
                               \s*$!x;
                                
        unless (defined $dlf{'rule'} ) {
            $errorlines++;
            lr_warn( "failed to parse %SEC-6-IPACCESSLOGDP record at line $." );
            next;
        }
    } elsif ( substr( $log->{process}, 0, 1) eq '%') {
        # Create msg record for 'unknown' Cisco messages
        # unless it's a PIX record
        next if $log->{process} =~ /^%PIX/;

        $dlf{'msg'} = $log->{process} . ": " . $log->{content};
    } else {
        # Skip non Cisco messages
        next 
    }

    $dlf{'time'} = $log->{timestamp};

    my $dlf = eval { 
        firewall_number2names( \%dlf );
        $dlf_maker->( \%dlf )
    };
    if ( $@ ) {
        lr_warn( $@ );
        lr_warn("cannot convert %dlf to dlf, skipping\n");
        $errorlines++;
        next;
    }

    print join( " ", @$dlf ), "\n";
    $dlflines++;
}

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

exit 0;

__END__

=pod

=head1 NAME

cisco_ios2dlf - convert cisco logs to dlf format

=head1 SYNOPSIS

B<cisco_ios2dlf>

=head1 DESCRIPTION

This script expects syslog-type logs from a CISCO IOS router on stdin.  These
look like e.g.

 Jul  3 00:00:39 router 40108: 4d09h: %SEC-6-IPACCESSLOGP:
  list FR_VA_in permitted udp 192.168.19.1(137) (Serial0/0.2 DLCI 120)
  -> 192.168.19.255(137), 2 packets
 Jul  3 00:02:39 router 40109: 4d09h: %SEC-6-IPACCESSLOGP: list FR_VA_in
  permitted udp 192.168.80.42(138) (Serial0/0.2 DLCI 120) ->
  192.60.60.148(138), 1 packet
 Jul  3 00:02:39 router 40110: 4d09h: %SEC-6-IPACCESSLOGDP: list FR_VA_in
  permitted icmp 192.168.80.82 (Serial0/0.2 DLCI 120) -> 149.1.1.1 (8/0),
  1 packet

or

 Aug 19 04:02:34 gateway.foo.bar 218963: Aug 19 04:02:32.977:
  %LINEPROTO-5-UPDOWN: Line protocol on Interface BRI0:1, changed state
  to down
 Aug 19 04:02:34 gateway.foo.bar 218964: Aug 19 04:02:33.262:
  %ISDN-6-DISCONNECT: Interface BRI0:1  disconnected from 172605440 acme,
  call lasted 42 seconds
 Aug 19 04:02:35 gateway.foo.bar 218965: Aug 19 04:02:33.266:
  %LINK-3-UPDOWN: Interface BRI0:1, changed state to down
 Aug 19 04:02:38 gateway.foo.bar 218966: Aug 19 04:02:36.103:
  %SEC-6-IPACCESSLOGP: list 102 denied tcp 100.198.139.148(4652) ->
  100.193.176.49(80), 1 packet
 Aug 19 04:02:45 gateway.foo.bar 218967: Aug 19 04:02:43.543:
  %ISDN-6-LAYER2DOWN: Layer 2 for Interface BR0, TEI 86 changed to down
 Aug 19 04:02:53 gateway.foo.bar 218968: Aug 19 04:02:51.471:
  %SEC-6-IPACCESSLOGP: list 102 denied tcp 100.74.103.1(2162) ->
  100.193.176.98(80), 1 packet

The outputted dlf files look like:

 994118619 permitted icmp 192.168.80.9 - Serial0/0.2 DLCI_120
  192.168.19.1 - 1
 994118619 permitted udp 192.168.19.1 138 Serial0/0.2 DLCI_120
  192.168.19.255 138 1

=head1 EXAMPLES

To process a log as produced by Cisco IOS:

 $ cisco_ios2dlf < cisco.log

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

 $ lr_log2report cisco_ios < /var/log/cisco.log

=head1 AUTHORS

Francis J. Lacoste based on initial code by Joost Bekkers <joost@jodocus.org>

=head1 VERSION

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

=head1 COPYRIGHT

Copyright (C) 2001 Joost Bekkers <joost@jodocus.org>
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.

=cut

# Local Variables:
# mode: cperl
# End:
