#!/usr/bin/perl -w use strict; use Net::Ping; use Sys::Syslog; use File::Basename; use File::stat; my $iface = "eth2"; my $NAME = basename $0; my $verbose = 0; my $apache_uid = 48; my $apache_gid = 48; log_info("Daemon started up"); # Loop forever... it's a daemon afterall while ( 1 == 1 ) { &check_old_authed(); &check_new_authed(); sleep 10; } # Ok so this won't be called ever... at least not until I write a sig handler. log_info("Daemon exiting"); # Look for already authed wireles IPs/MAC combos, do a ping to see # if they're live, and make sure arp shows known MAC. If either # it doesn't ping or mac doesn't match, remove iptables rules for # the IP/MAC combo. # This makes sure that a host's access is removed ASAP after a host goes offline. # this should minimize the chances of someone being able to jump onto the wireless net after an # authenticated user goes offline. sub check_old_authed { my $file = "/tmp/known_wireless"; my $write_file = 0; my %known_pairs = (); if (-f "$file" ) { open(KNOWN,"$file") || my_die("Can't open known file $file",1); while(<KNOWN>) { chomp; if (/(10.0.1.\d+)\s([\d|\w]+:[\d|\w]+:[\d|\w]+:[\d|\w]+:[\d|\w]+:[\d|\w]+)/) { my $ip = $1; my $mac = $2; if (!ping_host($ip,$mac)) { revoke_auth($ip,$mac); $write_file = 1; } else { $known_pairs{$ip}=$mac; } } } close(KNOWN); if ($write_file) { write_out_known(%known_pairs); } } } # Subroutine to look for the expected files in /tmp that are written out by the auth.cgi module, # make sure they're ok, then run the iptables script to give the ip/mac combo net access. sub check_new_authed { my $added = 0; my %known_hosts = (); my $known_file = "/tmp/known_wireless"; open(LS, "/bin/ls -1 /tmp/new_auth*|") || my_die("Can't run /bin/ls -1 /tmp/new_auth*",1); while (<LS>) { my $ip = ""; my $mac = ""; chomp; my $file = $_; # Do a quick test to make sure the file is owned by the right uid/gid.. quasi security. # Possible DOS attack if user could touch file first. Not an issue on my setup, I don't # have other users that shell into my box. my $info = stat($file); my $file_uid = $info->uid; my $file_gid = $info->gid; if ( ($file_uid != $apache_uid) || ($file_gid != $apache_gid) ) { log_info("saw file with improper uid/gid: uid: $file_uid gid: $file_gid"); } else { if ($file =~ /new_auth(\d+\.\d+\.\d+\.\d+)/) { $ip = $1; open(FILE,"$file") || my_die("Couldn't open file $file",1); $mac = <FILE>; chomp $mac; close(FILE); } } unlink($file) || my_die("Couldn't unlink file $file\n",1); if ( ("$ip" ne "") && ("$mac" ne "") ) { grant_auth($ip,$mac); $known_hosts{$ip}=$mac; $added=1; } } if ($added) { if ( -f "$known_file" ) { open(IN,"$known_file") || my_die("Can't open file $known_file",1); while (<IN>){ if (/(10.0.1.\d+)\s([\d|\w]+:[\d|\w]+:[\d|\w]+:[\d|\w]+:[\d|\w]+:[\d|\w]+)/) { $known_hosts{$1}=$2; } } } write_out_known(%known_hosts); } } # My die routine to write into to syslog first for admin to troubleshoot. sub my_die { my $msg = shift; my $fatal = shift; if ($fatal) { my $date = log_date(); openlog("$date $NAME", "cons,pid", "user"); syslog("local1|info","fatal: $msg"); closelog(); exit 1; } } # Subroutine that pings hosts passed to it, returns 0 if the host is either not online or # if the mac seen in the arp table after the ping doesn't match what's expected. sub ping_host { my $ip = shift; my $mac = shift; my $seen_mac = ""; print "Pinging host $ip $mac" if $verbose; my $p = Net::Ping->new(); if ($p->ping($ip,7)) { $p->close(); open(ARP,"/sbin/arp $ip|") || my_die("Couldn't arp host $ip",0); while (<ARP>) { next if /^Address/; if (/^$ip\s+ether\s+([\d|\w]+:[\d|\w]+:[\d|\w]+:[\d|\w]+:[\d|\w]+:[\d|\w]+)\s+.*$iface/) { $seen_mac = $1; } } close(ARP); if ("$seen_mac" eq "$mac") { print "ok\n" if $verbose; return 1; } } $p->close(); print "no answer\n" if $verbose; return 0; } # Logs to syslog and calls script that removes iptables rules allowing passed ip/mac pair # net access. sub revoke_auth { my $ip = shift; my $mac = shift; print "About to run /usr/sbin/wireless-forwarding deny $ip $mac\n" if $verbose; system("/usr/sbin/wireless-forwarding deny $ip $mac"); log_info("revoked internet access from $ip $mac"); } # Logs to syslog and calls script that adds iptables rules allowing passed ip/mac pair # net access. sub grant_auth { my $ip = shift; my $mac = shift; print "About to run /usr/sbin/wireless-forwarding allow $ip $mac\n" if $verbose; system("/usr/sbin/wireless-forwarding allow $ip $mac"); open(CONF,">/tmp/auth_done$ip$mac"); chown($apache_uid,$apache_gid,("/tmp/auth_done$ip$mac")); print CONF "done"; close(CONF); log_info("Granted internet access to $ip $mac"); } # Writes the known ip/mac pairs to the /tmp/known_wireless file sub write_out_known { my %known = @_; my $file = "/tmp/known_wireless"; unlink($file) || my_die("Couldn't unlink $file",1); open(KNOWN,">$file") || my_die("Couldn't open file $file for writing",1); foreach my $ip (keys %known) { print KNOWN "$ip $known{$ip}\n"; } close(KNOWN); chmod(600,$file); } # Subroutine to log syslog info... saved some typing making it a sub sub log_info { my $msg = shift; my $date = log_date(); openlog("$date $NAME", "cons,pid", "user"); syslog("local1|info","info: $msg"); closelog(); } sub log_date { my $date = localtime(time()); $date =~ s/^\w+\s//g; $date =~ s/\s\d\d\d\d$//g; return $date; }