#! /usr/bin/perl -w

# vim:syntax=perl

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

# expects 10.27.2.30/1258 or dmz:10.168.50.56
# return 3-entry array
sub grok_addr {
    my $a = shift;
    return $a =~
          m/^(?:
            ([^:]+):    # intf
          )?
          (
            [\d\.]+     # ip
          )
          (?:
            \/(\d+)     # port
          )?$/x;
}

init_dlf_converter('firewall');

my $timestart = time() or lr_crit("can't get time");
my @ltime = localtime($timestart) or lr_crit("can't get time");

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 action
    protocol from_ip from_port rcv_intf to_ip to_port snt_intf rule length
    msg count/);
# beware: PIX logs do not specify `count' for permitted sessions! PIX gives
# no info on how many packets were involved in any connection
# we assume denied sessions involve 1 packet

# list of PIX messages we've found in the wild, but do net yet support
#
my @not_yet_supported_pix_tags = (
    # 10.16.2.18 Retrieved 10.124.173.25:/bar.exe
    # or: 10.16.200.27 Stored 10.109.36.19:foo/bar.htm
    303002,

    # %PIX-5-304001: 10.16.1.29 Accessed URL 10.39.167.71:/style.css
    # or: '10.155.253.1 Accessed JAVA URL 10.185.106.10:/foo.class'
    304001,
);

#
# list of pix tags we've found in the wild, and which we'll quite likely never
# support in the firewall dlf, since the firewall dlf format can't use the
# information in these messages
#
my @uninformative_pix_tags = (

    # Deny inbound (No xlate) tcp src outside:24.65.99.246/2018 dst
    # outside:194.185.106.181/80
    #
    # Deny inbound (No xlate) chars
    #
    # no guaranteed format in `chars': therefore, skip it.
    106011,

    # Connection buildup data is ignored (All neccesary info is
    # in the Teardown messages) : skip %PIX-6-302013, %PIX-6-302015,·
    # and %PIX-6-305009, %PIX-6-609001

    # %PIX-3-201002: Too many connections on static|xlate gaddr! econns nconns
    201002,

    # %PIX-2-201003: Embryonic limit exceeded neconns/elimit for faddr/fport 
    #  (gaddr) laddr/lport on interface int_name
    201003,

    # not in PIX 6.2
    # %PIX-6-302001: Built inbound TCP connection 178361 for
    # faddr 10.27.1.68/1662 gaddr 10.27.2.16/524 laddr 10.27.2.16/524
    302001,

    # %PIX-6-302005: Built UDP connection for faddr 10.1.1.16/711 gaddr
    # 10.27.2.30/1259 laddr 10.27.2.30/1259
    302005,

    # %PIX-3-305006: Dst IP is network/broadcast IP, translation creation failed for udp src outside:10.0.0.1/137 dst dmz:10.0.0.255/137
    305006,

    # %PIX-6-302010: 50 in use, 192 most used
    # Summary statistics not supported
    302010,

    # %PIX-6-302013: Built inbound TCP connection 240104 for·
    # outside:10.240.192.35/14394 (10.240.192.35/14394) to·
    # dmz:10.168.0.10/80 (10.185.106.10/80)
    # or: %PIX-6-302013: Built outbound TCP connection 236126 for ...
    302013,

    # %PIX-6-302015: Built inbound UDP connection 240103 for·
    # outside:10.215.170.158/49453 (10.215.170.158/49453) to·
    # dmz:10.168.0.2/53 (194.185.106.2/53)
    302015,

    # 10.192.143.51/21652 laddr 10.27.2.174/4107
    # (This message is not included in PIX 6.2)
    305001,

    # Portmap info isn't supported yet
    # %PIX-6-305004: Teardown portmap translation for global
    # 10.192.143.51/21652 local 172.27.2.174/4107
    # (this message is not included in PIX 6.2)
    305004,

    # %PIX-6-305002: Translation built for gaddr 10.166.225.158 to laddr
    # 10.168.50.4
    305002,

    # %PIX-6-305003: Teardown translation for global 10.166.225.62 local
    # 10.168.100.77
    305003,

    # %PIX-6-305009: Built dynamic translation from inside:10.16.254.28 to 
    # outside:10.185.106.198
    305009,

    # 'Teardown dynamic translation from inside:10.16.254.25 to 
    # outside:10.185.106.141 duration 0:05:01'
    # 'Teardown static translation from inside ... dmz:10.16.1.250'
    # Teardown {dynamic|static} translation from interface:real-address
    # to interface:mapped-address duration hh:mm:ss
    305010,

    # other masquerading state messages
    305011,
    305012,

    # %PIX-6-314001: Pre-allocate RTSP UDP backconnection for faddr
    # 10.188.7.147/28098 to laddr 10.168.100.71/6970
    314001,

    # %PIX-6-602301: sa created, (sa) sa_dest= 10.192.143.50, sa_prot= 50,
    # sa_spi= 0xdf15aa6(233921190), sa_trans= esp-3des esp-md5-hmac ,
    # sa_conn_id= 3
    602301,

    # %PIX-6-602302: deleting SA, (sa) sa_dest= 10.192.143.50, sa_prot= 50,
    # sa_spi= 0x43043d2b(1124351275), sa_trans= esp-3des esp-md5-hmac , 
    # sa_conn_id= 4
    602302,

    # %PIX-6-609001: Built local-host inside:10.16.1.250
    609001,

    # 'Teardown local-host inside:172.16.254.25 duration 0:05:33'
    609002,
);

