#!/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;
}