#!/usr/bin/perl -Tw # ################################# # # Jon Hart (jhart@ccs.neu.edu) # or # warchild@spoofed.org # for # crew (crew@ccs.neu.edu) # # # Watch the firewall. Nuke rules # corresponding to dead/unresponsive/expired # hosts. # # a.) Query the current ruleset; snag hosts # b.) See if each unique host responds to an arp-ping # c.) If no response, nuke the relevant rules # d.) If response, check against our record of allowed MACs # e.) If current MAC and allowed do no match, nuke. # f.) If match, done. # ################################# use strict; use Getopt::Long; use Unix::Syslog qw(:macros); # Syslog macros use Unix::Syslog qw(:subs); # Syslog functions # housekeeping $ENV{'PATH'} = "/usr/bin:/bin"; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $time; my $debug; GetOptions( "time=s" => \$time, "debug" => \$debug); if ( !(defined $time) ) { $time = 1800; } while ( 1 ) { &query(); sleep($time); } ######################################################################### # get the desired IP's out of the current ruleset sub query { my $IPFSTAT_CMD = "/sbin/ipfstat -i"; my @temp_ruleset; my $host; # the host we are currently checking my $check_arp_result; # the result of checking the host my $nuke_rule; # the current rule we are checking my %good; # hosts that don't need to be checked this time my %bad; # hosts that don't need to be checked; just nuke 'em # snag the current active ruleset die "Cannot fork: $!" unless defined (my $pid = open(KID, "-|")); if ($pid == 0) { exec($IPFSTAT_CMD) or die "Cannot get ipf status: $!"; } else { @temp_ruleset = ; close KID; } my @nuke_rules = grep { s/^\s+//; s/\s+$//; $_ } @temp_ruleset; if ( defined $debug) { print "Checking rules\n"; } foreach (@nuke_rules) { $nuke_rule = $_; if ( /^pass in quick on de0.*group.*$/ ) { if ( /([\d.]+)\/32/ ) { $host = $1; if ( exists $good{$host} ) { # we've checked this host already and it's safe. if (defined ($debug)) { print "Skipping rule for $host -- alread checked\n"; } next; } elsif ( exists $bad{$host} ) { # we've checked this host already and it's not safe. if (defined($debug)) { print "Nuking rule for $host -- already found to be bad\n"; } &nuke($nuke_rule); next; } $check_arp_result = check_arp($host); if ( $check_arp_result eq "trusted" ) { if (defined($debug)) { print "$host is trusted\n"; } $good{$host} = 1; } elsif ( $check_arp_result eq "untrusted" ) { if (defined($debug)) { print "$host is untrusted! Removing all rules\n"; } openlog "rule_reaper", LOG_PID | LOG_PERROR, LOG_DAEMON; syslog LOG_INFO, "$host is untrusted! Removing all rules."; closelog(); &nuke($nuke_rule); $bad{$host} = 1; } elsif ( $check_arp_result eq "false" ) { if (defined($debug)) { print "$host was unresponsive -- nuking\n"; } &nuke($nuke_rule); $bad{$host} = 1; } } } } } # end_query ########################################################################### # check to see if a host is up (arp), and if its allowed (/var/db/dhcp.leases) # return true if it is both responds to an "arp who has" and is allowed sub check_arp { my $host = shift; my $LEASE_FILE = "/priv/adm/data/dhcp.leases"; my $ARPING_CMD = "/usr/local/bin/arping -rc 1 $host"; my %MAC; my $jail_mac; my $return = "false"; # arp-ping this host open(ARP, "$ARPING_CMD |") or die "Couldn't arp-ing $host : $!\n"; while() { chomp; $jail_mac = $_; } close(ARP); # a responsive host will have defined $jail_mac unless ( !( defined $jail_mac )) { if (defined($debug)) { print "Checking $jail_mac\n"; } open(LEASES, "< $LEASE_FILE") or die "Couldn't open lease file: $!\n"; while () { # hash all the registered MACs unless (/#/) { chomp; my @record = split(/,/, $_); my $record_mac = lc($record[0]); #unless ( exists( $MAC{$record[0]}) ) { # $MAC{$record[0]} = 1; #} unless (exists( $MAC{$record_mac})) { $MAC{$record_mac} = 1; } } } close(LEASES); # setup some kludgy return values if ( exists( $MAC{$jail_mac} ) ) { # a MAC that we know about and is trusted if(defined($debug)) { print "$jail_mac is trusted\n"; } $return = "trusted"; } else { # we got a response, but the MAC is untrusted if(defined($debug)) { print "$jail_mac is untrusted\n"; } $return = "untrusted"; } } # getting here means that the host is down or unresponsive # and the return value is still false (from the initializtion) return $return; } # end_check_arp ########################################################################## # pass a rule to get nuked from the firewall sub nuke { my $unsafe_rule = shift; my $rule; if ( $unsafe_rule =~ /^([\w\s\d.=\/]+)$/ ) { print "$1 is nukeable\n"; $rule = $1; # make the command to nuke a rule my @nuke_cmd = ("/bin/echo $rule | /sbin/ipf -yr -f -"); # ipf is pretty graceful, so just check to see if it barfed my $nuke_result = system @nuke_cmd; } }