#! /usr/bin/env perl

# enable warnings
$^W = 1;
use strict;

#######################################################################
# $SiLK: update-sensor-conf 11665 2008-05-28 16:19:28Z mthomas $
#######################################################################

sub Usage
{
    my $name = $0;
    $name =~ s,.*/,,;
    print <<EOF;
Usage: $name sensor.conf > new-sensor.conf

$name converts a sensor.conf file used by previous
versions of SiLK to the format used in SiLK-1.0.

EOF
    exit 0;
}

# Make certain we have input to process
if (@ARGV == 0 && -t STDIN) {
    Usage();
}

# If the first argument begins with a -, assume it is --help
if ($ARGV[0] =~ /^-/) {
    Usage();
}

# Only accept one file
if (@ARGV > 1) {
    Usage();
}


# sensor S01
#     class all


# sensor-probe S01
#     probe-name netflow
#     probe-type netflow
#     priority 8
#     log-flags none all bad missing
#     listen-on-port 9999
#     protocol udp
#     listen-as-host 192.168.1.1
#     listen-on-unix-domain-socket /tmp/sock
#     read-from-file /var/tmp/flow-file
#     accept-from-host 172.16.22.22
#     null-index 0
#     isp-ip 10.10.10.10,10.10.10.11
#     external-index 1,2,3,4
#     internal-index 8,9
#     null-ipblock 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
#     internal-ipblock 128.2.0.0/16
#     external-ipblock remainder



# @lines holds three types of objects: 1. Hash references that refer
# to Probes.  2. Hash references that refer to Sensors. 3. Lines of
# input that do make up a sensor-probe block, including lines for the
# old temporary sensor block.  All input is read into @lines, then the
# output is printed.
my @lines;


# The current sensor.  This is a hash reference that has the following
# fields:
#
#    sensor -- scalar of 1, to distinguish from probes
#
#    name -- the name of the sensor.  This is the same as the name
#    from the old sensor-probe block
#
#    last_seen -- scalar giving line on which the sensor is defined.
#    When multiple probes make up a sensor, we must ensure that we
#    only print the sensor once all its probes have been defined.
#
#    probes -- array reference of probe hash arrays making up the sensor
#
#    lines -- array reference of text to be printed inside the sensor
#    block, but requiring no alternations
#
#    interfaces_seen -- this is 1 if a *-iterface or *-index statement
#    is seen, and 2 if the * is "null".  Used to determine whether a
#    "null-interface" statement should be explicitly added.
#
my $s;


# The current probe.  This is a hash reference that has the following
# fields:
#
#    probe -- scalar of 1, to distinguish from sensors
#
#    name -- the name of the probe.  This begins with the name of the
#    old sensor-probe block.  If a probe-name statement is found, that
#    string is appended to name.
#
#    type -- the type of the sensor.  Must keep this separate since it
#    goes on the new probe statement
#
#    lines -- array reference of text to be printed inside the probe
#    block, but requiring no alternations
#
#    protocol_seen -- this is 1 if "listen-on-port" was specified, and
#    2 if a "protocol" statement was seen.  Used to determine if a
#    protocol statement should be explicilty added.
#
my $p;


# The %sensor has is used to lookup sensors by name.  Used for a
# sensor that is made up of multiple probes.
my %sensors;


# Read from STDIN or the files on the command line
while (<>) {
    chomp;

    # Check for lines that indicate we're trying to (re-)convert a
    # SiLK-1.0 file
    if (/^\s*end\s/ || /^\s*probe\s/) {
        die "$0: Input already appears to be in the SiLK 1.0 format\n";
    }

    # SENSOR BLOCK -- old block to define temporary sensor
    if (/^\s*sensor\s+([-\w]+)/) {
        our $warned;
        unless ($warned) {
            warn "Sensors ('$1') must now be defined in the silk.conf file\n";
        }
        push @lines, "#ERROR!  Sensors must be defined in silk.conf", "#$_";
    }
    elsif (/^\s*class(|es)\s/) {
        push @lines, "#$_";
    }

    # SENSOR-PROBE BLOCK
    elsif (/^\s*sensor-probe\s+([-\w]+)(.*)/) {
        # Start a new probe; must be defined before the sensor
        my $name = $1;
        $p = {
            probe => 1,
            name => $name,
            lines => [],
            protocol_seen => 0,
        };
        push @lines, $p;

        # See if this sensor is already defined
        $s = $sensors{$name};
        if ($s) {
            # Sensor already defined.  Update it.

            # Keep track of last occurrence of sensor name in file
            $s->{last_seen} = scalar(@lines);
            # Update probe list for this sensor
            push @{$s->{probes}}, $p;
        }
        else {
            # Sensor not defined.  Create new.
            $s = {
                sensor => 1,
                name => $name,
                lines => [],
                probes => [$p],
                last_seen => scalar(@lines),
                interfaces_seen => 0,
            };
            # Add sensor to list
            $sensors{$name} = $s;
        }
        push @lines, $s;
    }

    # these four statements go into the new sensor block
    elsif (/^\s*[-\w]+-ipblocks?\s/) {
        push @{$s->{lines}}, $_;
    }
    elsif (/^\s*([-\w]+)-interfaces?\s/) {
        push @{$s->{lines}}, $_;
        if ($1 eq "null") {
            $s->{interfaces_seen} = 2;
        }
        elsif ($s->{interfaces_seen} == 0) {
            $s->{interfaces_seen} = 1;
        }
    }
    elsif (/^(\s*[-\w]+)-(?:index(?:|es))\b(.*)$/) {
        # change "-index" to "-interface"
        push @{$s->{lines}}, "$1-interfaces$2";
        if ($1 eq "null") {
            $s->{interfaces_seen} = 2;
        }
        elsif ($s->{interfaces_seen} == 0) {
            $s->{interfaces_seen} = 1;
        }
    }
    elsif (/^\s*isp-ips?\s/) {
        push @{$s->{lines}}, $_;
    }

    # the remaining statements go into the new probe block
    elsif (/^\s*probe-name\s+([-\w]+)(.*)/) {
        # Append old probe-name to old sensor-probe name
        my $name = $1;
        if ($name ne $p->{name}) {
            $p->{name} .= "_$name";
        }
    }
    elsif (/^\s*probe-type\s+([-\w]+)(.*)/) {
        # Change netflow to netflow-v5
        my $type = $1;
        if ($type eq "netflow") {
            $type = "netflow-v5";
        }
        $p->{type} = $type;
    }
    elsif (/^\s*priority\s/) {
        push @{$p->{lines}}, $_;
    }
    elsif (/^\s*log-flags\s/) {
        push @{$p->{lines}}, $_;
    }
    elsif (/^\s*listen-on-port\s/) {
        push @{$p->{lines}}, $_;
        if ($p->{protocol_seen} == 0) {
            $p->{protocol_seen} = 1;
        }
    }
    elsif (/^\s*protocol\s/) {
        push @{$p->{lines}}, $_;
        $p->{protocol_seen} = 2;
    }
    elsif (/^\s*listen-as-host\s/) {
        push @{$p->{lines}}, $_;
    }
    elsif (/^\s*listen-on-unix-(|domain-)socket\s/) {
        push @{$p->{lines}}, $_;
    }
    elsif (/^\s*read-from-file\s/) {
        push @{$p->{lines}}, $_;
    }
    elsif (/^\s*accept-from-host\s/) {
        push @{$p->{lines}}, $_;
    }

    # ANYTHING ELSE
    else {
        push @lines, $_;
    }
}


