#!/usr/bin/perl
#
# *BSD route compare script by Petje < route_compare at painfullscratch.nl >
# tested on FreeBSD
#
# Compares 'netstat -rn' and 'rc.conf' and outputs differences to stdout.
#
# Needs /usr/ports/net-mgmt/p5-NetAddr-IP, /usr/ports/devel/p5-Algorithm-Diff and Perl :)
#

my $rcconf      = '/etc/rc.conf';
my $netstatrn   = '/usr/bin/netstat -W -r -n -f inet';

#
#
#
#

use lib './lib';    # for those who install Algorithm::Diff and NetAddr::IP in '.'
use strict;
use NetAddr::IP;
use Algorithm::Diff;
my $VERSION = '0.3.1';

sub collect_active_array {
	my @active_array;
	my @activeroutes = `$netstatrn`;
	for my $line (@activeroutes) {
		next if ($line =~ /^127.0.0.1\s+/);
		$line =~ s!^default(\s+.*?)!0.0.0.0/0${1}!;
		if($line =~ /^([0-9\.\/]+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+.*?$/) { 
			my $dest = $1; my $gateway = $2;
			push(@active_array,main::normalize_ip($1).' '.main::normalize_ip($2));
		} 
	}
	return sort(@active_array);
}

sub collect_config_array {
	my @config_array;
	my $configlines = main::read_file($rcconf);
	if($configlines =~ /^static_routes=["'](.+)["']\s*$/m) {
		for my $route (split(/\s+/,$1)) {
			if($configlines =~ /^route_$route=['"]-(host|net)\s+(.+?)\s+(.+?)['"]/m) {
				my $type = $1; my $dest = $2; my $gateway = $3;
				my $ip = main::normalize_ip($dest);
				if($type eq 'host' && $ip->num != 1) {
					print STDERR $ip." isn't a host-type\n";
				}
				if($type eq 'net' && $ip->num <= 1) {
					print STDERR $ip." isn't a net-type\n";
				}
				push(@config_array,$ip.' '.main::normalize_ip($gateway));
			}
			else { 
				print STDERR "There's something wrong with route \"$route\"\n";
			} 
		} 
	} 
	if($configlines =~ /^defaultrouter=["'](.+)["']\s*$/m) {
		my $gateway = $1;
		push(@config_array,'0.0.0.0/0 '.main::normalize_ip($gateway));
	} 
	return sort(@config_array);
} 

sub normalize_ip {
        my $ip = shift;

        my $octets = '0';
        $_ = $ip;
        if(! /.*\/\d{1,2}$/) {
                $octets = scalar(split(/\./,$ip));
                for(my $i=scalar($octets); $i < 4; $i++) {
                        $ip .= '.0';
                }
        }
        if($octets > 0) { $ip .= '/'.($octets * 8); }

        my $nip = new NetAddr::IP $ip;
        return $nip;
}

sub read_file {
	my ($file) = @_;
	local($/) = wantarray ? $/ : undef; 
	local(*F);
	my $r; my (@r);
	open(F, "<$file") || die "error opening $file: $!";
	@r = <F>;
	close(F) || die "error closing $file: $!";
	return $r[0] unless wantarray;
	return @r;
}

die('You must be root to run this program') unless($> == 0);
my $reqversion = 1.19; # OO interface available since 1.19
if($Algorithm::Diff::VERSION < $reqversion) { 
	die("I need at least version $reqversion of Algorithm::Diff");
}

my @activerules = main::collect_active_array();
my @configrules = main::collect_config_array();

my $diff = Algorithm::Diff->new( \@activerules, \@configrules );
$diff->Base(1);
my @output;
while(  $diff->Next()  ) {
	next   if  $diff->Same();
	my $sep = '';
	if(  ! $diff->Items(2)  ) {
	    push(@output,sprintf "%d,%dd%d", $diff->Get(qw( Min1 Max1 Max2 )));
	} elsif(  ! $diff->Items(1)  ) {
	    push(@output,sprintf "%da%d,%d", $diff->Get(qw( Max1 Min2 Max2 )));
	} else {
	    $sep = "---\n";
	    push(@output,sprintf "%d,%dc%d,%d", $diff->Get(qw( Min1 Max1 Min2 Max2 )));
	}
	push(@output,"+ ${_}") for $diff->Items(1);
	push(@output,"- ${_}") for $diff->Items(2);
}

if(scalar(@output) > 0) { 
	print '+++ active ('.$netstatrn.")\n";
	print '--- config ('.$rcconf.")\n";
	print join("\n",@output);
	print "\n";
};

exit(0);
