#!/usr/bin/perl -w
#
########################################################################
#
# File: snort_opts.pl
#
# Purpose: To parse snort rules and display a listing of snort fields
#          along with how many snort rules in which each field is
#          found.
#
########################################################################
#

use Getopt::Long;
use strict;

my $sig_file = '';
my $help = 0;

die "[*] Use --help for usage information.\n" unless (GetOptions(
    'file=s'    => \$sig_file,
    'help'      => \$help
));
&usage() if $help;

my %options = (
    'flow'         => 0,
    'flowbits'     => 0,
    'msg'          => 0,
    'logto'        => 0,
    'ttl'          => 0,
    'tos'          => 0,
    'id'           => 0,
    'ipopts'       => 0,
    'fragbits'     => 0,
    'dsize'        => 0,
    'flags'        => 0,
    'seq'          => 0,
    'ack'          => 0,
    'itype'        => 0,
    'icode'        => 0,
    'icmp_id'      => 0,
    'icmp_seq'     => 0,
    'content'      => 0,
    'uricontent'   => 0,
    'content-list' => 0,
    'offset'       => 0,
    'depth'        => 0,
    'nocase'       => 0,
    'file_data'    => 0,
    'rawbytes'     => 0,
    'session'      => 0,
    'rpc'          => 0,
    'resp'         => 0,
    'react'        => 0,
    'reference'    => 0,
    'sid'          => 0,
    'rev'          => 0,
    'classtype'    => 0,
    'priority'     => 0,
    'tag'          => 0,
    'ip_proto'     => 0,
    'sameip'       => 0,
    'asn1'         => 0,
    'stateless'    => 0,
    'regex'        => 0,
    'window'       => 0,
    'isdataat'     => 0,
    'distance'     => 0,
    'within'       => 0,
    'byte_jump'    => 0,
    'byte_test'    => 0,
    'byte_extract' => 0,
    'pcre'         => 0,
    'ftpbounce'    => 0,
    'base64_data'  => 0,
    'base64_decode' => 0,
    'http_header'  => 0,
    'http_cookie'  => 0,
    'http_uri'     => 0,
    'http_raw_uri' => 0,
    'urilen'       => 0,
    'http_method'  => 0,
    'http_stat_code' => 0,
    'http_stat_msg' => 0,
    'http_client_body' => 0,
    'fast_pattern' => 0,
    'metadata'     => 0,
    'threshold'    => 0,
    'detection_filter' => 0,
);

my %unrecognized = ();
my $total_rules = 0;
my $dir = 'deps/snort_rules';

my @rfiles = ();
if ($sig_file) {
    push @rfiles, $sig_file;
} else {
    opendir D, $dir or die "[*] Could not open $dir: $!";
    @rfiles = readdir D;
    closedir D;
}

print "[+] Calculating snort rule keyword percentages:\n";
for my $rfile (@rfiles) {

    my @lines = ();

    if ($sig_file) {
        open R, "< $sig_file" or die $!;
        @lines = <R>;
        close R;
    } else {
        next unless $rfile =~ /\.rules/;
        open R, "< $dir/$rfile" or die $!;
        @lines = <R>;
        close R;
    }

    for my $line (@lines) {
        chomp $line;
        next unless $line =~ /\S/;
        next if $line =~ /^#/;
        $total_rules++;
        if ($line =~ /^\s*alert/) {
            for my $opt (keys %options) {
                if ($line =~ /\s$opt[:;]/) {
                    $options{$opt}++;
                } elsif ($line =~ /\($opt[:;]/) {
                    $options{$opt}++;
                } elsif ($line =~ /;$opt[:;]/) {
                    $options{$opt}++;
                }
            }
            while ($line =~ m/[\s;](\w+)[:;]/g) {
                next if $1 =~ /^\d+$/;
                unless (defined $options{$1}) {
                    $unrecognized{$1}++;
                }
            }
        }
    }
}

my $max_opt_len = 0;
for my $opt (keys %options) {
    $max_opt_len = length($opt) if length($opt) > $max_opt_len;
}

for my $opt (sort {$options{$b} <=> $options{$a}} keys %options) {
    printf("%${max_opt_len}s %13s", $opt, "$options{$opt}/$total_rules  ");
    print sprintf("%.1f", $options{$opt} / $total_rules * 100) . "%\n";
}

print "\n[-] Potentially unrecognized options:\n";
for my $opt (keys %unrecognized) {
    print "    $opt\n";
}

exit 0;

sub usage() {
    print <<_USAGE_;
Usage: $0 [-f <sig file>] [-h]
_USAGE_
    exit 0;
}