my $syslog_parser = new Lire::Syslog;
my ($lines, $dlflines, $errorlines) = (0, 0, 0);

LINE:
while(<>) {
    lire_chomp();
    $lines++;

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

    # Parse syslog encapsulation
    my $rec = eval { $syslog_parser->parse( $_ ) };
    if($@) {
        lr_warn($@);
        $errorlines++;
        next;
    }

    unless($rec->{process} =~ /^\%PIX-([0-9])-([0-9]+)$/) {
        lr_debug("skipped non PIX syslog line '$_'");

        # FIXME: I don't think we should mark lines that we know for sure aren't PIX lines
        # as error. Better to just skip them (FJL)
        $errorlines++;
        next;
    }
    # pix_msg_nr determines pix_level, as well as what to
    # expect on the rest of the line
    my $pix_level = $1;   # severity level
    my $pix_msg_nr = $2;  # message number

    my @w = split / +/, $rec->{content};
    my @ip;
    my %dlf = ( time => $rec->{timestamp} );


    my $pix_body = $rec->{content};

    my ($hours, $minutes, $seconds);
    my ($from_addr, $to_addr);

    # i'm not sure wether it would be sane to use qr// here to build a regexp
    # from @not_yet_supported_pix_tags .  would this really be an optimisation?
    # note that the array is just a list of integers here.
    ## next if grep { $pix_msg_nr == $_ } @not_yet_supported_pix_tags;
    next if grep { $pix_msg_nr == $_ }
      (@not_yet_supported_pix_tags, @uninformative_pix_tags);

    if ($pix_msg_nr == 302002) {
        # not in PIX 6.2! (but does occur in e.g. PIX 5.0, see
        #  http://www.cisco.com/univercd/cc/td/doc/product/iaabu/pix/pix_v50/pix55em/pixemsgs.htm#xtocid6
        # )

        # %PIX-6-302002: Teardown TCP connection 178357 faddr 10.1.1.12/445
        # gaddr 10.27.2.30/1258 laddr 10.27.2.30/1258 duration 0:01:09
        # bytes 6076 (TCP FINs)

        # The faddr IP address is the foreign host, the gaddr IP address is a
        # global address on the lower security level interface, and the laddr
        # IP address is the local IP address "behind" the PIX Firewall on the
        # higher security level interface.·

        # the format of this log message has changed in various PIX versions:

        # PIX 4.3
        # %PIX-6-302002: Teardown TCP connection for faddr IP_addr/port
        # gaddr IP_addr/port laddr IP_addr/port

        # PIX 4.4
        # %PIX-6-302002: Teardown TCP connection for faddr IP_addr/port
        # gaddr IP_addr/port laddr IP_addr/port duration time bytes num (text)

        # PIX 5.0
        # %PIX-6-302002: Teardown TCP connection id for faddr IP_addr/port
        # gaddr IP_addr/port laddr IP_addr/port {username)

        # PIX 5.2 and 6.0
        # %PIX-6-302002: Teardown TCP connection id for faddr IP_addr/port
        # gaddr IP_addr/port laddr IP_addr/port (username) duration time
        # bytes num (chars).

        # we support what we've found in the wild.  unfortunately, the pix
        # version of this log is unknown.

        $dlf{'action'} = 'permitted';

        (
          $dlf{'protocol'}, $dlf{'from_ip'}, $dlf{'from_port'},
          $dlf{'to_ip'}, $dlf{'to_port'}, $hours, $minutes, $seconds,
          $dlf{'length'}
        ) = $pix_body =~ m/^
            Teardown\ (\w+)\ connection\ \d+\ # protocol
            faddr\ ([\d\.]+)\/                # from_ip
            (\d+)\                            # from_port
            gaddr\ ([\d\.]+)\/                # to_ip
            (\d+)\                            # to_port
            laddr\ [\d\.]+\/\d+\              # thrown away for now
            duration\ (\d+):(\d+):(\d+)\      # hours, minutes, seconds
            bytes\ (\d+)\                     # length
            \([a-zA-Z\ 0-9-]+\)
          $/x;

        unless (defined $dlf{'length'}) {
            lr_warn( "Cannot parse $pix_msg_nr type PIX record '$_'\n" );
            $errorlines++;
            next LINE;
        }

        # FIXME: That's a bug, that's the duration of the connection, not
        # the timestamp at which the connection started.
        #$dlf{'time'} -= $hours*3600 + $minutes*60 + $seconds;

    } elsif ($pix_msg_nr == 302006) {
        # %PIX-6-302006: Teardown UDP connection for faddr 10.1.1.16/711
        # gaddr 10.27.2.30/1259 laddr 10.27.2.30/1259

        # %PIX-6-302006: Teardown UDP connection for faddr 209.250.128.6/1 gaddr 199.166.225.115/2198 laddr 192.168.100.104/2198

        $dlf{'action'} = 'permitted';

        (
          $dlf{'protocol'}, $dlf{'from_ip'}, $dlf{'from_port'},
          $dlf{'to_ip'}, $dlf{'to_port'}
        ) = $pix_body =~ m/^
            Teardown\ (\w+)\ connection\ for\ # protocol
            faddr\ ([\d\.]+)\/                # from_ip
            (\d+)\                            # from_port
            gaddr\ ([\d\.]+)\/                # to_ip
            (\d+)\                            # to_port
            laddr\ [\d\.]+\/\d+               # thrown away for now
          $/x;

        unless (defined $dlf{'to_port'}) {
            lr_warn( "Cannot parse $pix_msg_nr type PIX record '$_'\n" );
            $errorlines++;
            next LINE;
        }

    } elsif (
      # %PIX-6-302014: Teardown TCP connection 236174 for 
      # outside:10.210.91.83/80 to inside:172.16.1.29/1738 duration 0:00:01
      # bytes 2362 TCP FINs
      #
      # ... bytes 17024 TCP Reset-O
      $pix_msg_nr == 302014 or

      # %PIX-6-302016: Teardown UDP connection 243322 for 
      # outside:10.138.3.20/53 to inside:10.16.1.29/53 duration 0:00:01 
      # bytes 271
      $pix_msg_nr == 302016
    ) {
        $dlf{'action'} = 'permitted';
        $dlf{'protocol'} = lc($w[1]);

        ($dlf{'rcv_intf'}, $dlf{'from_ip'}, $dlf{'from_port'}) = 
          grok_addr($w[5]);
        ($dlf{'snt_intf'}, $dlf{'to_ip'}, $dlf{'to_port'}) =
          grok_addr($w[7]);

        $dlf{'length'} = $w[11] if @w > 11;  # make sure this is numeric
        if(@w > 8 && $w[8] eq 'duration') {
            @ip = split(':', $w[9]);
            $dlf{'time'} -= $ip[0]*3600 + $ip[1]*60 + $ip[2];
        }

    } elsif ( $pix_msg_nr eq '106010' ) {
        # In PIX 5.3:
        # %PIX-3-106010: Deny inbound icmp src outside: IP_addr dst inside: 
        # IP_addr (type dec, code dec)
        #
        # In PIX 6.2:
        #
        # %PIX-3-106010: Deny inbound udp src outside:64.217.216.227/137 
        # dst dmz:207.20.85.64/137
        # %PIX-3-106010: Deny inbound tcp src outside:64.217.216.227/4932 
        # dst dmz:207.20.85.64/25
        #
        # Acerb remarks removed.
        if ( $pix_body =~ /Deny inbound icmp/ ) {
            (
             $dlf{'protocol'}, $dlf{'rcv_intf'}, $dlf{'from_ip'},
             $dlf{'snt_inft'}, $dlf{'to_ip'},
             $dlf{'from_port'}, $dlf{'to_port'},
            ) = $pix_body =~ m/^
                Deny\ inbound\ (icmp)\ src\ (outside):\
                ([\d.]+)\                             # from_ip
                dst (inside):\
                ([\d\.]+)\                            # to_ip
                \(type (\d+), code (\d+)\)\s*
            $/x
        } elsif ( $pix_body =~ /Deny inbound (\w+)/ ) {
            $dlf{'protocol'} = $1;

            ($dlf{'rcv_intf'}, $dlf{'from_ip'}, $dlf{'from_port'}) =
              grok_addr($w[4]);
            ($dlf{'snt_intf'}, $dlf{'to_ip'}, $dlf{'to_port'}) =
              grok_addr($w[6]);
        } # else is handled by error checking below

        $dlf{'action'} = 'denied';

        unless (defined $dlf{'to_port'}) {
            lr_warn( "Cannot parse $pix_msg_nr type PIX record '$_'\n" );
            $errorlines++;
            next LINE;
        }

    } elsif (
      # %PIX-2-106006: Deny inbound UDP from 10.71.190.39/137 to 
      # 10.185.106.167/137 on interface outside
      $pix_msg_nr eq '106006' or

      # %PIX-2-106007: Deny inbound UDP from 10.184.29.230/28824 to 
      # 10.185.106.133/53 due to DNS Query
      $pix_msg_nr eq '106007' or

      # %PIX-3-106014: Deny inbound icmp src outside:208.184.29.230 dst 
      # inside:194.185.106.133 (type 8, code 0)
      $pix_msg_nr eq '106014'
    ) {
        $dlf{'action'} = 'denied';
        $dlf{'protocol'} = $w[2];

        ($dlf{'rcv_intf'}, $dlf{'from_ip'}, $dlf{'from_port'}) =
          grok_addr($w[4]);
        ($dlf{'snt_intf'}, $dlf{'to_ip'}, $dlf{'to_port'}) =
          grok_addr($w[6]);

        if(@w > 10 && $w[2] eq 'icmp') {
            #   12  13  14  15
            # (type 8, code 0)

            # type field and code field in the ICMP message, see RFC 792 or
            # Stevens TCP/IP Illustrated Vol. 1, 6.2

            # FIXME : do we really wanna do this evil overloading of ports
            # here?  perhaps the humanreadable tags, as supplied by
            # num2icmp_type() (e.g. 3 gets dest-unreach) could better get
            # stored in the dlf's `msg' field.
            #
            # firewall_number2names will call num2icmp_type to convert 
            # `from_port' to a human readable description.  to_port is not yet
            # handled in the icmp case.

            ($dlf{'from_port'} = $w[8]) =~ tr/0-9//cd;
            ($dlf{'to_port'} = $w[10]) =~ tr/0-9//cd;
        }
    } elsif ($pix_msg_nr == 106023) {
        # PIX 6.2:
        # %PIX-4-106023: Deny protocol src
        # [inbound-interface]:[src_address/src_port] dst
        # outbound-interface:dst_address/dst_port [type {type}, code {code}]
        # by access_group access-list-name
        #
        # Deny udp src outside:10.250.128.8/53 dst inside:10.166.225.120/1030
        # by access-group "10"
        #
        # %PIX-4-106023: Deny icmp src dmz:10.168.50.56 dst
        # outside:10.250.128.8 (type 3, code 3) by access-group "acl_dmz"

        $dlf{'action'} = 'denied';

        ($dlf{'protocol'}, $from_addr, $to_addr, $dlf{'rule'}) =
          $pix_body =~ m/^
            Deny\ (\w+)\                     # protocol
            src\ ([-\w\.:\/]+)\               # from_addr
            dst\ ([-\w\.:\/]+)\               # to_addr
            (?:\(type\ \d+,\ code\ \d+\)\ )? # optional (ignored)
            by\ access-group\ "([\w-]+)"     # rule
          \s*$/x;

        ($dlf{'rcv_intf'}, $dlf{'from_ip'}, $dlf{'from_port'}) = 
          grok_addr($from_addr);
        ($dlf{'snt_intf'}, $dlf{'to_ip'}, $dlf{'to_port'}) =
          grok_addr($to_addr);

        unless (defined $dlf{'rule'}) {
            lr_warn( "Cannot parse $pix_msg_nr type PIX record '$_'\n" );
            $errorlines++;
            next LINE;
        }

    } elsif ($pix_msg_nr > 399999 and $pix_msg_nr < 400052) {

        # Messages 400000 through 400051: Cisco Secure Intrusion Detection
        # System signature messages.

        # beware! these two log lines:
        # May 07 09:47:04 fw %PIX-4-400014: IDS:2004 ICMP echo request from
        #    10.123.77.194 to 10.185.106.133 on interface outside
        # May 07 09:47:04 fw %PIX-3-106014: Deny inbound icmp src
        #    outside:10.123.77.194 dst inside:10.185.106.133 (type 8, code 0)
        # represent one `dlf-event'
        #
        # %PIX-4-400037: IDS:6053    leads to
        # %PIX-6-302016: Teardown UDP connection
        #
        # %PIX-4-400011: IDS:2001 ICMP unreachable   seem not to generate any
        # followup.  o well, just skip all these for now, and get data from
        # %PIX-3-106014, %PIX-6-302016 and friends

        next LINE;

        # 4000nn: IDS:sig_num sig_msg from IP_addr to IP_addr on
        # interface int_name

        # 'IDS:2000 ICMP echo reply from 10.168.0.9 to 10.168.10.197 on
        # interface dmz'
        # '400010',

        # %PIX-4-400011: IDS:2001 ICMP unreachable from 10.17.247.10 to
        # 10.185.106.224 on interface outside
        # '400011',

        # 'IDS:2004 ICMP echo request from 10.207.130.72 to 10.185.106.133 on
        # interface outside'
        # '400014',

        # 'IDS:2005 ICMP time exceeded from 10.35.213.41 to 10.185.106.2 on
        # interface outside'
        # '400015',

        # IDS:2151 Large ICMP packet from 10.99.129.5 to 10.185.106.2 on
        # interface outside
        # '400024',

        # IDS:6053 DNS all records request from 10.215.170.158 to 10.168.0.2 on
        # interface outside
        # '400037',


        # XXX not reached XXX

        $dlf{'action'} = 'denied';

        ($dlf{'msg'}, $dlf{'from_ip'}, $dlf{'to_ip'}, $dlf{'rcv_intf'}) = 
          $pix_body =~ m/^
            IDS:\d+\                       #
            ([-+\(\)\ a-zA-Z]+?)\          # sig_msg [1]
            from\                          #
            ([\d\.]+)\                     # from_ip
            to\                            #
            ([\d\.]+)\                     # to_ip
            on\ interface\                 #
            ([-\w]+)                       # rcv_intf
          \s*$/x;

        # [1] see
        # http://www.cisco.com/univercd/cc/td/doc/product/iaabu/pix/pix_62/syslog/pixemsgs.htm#18407
        # for a list of sig_msg values

        if ($dlf{'msg'} =~ /icmp/i) {
            $dlf{'protocol'} = 'icmp';
        } elsif ($dlf{'msg'} =~ /udp/i) {
            $dlf{'protocol'} = 'udp';
        } elsif ($dlf{'msg'} =~ /tcp/i) {
            $dlf{'protocol'} = 'tcp';
        }

        unless (defined $dlf{'rcv_intf'}) {
            lr_warn( "Cannot parse IDS type PIX record '$_'\n" );
            $errorlines++;
            next LINE;
        }

        # XXX end of not reached code XXX

    } elsif ($pix_msg_nr == 106001) {
        # Inbound TCP connection denied from 10.216.176.202/49239 to 
        # 10.185.106.2/25 flags SYN on interface outside

        # Inbound TCP connection denied from IP_addr/port to
        # IP_addr/port flags TCP_flags on interface int_name

        (
          $dlf{'protocol'}, $dlf{'action'}, $dlf{'from_ip'},
          $dlf{'from_port'}, $dlf{'to_ip'}, $dlf{'to_port'}, $dlf{'rcv_intf'}
        ) = $pix_body =~ m/^
            Inbound\ (\w+)\ connection\ # protocol
            (\w+)\ from\                # action
            ([\d\.]+)\/                 # from_ip
            (\d+)\ to\                  # from_port
            ([\d\.]+)\/                 # to_ip
            (\d+)\ flags\               # to_port
            [A-Z\ ]+?on\ interface\     # TCP_flags, possibly space separated
            ([-\w]+)                    # rcv_intf
          \s*$/x;

        unless (defined $dlf{'rcv_intf'}) {
            lr_warn( "Cannot parse $pix_msg_nr type PIX record '$_'\n" );
            $errorlines++;
            next LINE;
        }

    } elsif ($pix_msg_nr == 106002) {
        # tcp connection denied by outbound list 10 src 10.16.1.144 1125 dest
        # 10.188.24.156 80

        # protocol Connection denied by outbound list list_ID src laddr dest
        # faddr

        $dlf{'action'} = 'denied';
        $dlf{'protocol'} = $w[0];
        $dlf{'from_ip'} = $w[8];
        $dlf{'from_port'} = $w[9];
        $dlf{'to_ip'} = $w[11];
        $dlf{'to_port'} = $w[12];

    } elsif ($pix_msg_nr == 106015) {
        # Deny TCP (no connection) from 141.202.248.185/80 to 
        # 194.185.106.133/1181 flags FIN PSH ACK  on interface outside

        # Deny TCP (no connection) from IP_addr/port to IP_addr/port flags
        # flags on interface int_name.

        $dlf{'action'} = 'denied';

        (
          $dlf{'protocol'}, $dlf{'from_ip'}, $dlf{'from_port'},
          $dlf{'to_ip'}, $dlf{'to_port'}, $dlf{'rcv_intf'}
        ) = $pix_body =~ m/^
            Deny\ (\w+)\ \(no\ connection\)\ # protocol
            from\ ([\d\.]+)\/                # from_ip
            (\d+)\                           # from_port
            to\ ([\d\.]+)\/                  # to_ip
            (\d+)\                           # to_port
            flags\ [A-Z\ ]+?                 # TCP_flags, possibly spaces
            on\ interface\ ([-\w]+)          # rcv_intf
          \s*$/x;

        unless (defined $dlf{'rcv_intf'}) {
            lr_warn( "Cannot parse $pix_msg_nr type PIX record '$_'\n" );
            $errorlines++;
            next LINE;
        }
    } elsif ($pix_msg_nr == 106021) {
        # Deny udp reverse path check from 10.168.0.1 to 10.185.106.133 on
        # interface outside

        # Deny protocol reverse path check from src_addr to dest_addr on
        # interface int_name

        $dlf{'action'} = 'denied';
        $dlf{'msg'} = 'reverse path check';

        (
          $dlf{'protocol'}, $dlf{'from_ip'}, $dlf{'to_ip'}, $dlf{'rcv_intf'}
        ) = $pix_body =~ m/^
            Deny\ (\w+)\ reverse\ path\ check\ # protocol
            from\ ([\d\.]+)\                   # from_ip
            to\ ([\d\.]+)\                     # to_ip
            on\ interface\ ([-\w]+)            # rcv_intf
          \s*$/x;

        unless (defined $dlf{'rcv_intf'}) {
            lr_warn( "Cannot parse $pix_msg_nr type PIX record '$_'\n" );
            $errorlines++;
            next LINE;
        }
    } else {
        lr_warn( "skipped unknown PIX record: $_\n" );
        lr_warn( "please mail development\@logreport.org if you'd like " .
           "PIX messages of type $pix_msg_nr to be supported in future " .
           "releases.  of course, supplying a patch will drastically " .
           "speed up development!" );
        $errorlines++;
        next;
    }

    # assumption
    $dlf{'action'} eq 'denied' and $dlf{'count'} = 1;

    my $dlf;
    eval {
        firewall_number2names(\%dlf);
        $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

pix2dlf - convert PIX logs to the firewall DLF format

=head1 SYNOPSIS

B<pix2dlf>

=head1 DESCRIPTION

This script expects syslog-type logs from a Cisco PIX firewall on stdin.
Messages with severity level informational (6) and up should be logged.  These
look like e.g.:

 Jan 15 12:58:37 pix1 %PIX-4-106543: Deny tcp src outside:1.2.3.4/1234 dst
  inside:2.3.4.5/80 by access-group "foo"
 Jan 16 10:37:09 pix1 %PIX-4-106543: Deny udp src outside:3.4.5.6/137 dst
  inside:4.5.6.7/137 by access-group "foo"
 Jan 17 08:43:46 pix1 %PIX-4-106543: Deny icmp src outside:5.6.7.8 dst
  inside:6.7.8.9 (type 8, code 0) by access-group "foo"
 Jan 24 00:07:39 pix1 %PIX-6-302000: Teardown TCP connection 178359 faddr
  7.8.9.10/102 gaddr 8.9.10.11/21652 laddr 9.10.11.12/4107 duration
  0:00:01 bytes 755 (TCP FINs)
 Jan 24 00:07:45 pix1 %PIX-6-302000: Teardown UDP connection for faddr
  10.11.12.13/711 gaddr 11.12.13.14/1259 laddr 12.13.14.15/1259

That is

 syslog_time_stamp log_host %PIX-Level-Message_number: Message_text

See also
http://www.cisco.com/univercd/cc/td/doc/product/iaabu/pix/pix_62/
syslog/pixemint.htm#xtocid11 .

It will output DLF records in the Lire firewall DLF format on STDOUT.

For now, only messages

 %PIX-2-106001
 %PIX-2-106002
 %PIX-2-106006
 %PIX-2-106007
 %PIX-3-106010
 %PIX-3-106014
 %PIX-6-106015
 %PIX-1-106021
 %PIX-4-106023
 %PIX-6-302002
 %PIX-6-302006
 %PIX-6-302014
 %PIX-6-302016

are used.  Note that severity level 1 is `alert', 6 is ` informational'. (0 is
`emergency', 7 is `debugging'.)

=head1 EXAMPLES

To process a log as produced by a Cisco PIX:

 $ pix2dlf < pix.log

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

 $ lr_log2report pix < /var/log/pix.log

=head1 BUGS

This script hasn't yet been tested by a very wide range of log files, and
therefore is not mature yet.

Studying the Cisco documentation for any changes in the log file format, e.g.
between PIX Firewall Version 4.0 and 6.2, has I<not> been done yet.

We probably do not support any of the PIX products really fully.  We've found
documentation for log files for PIX version 4.3, 4.4, 5.0, 5.1, 5.2, 5.3, 6.0,
6.1 and 6.2, but didn't implement all peculiarities found in these docs yet.
This script strives to support PIX 6.2 in most common cases.

When hacking on this script, beware that log syntax has changed during PIX
development.  Furthermore, note that some rudimentary state is represented in
PIX logs.  This state is not used yet in this script.

=head1 SEE ALSO

"Cisco PIX Firewall System Log Messages"

http://www.cisco.com/univercd/cc/td/doc/product/iaabu/pix/pix_62/
syslog/pixemsgs.htm

=head1 VERSION

$Id: pix2dlf.in,v 1.26 2009/03/15 08:10:55 vanbaal Exp $

=head1 COPYRIGHT

Copyright (C) 2002 Stichting LogReport Foundation <logreport@logreport.org>

This file is part of Lire.

Lire 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.

=head1 THANKS

Roberto dal Zilio and Ketil Adolfsen, for supplying PIX logs for debugging.
Anthony (acquant) for fixing bugs.

=head1 AUTHOR

Initial version by Wessel Dankers <wsl@logreport.org>, based upon Lire's
cisco_acl2dlf script.  Lots of later changes by Joost van Baal
<joostvb@logreport.org>.

=cut

# Local Variables:
# mode: cperl
# End:

