#!/usr/bin/perl -w

use strict;
use Mail::IMAPClient;
use File::stat;
use Sys::Syslog;
use File::Basename;

my $NAME=basename $0;
my $debug = 0;

# Make sure the output is valid 
print "Content-type: text/html\n\n";
print "<html>
  <head>
    <Title>Wireless authorization</Title>
  </head>
  <body>
";

# See if someone's trying to access the auth bits incorrectly
if ($ENV{'REQUEST_METHOD'} ne "POST") {
  print "Sorry, this form is not for $ENV{'REQUEST_METHOD'} requests<br>\n";
} else {
  (my $username, my $password, my $ip, my $mac) = parse_post();
  # I made this is a call to a subroutine so if anyone wants a different auth method
  #  they can just change that one subroutine.
  if (authenticate($username,$password)) {
    # Create the file and insert it's MAC for the daemon to parse and run the
    #  proper iptables rules to allow this host through.
    open(FILE,">/tmp/new_auth$ip") || my_bad("Couldn't open /tmp/new_auth$ip for auth daemon");
    print FILE "$mac";
    close(FILE);
    print "Authorization complete... please wait for filter to take effect....<br>\n";
    # Wait for auth daemon to write out file letting this script know it's ok to let user
    #  know it's rules have been run.
    while (! -f "/tmp/auth_done$ip$mac") {
      sleep 1;
    }
    # Do a quickie test to make sure the file is owned by the right user/group.. quasi 
    #  security and way to keep user from spoofing.  Does leave open possible DOS attacks
    #  by user.
    my $info = stat("/tmp/auth_done$ip$mac");
    my $file_gid = $info->gid;
    my $file_uid = $info->uid;
    my $apache_uid = 48;
    my $apache_gid = 48;
    if ( ( $file_uid != $apache_uid ) || ($file_gid != $apache_gid) ) {
      finish_html("There was a problem with the authorization file, please contact Mike");
      my_die("File /tmp/auth_done$ip$mac had wrong uid/gid: $file_uid / $file_gid, please investigate",1);
    }
    unlink("/tmp/auth_done$ip$mac");
    finish_html("Filter rules in place, you now have access to the net<br>\n");
  } else {
    finish_html("Authorization failed, please go <a href=https://10.0.1.1/wireless_auth/>back</a> and try again<br>\n");
  }
}

# sub that just saved me from printing out the final html tags more then once.
sub finish_html {
  my $msg = shift;
print "
    $msg
  </body>
</html>
";
}

# Parse the POST info passed via webserver and set variables
#  I used the Sys::Syslog bits here to log data if $debug at the head is set to 1
#  to help troubleshoot some stuff.
sub parse_post {
  my $pw = "";
  my $un = "";
  my $ip = "";
  my $mac = "";
  read(STDIN, my $buffer, $ENV{'CONTENT_LENGTH'});
  my @args = split(/&/,$buffer);
  while (my $line = pop @args) {
    if ($debug) {
      open(DEBUG,">>/tmp/debug.auth");
      print DEBUG "before scrub: $line\n";
      openlog("auth.cgi", "cons,pid", "user");
      syslog("local1|info","info: post line: $line");
      closelog();
    }
    $line = scrub_html($line);
    if ($debug) {
      print DEBUG "After scrub: $line\n\n";
      close(DEBUG);
      openlog("auth.cgi", "cons,pid", "user");
      syslog("local1|info","info: post line after changes: $line");
      closelog();
    }
    (my $arg, my $value) = split(/=/,$line);
    if ($arg eq "user_pass") {
      $pw = $value;
    } elsif ($arg eq "user_name") {
      $un = $value;
    } elsif ($arg eq "rem_ip") {
      $ip = $value;
    } elsif ($arg eq "rem_mac") {
      $mac = $value;
      # never could find why the mac had two +'s added, but this strips them if they're there.
      $mac =~ s/\+\+//g;
    }
  }
  if ($debug) {
    openlog("auth.cgi", "cons,pid", "user");
    syslog("local1|info","info: parsed from post ip: $ip, mac: $mac, user: $un, pass $pw");
    closelog();
  }
  return $un,$pw,$ip,$mac;
}

#Simple authentication subroutine.  Currently just authenticates against a running imap server on localhost.
#  I already have an imap server so I used that.
sub authenticate {
  my $user_name = shift;
  my $user_pass = shift;

  my $imapConn = Mail::IMAPClient->new;
  $imapConn->Port("143");
  $imapConn->Server("localhost");
  $imapConn->User($user_name);
  $imapConn->Password($user_pass);
  if ($debug) {
    openlog("auth.cgi", "cons,pid", "user");
    syslog("local1|info","info: About to auth with $user_name pw: $user_pass");
    closelog();
  }
  if ($imapConn->connect() && $imapConn->login()) {
    $imapConn->logout();
    return 1;
  } else {
    return 0;
  }
}

# Made my own die subroutine to make sure and syslog what went wrong before quitting so that admin can
#  have at least some idea what happened.
sub my_die {
  my $msg = shift;
  my $fatal = shift;
  if ($fatal) {
    openlog($NAME, "cons,pid", "user");
    syslog("local1|info","fatal: $msg");
    closelog();
    exit 1;
  }
  return 0;
}

# Special characters (which we all have at least one of in our password.. right?) are changed by web server
#  when passed since it can't handle raw special characters.  This routine changes them back.
sub scrub_html {
  my $line = shift;

  $line =~ s/%3A/:/g;
  $line =~ s/%23/#/g;
  $line =~ s/%40/@/g;
  $line =~ s/%21/!/g;
  $line =~ s/\s+//g;
  $line =~ s/%24/\$/g;
  $line =~ s/%25/%/g;
  $line =~ s/%26/\&/g;
  $line =~ s/%2A/\*/g;
  $line =~ s/%2B/\+/g;
  $line =~ s/%3D/=/g;
  $line =~ s/%28/\(/g;
  $line =~ s/%29/\)/g;
  if ($line =~ /\+\+$/) {
    $line =~ s/\+\+//g;
  }

  return $line;
}