#!/usr/local/bin/perl
#
# Check iptables on linux systems
#
# $Header: /home/doke/work/nagios/RCS/check_iptables,v 1.4 2015/12/09 20:32:16 doke Exp $


use strict;
use warnings;
use Getopt::Long;
#use Data::Dumper;

use vars qw( $verbose $help @crits @warns @unknowns @oks @ignores );

$ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin";

$verbose = 0;
$help = 0;

sub usage {
    my( $rc ) = @_;
    print "Usage: $0 [-vh]
    -v    verbose
    -h    help
";
    exit $rc;
    }

Getopt::Long::Configure ("bundling");
GetOptions(
    'v+' => \$verbose,
    'h' => \$help,
    );
&usage( 0 ) if ( $help );

&check_iptables();

my $rc = 0;
my $sep = '';
if ( $#crits >= 0 ) {
    $rc = 2;
    print "CRITICAL ", join( ", ", @crits );
    $sep = '; ';
    }
if ( $#warns >= 0 ) {
    $rc = 1 if ( $rc == 0 );
    print $sep, "Warning ", join( ", ", @warns );
    $sep = '; ';
    }
if ( $#unknowns >= 0 ) {
    $rc = -1 if ( $rc == 0 );
    print $sep, "Unknown ", join( ", ", @unknowns );
    $sep = '; ';
    }
if ( $rc == 0 ) {
    print "Ok ", join( ", ", @oks );
    $sep = '; ';
    }
if ( $#ignores >= 0 ) {
    print $sep, "Ignoring ", join( ", ", @ignores );
    }


print "\n";
exit $rc;


##################



sub check_iptables {
    my( $cmd, $nlines, $chain, %chain_policy, %chain_references,
	%chain_rules, $chain_index, $nrules, $total_pkts, $pkts, $n, $m,
	$i, $last_target, $policy );

    $cmd = "sudo iptables -L -v -n |";
    $verbose && print "+ $cmd\n";
    if ( ! open( pH, $cmd ) ) { 
	push @unknowns, "can't sudo iptables: $!";
	return;
	}

    $nlines = 0;
    while ( <pH> ) { 
	$nlines ++;
	if ( m/^Chain (\S+) \(policy (\w+)/i ) { 
	    $chain = $1;
	    $chain_policy{ $chain } = $2;
	    $chain_index = 0; 
	    }
	elsif ( m/^Chain (\S+) \((\d+) references/i ) { 
	    $chain = $1;
	    $chain_references{ $chain } = $2;
	    $chain_index = 0; 
	    }
	elsif ( m/^\s*pkts\s+bytes/i )  { 
	    # header, ignore
	    next;
	    }
	elsif ( m%^ \s* (\d+[kmg]?) \s+ (\d+[kmg]?) \s+ (\S+) \s+ (\S+) 
		\s+ (\S+) \s+ (\S+) \s+ (\S+) 
		\s+ ([\d\.\/]+) \s+ ([\d\.\/]+) 
		\s* (\S.*)? %ix ) { 
	    $chain_rules{ $chain }[ $chain_index ]{ pkts } = $1;
	    $chain_rules{ $chain }[ $chain_index ]{ bytes } = $2;
	    $chain_rules{ $chain }[ $chain_index ]{ target } = $3;
	    $chain_rules{ $chain }[ $chain_index ]{ protocol } = $4;
	    $chain_rules{ $chain }[ $chain_index ]{ opt } = $5;
	    $chain_rules{ $chain }[ $chain_index ]{ int_in } = $6;
	    $chain_rules{ $chain }[ $chain_index ]{ int_out } = $7;
	    $chain_rules{ $chain }[ $chain_index ]{ source } = $8;
	    $chain_rules{ $chain }[ $chain_index ]{ dest } = $9;
	    $chain_rules{ $chain }[ $chain_index ]{ module_options } = $10;
	    $chain_index ++;
	    }
	}
    close pH;

    if ( ! $nlines ) { 
	push @crits, "iptables -L produced no output";
	return;
	}

    # check the input chain policy
    $policy = $chain_policy{ 'INPUT' };
    if ( ! defined $policy ) { 
	push @crits, "INPUT chain does not exist";
	return;
	}
    elsif ( $policy ne 'DROP' && $policy ne 'REJECT' ) { 
	push @warns, "INPUT chain default policy is $policy";
	}
    else {
	push @oks, "INPUT chain default policy is $policy";
	}
    if ( ! defined $chain_rules{ 'INPUT' } ) {   
	push @crits, "INPUT chain has no rules";
	return;
	}

    # check the input chain rules 
    $nrules = scalar( @{$chain_rules{ 'INPUT' }} );  
    $total_pkts = 0;
    foreach $i ( 0 .. $nrules - 1 ) {  
	$pkts = $chain_rules{ 'INPUT' }[ $i ]{ pkts };
	if ( $pkts =~ m/(\d+)([kmg])/i )  { 
	    $n = $1;
	    $m = lc $2;
	    if ( $m eq 'k' ) { 
		$pkts = $n * 1000;
		}
	    elsif ( $m eq 'm' ) { 
		$pkts = $n * 1000000;
		}
	    elsif ( $m eq 'g' ) { 
		$pkts = $n * 1000000000;
		}
	    }
	$verbose && print "INPUT chain rule $i has $pkts packet hits\n";
	$total_pkts += $pkts;
	}

    # check the input chain has rules
    if ( $nrules <= 0 ) { 
	push @crits, "INPUT chain has no rules";
	}
    else { 
	push @oks, "INPUT chain has $nrules rules";
	}

    if ( $total_pkts == 0 ) { 
	push @warns, "INPUT chain does not have any packet hits";
	}
    else { 
	push @oks, "INPUT chain has $total_pkts packet hits";
	}

    # check the input chain ends with drop or reject
    $last_target =  $chain_rules{ 'INPUT' }[ $nrules - 1 ]{ 'target' };
    if ( $last_target ne 'DROP' && $last_target ne 'REJECT' ) { 
	push @warns, "INPUT chain ends with $last_target instead of DROP or REJECT";
	}


    # todo: 
    # look for allowing ftp?
    # look for allowing telnet?
    # look for allowing any-any rules?
    # look for allowing any-any rules?
    }



