#!/usr/bin/perl -wT ######################## # # Uh, yeah. # Watch /var/log/daemon 'cuz swatch sux. # Watch for dhcp leases going out, and concoct # firewall rules for these new IP's. # # Jon Hart (jhart@ccs.neu.edu) # ######################## use strict; use Getopt::Long; use Unix::Syslog qw(:macros); # Syslog macros use Unix::Syslog qw(:subs); # Syslog functions my $debug; GetOptions( "debug" => \$debug); # make Perl happy $ENV{'PATH'} = "/usr/bin:/bin"; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # dhcpd, among others, writes to this file. my $DHCPD_LOG = "/var/log/daemon"; # the base firewall rules to be reinstated when things go boink. my $BASE_FWRULES = "/etc/ipf.rules"; my $ALLOWED_TRAFFIC = "/priv/adm/data/allowed_traffic"; # tail -f'ing the log and using this script to parse # results in buffering errors, so just tail it here instead. my $WATCH_CMD = "/usr/bin/tail -n 1 -f $DHCPD_LOG"; # store all the IP's in the firewall here my %IPS; # sanitize stuff a bit $SIG{INT} = 'IGNORE'; $SIG{HUP} = $SIG{QUIT} = \&reset; #unless (defined $debug) { # openlog "firewatch", LOG_PID | LOG_PERROR, LOG_DAEMON; # syslog LOG_INFO, "Firewatch started"; # closelog(); # fork() or die "Couldn't fork: $!"; # #fork && exit; # &reset(); #} &reset(); open(WATCH, "$WATCH_CMD |"); while () { chomp; # we only care about DHCPACK's... unless ( /DHCPACK/ ) { next; } # ...which are generated by dhcpd unless ( /dhcpd/ ) { next; } # tear up the log. my ($month, $day, $time, $host, $daemon, @message) = split(/\s+/, $_); # this should be the IP we want my $possible_newip = $message[2]; # this is the IP we will use if correct my $newip; # untaint this IP (check to make sure its all numeric) if ($possible_newip =~ /^([\d.]+)$/) { $newip = $1; &addtofirewall($newip); } else { # either the IP was formatted incorrectly # or I can't parse. print "$possible_newip seems wonky\n"; } } close(WATCH); sub addtofirewall { my $host = shift; my @rules; my $new_rule; open(RULES, "$ALLOWED_TRAFFIC") or die "Couldn't open base file: $!"; while() { my ($port, $proto) = split(/\s+/, $_); if ($proto eq "tcp") { $new_rule = "pass in quick on de0 proto $proto from $host to any port = $port flags S/SA keep state group $port"; } elsif ($proto eq "udp") { $new_rule = "pass in quick on de0 proto $proto from $host to any port = $port keep state group $port"; } push(@rules, $new_rule); } close(RULES); foreach (@rules) { # untaint the rule, making sure $host doesn't contain anything crazy my $unsafe_rule = $_; my $rule; if ( $unsafe_rule =~ /^([\w\s\d.\/=]+)$/ ) { $rule = $1; my @ruleaddition = ("/bin/echo $rule | /sbin/ipf -f -"); my $result = system @ruleaddition; openlog "firewatch", LOG_PID | LOG_PERROR, LOG_DAEMON; syslog LOG_INFO, "Added $host to firewall"; closelog(); } } } sub reset { # flush the ruleset my @clearcmd = ("/sbin/ipf -Fa -f $BASE_FWRULES"); system @clearcmd; # and nuke the hash of IP's foreach (keys %IPS) { delete $IPS{$_}; } } # stupid tail has a bad habit of not closing END { close(WATCH); }