# We've read all the input.  Now write the output by removing the data
# from @lines.  If it is text, just print it.  If it is a probe hash
# reference, write a probe block.  If it is a sensor hash reference,
# write a sensor block if the block's last_seen value matches the
# current line count.

my $line_count = 0;
while (defined(my $obj = shift @lines)) {
    if (!ref($obj)) {
        # line is text.  print it
        print $obj, "\n";
    }
    elsif ($obj->{probe}) {
        # print the probe block

        # if the probe type is undefined, set it to netflow-v5
        unless (defined($obj->{type})) {
            $obj->{type} = "netflow-v5";
        }

        print "probe ", $obj->{name}, " ", $obj->{type}, "\n";
        print join("\n", @{$obj->{lines}}), "\n";
        # add an explicit protocol
        if ($obj->{protocol_seen} == 1) {
            print "    protocol udp\n";
        }
        print "end probe\n\n";
    }
    else {
        # we'd better have a sensor
        die "not a sensor\n" unless $obj->{sensor};

        # print it if we've defined all its probes
        if ($obj->{last_seen} == $line_count) {
            # print it now

            # the probes on the sensor must be grouped by their type
            my %probe_list;
            for my $probe (@{$obj->{probes}}) {
                unless (ref($probe_list{$probe->{type}})) {
                    $probe_list{$probe->{type}} = [];
                }
                push @{$probe_list{$probe->{type}}}, $probe->{name};
            }

            print "sensor ", $obj->{name}, "\n";
            for my $type (sort keys %probe_list) {
                print join " ", "    $type-probes", @{$probe_list{$type}};
                print "\n";
            }
            if (@{$obj->{lines}}) {
                print join("\n", @{$obj->{lines}}), "\n";
            }
            # add null-interfaces line if required
            if ($obj->{interfaces_seen} == 1) {
                print "    #WARNING: null-iterface no longer defaults to 0\n";
                print "    null-interface 0\n";
            }
            print "end sensor\n\n";
        }
    }
    ++$line_count;
}


exit;

#######################################################################
# Copyright (C) 2008 by Carnegie Mellon University.
#
# @OPENSOURCE_HEADER_START@
#
# Use of the SILK system and related source code is subject to the terms
# of the following licenses:
#
# GNU Public License (GPL) Rights pursuant to Version 2, June 1991
# Government Purpose License Rights (GPLR) pursuant to DFARS 252.225-7013
#
# NO WARRANTY
#
# ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER
# PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY
# PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN
# "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
# KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT
# LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE,
# MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE
# OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT,
# SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY
# TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF
# WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES.
# LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF
# CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON
# CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE
# DELIVERABLES UNDER THIS LICENSE.
#
# Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie
# Mellon University, its trustees, officers, employees, and agents from
# all claims or demands made against them (and any related losses,
# expenses, or attorney's fees) arising out of, or relating to Licensee's
# and/or its sub licensees' negligent use or willful misuse of or
# negligent conduct or willful misconduct regarding the Software,
# facilities, or other rights or assistance granted by Carnegie Mellon
# University under this License, including, but not limited to, any
# claims of product liability, personal injury, death, damage to
# property, or violation of any laws or regulations.
#
# Carnegie Mellon University Software Engineering Institute authored
# documents are sponsored by the U.S. Department of Defense under
# Contract F19628-00-C-0003. Carnegie Mellon University retains
# copyrights in all material produced under this contract. The U.S.
# Government retains a non-exclusive, royalty-free license to publish or
# reproduce these documents, or allow others to do so, for U.S.
# Government purposes only pursuant to the copyright license under the
# contract clause at 252.227.7013.
#
# @OPENSOURCE_HEADER_END@
#
#######################################################################
