#!/usr/bin/perl # $Id: ipfw_compare.pl,v 1.3 2007/05/22 06:55:15 pkuijv Exp $ # # *BSD ipfw compare script by Petje and Vo # Please email your remarks and suggestions to < ipfw_compare at painfullscratch.nl > # Tested on FreeBSD (4 .. 6) # # Compares 'ipfw list'-output and ipfw's config (set in rc.conf) and output's the difference (if any) # # Needs /usr/ports/net-mgmt/p5-NetAddr-IP, /usr/ports/devel/p5-Algorithm-Diff and Perl :) # my $rcconf = '/etc/rc.conf'; my $ipfwlist = '/sbin/ipfw list'; # # # # use lib './lib'; # for those who install Algorithm::Diff and NetAddr::IP in '.' use strict; use Algorithm::Diff; use NetAddr::IP; my $VERSION = '0.8.3'; my $ipfw_log_limit = undef; # This "singleton" is set by get_ipfw_log_limit(). sub read_file { my ($file) = @_; local($/) = wantarray ? $/ : undef; local(*F); my $r; my (@r); open(F, "<$file") || die "error opening $file: $!"; @r = ; close(F) || die "error closing $file: $!"; return $r[0] unless wantarray; return @r; } # rcconf configfilename as input parameter sub get_ipfw_configfilename { my $rcconf = shift; my $content = main::read_file($rcconf); unless($content =~ /^firewall_enable=["']{1}YES["']{1}\s*$/m) { die "The firewall_enable option in '$rcconf' is not set to enabled!"; } unless($content =~ /^firewall_type=["']{1}(\/.+)["']{1}\s*$/m) { die "The firewall_type option in '$rcconf' makes no sense or isn't present!"; } return $1; } sub get_ipfw_log_limit { unless(defined($ipfw_log_limit)) { # Get active ipfw_log_limit from kernel and set global var. $ipfw_log_limit = `/sbin/sysctl -n net.inet.ip.fw.verbose_limit 2> /dev/null`; chomp($ipfw_log_limit); } return $ipfw_log_limit; } sub collect_active_array { my @activerules; my @ipfwlist_output = `$ipfwlist`; for my $line (@ipfwlist_output) { chomp($line); push(@activerules,main::normalize_rule($line)); } # ignore the implicit deny rule at the end if($activerules[$#activerules] eq 'deny ip from any to any') { pop(@activerules); } return @activerules; } sub collect_config_array { my $ipfwconf = shift; my @configlines = main::read_file($ipfwconf); my @configrules; foreach my $line (@configlines) { chomp($line); # only the 'add'-lines .. # todo: makes no sense, one can have a 'flush' somewhere in the middle ... if($line =~ /^\s*add\s*(.*?)$/) { push(@configrules,main::normalize_rule($1)); } } return @configrules; } # the ever changing 'normalize_rule'-sub .. sub normalize_rule { my $rule = shift; # strip linenumbers and whitespace at begin and end of line $rule =~ s/^\s*?\d*\s*(.*?)\s*$/$1/; # strip double whitespace $rule =~ s/\s{1,}/ /g; # normalize 'allow|accept|pass|permit' and deny|drop my $ipfw_allow_regexp = '^'.join('|',qw { allow accept pass permit }).'(.*)$'; my $ipfw_deny_regexp = '^'.join('|',qw { deny drop }).'(.*)$'; $rule =~ s/${ipfw_allow_regexp}/allow${1}/; $rule =~ s/${ipfw_deny_regexp}/deny${1}/; # todo , make the to|from while normalizing ipaddr nicer :) # normalize 'ip|all' $rule =~ s/^(allow|deny)\s+(log\s+)*(ip|all)(\s+from.*)$/${1} ${2}ip${4}/g; # normalize ipaddresses and subnets $_ = $rule; if(/^(.*to)\s+((?:(?:\d{1,3}\.){3}\d{1,3})(?:\/\d{1,2}|\:(?:\d{1,3}\.){3}\d{1,3})*)\s+(.*)$/) { $rule = $1.' '.main::normalize_ip($2).' '.$3; } $_ = $rule; if(/^(.*from)\s+((?:(?:\d{1,3}\.){3}\d{1,3})(?:\/\d{1,2}|\:(?:\d{1,3}\.){3}\d{1,3})*)\s+(.*)$/) { $rule = $1.' '.main::normalize_ip($2).' '.$3; } # normalize optional dst-port|src-port $rule =~ s/^(.*to)\s+((?:(?:\d{1,3}\.){3}\d{1,3})(?:\/\d{1,2}|\:(?:\d{1,3}\.){3}\d{1,3})*)\s+(\d{1,5}.*)$/$1 $2 dst-port $3/; $rule =~ s/^(.*from)\s+((?:(?:\d{1,3}\.){3}\d{1,3})(?:\/\d{1,2}|\:(?:\d{1,3}\.){3}\d{1,3})*)\s+(\d{1,5}.*)$/$1 $2 src-port $3/; # 'via any' is trivial $rule =~ s/^(.*?)\s*via any$/${1}/; # normalizing keep-state and established $rule =~ s/^(.*)\s+(keep-state|established) via (.+)$/${1} via ${3} ${2}/; # ipfw is nice to 'in via' and 'out via' rules $rule =~ s/^(.*\s+)in via (.{3,5})$/${1}in recv ${2}/; $rule =~ s/^(.*\s+)out via (.{3,5})$/${1}out xmit ${2}/; # ipoptions -> ipopt $rule =~ s/ipoptions/ipopt/g; # ignore logamount . Which is set with net.inet.ip.fw.verbose_limit kernel parameter. my $loglimit = main::get_ipfw_log_limit(); $rule =~ s/logamount\s+${loglimit}\s+//g; # ipfw on fbsd 4.x changes syntax on a rule from 'in via keep-state' to 'keep-state in recv ' $rule =~ s/^(.*\s+)in via (.{3,5})\s+keep-state/${1}keep-state in recv ${2}/; # ipfw on fbsd 4.x changes syntax on a rule from 'out via keep-state' to 'keep-state out xmit ' $rule =~ s/^(.*\s+)out via (.{3,5})\s+keep-state/${1}keep-state out xmit ${2}/; # ipfw is flexible on syntax 'via em0 setup limit {src-addr|src-port|dst-addr|dst-port} N' to # 'limit {src-addr|src-port|dst-addr|dst-port} N via em0 setup' $rule =~ s/^(.*\s+)via (.{3,5}) setup limit (src-addr|src-port|dst-addr|dst-port) ([0-9]*)/${1}limit ${3} ${4} via ${2} setup/; return $rule; } sub normalize_ip { my $ip = shift; $ip =~ s/^((?:\d{1,3}\.){3}\d{1,3}):((?:\d{1,3}\.){3}\d{1,3})$/$1\/$2/; my $nip = new NetAddr::IP($ip); return $nip->cidr(); } 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"); } if ( $ARGV[0] eq "-v" ) { print $main::VERSION."\n"; exit(0); } my $ipfwconf = main::get_ipfw_configfilename($rcconf); my @activerules = main::collect_active_array(); my @configrules = main::collect_config_array($ipfwconf); my $diff = Algorithm::Diff->new( \@activerules, \@configrules ); $diff->Base(1); my @output; while( $diff->Next() ) { next if $diff->Same(); 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 { 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 ('.$ipfwlist.")\n"; print '--- config ('.$ipfwconf.")\n"; print join("\n",@output); print "\n"; }; exit(0);