#!/usr/bin/perl
#
# Preforking PROXY w/ Postforking ADMIN
#
use IO::Socket;
use IO::Select;
use Symbol;
use POSIX;
use strict;
use MIME::Base64;
use DB_File;
# DEBUG DEFINES
my $DEBUG_PRINT_HEADERS = 'true';
my $DEBUG_PRINT_CHILDBIRTHS = 1;
my $DEBUG_PRINT_CHILDDEATHS = 1;
my $DEBUG_PRINT_PROXYCLIENTCONNECTS = 1;
my $DEBUG_PRINT_PROXYCLIENTDISCONNECTS = 1;
my $DEBUG_PRINT_PROXY_AUTHENTICATIONS = 1;
my $DEBUG_PRINT_PROXY_CREDENTIALS = 1;
my $DEBUG_USE_DEBUG_CREDENTIALS;# = 1;
my $DEBUG_SHOW_ADMIN_REQUEST_PAIRS = 1;
my $DEBUG_USE_ORIGIGNAL_PROXY_CODE = 1;
my $DEBUG_USE_SELECT_STYLE_PROXY_CODE;# = 1;
my $DEBUG_PROXY_USERNAME = 'mrj';
my $DEBUG_PROXY_PASSWORD = 'carol';
# ==============================================================================================================================
# THESE SETTINGS ARE READ FROM CONFIGURATION FILE NOW!
# PARENT PROXY DEFINES
my $PARENT_PROXY_ENABLE;# = 'true';
my $PARENT_PROXY_HOSTNAME;# = 'proxy.qut.edu.au';
my $PARENT_PROXY_PORT;# = 3128;
my $PARENT_PROXY_USERNAME;# = 'n2763524';
my $PARENT_PROXY_PASSWORD;# = '';
# SSH TUNNEL TO QUT DEFINES
my $SSH_TUNNEL_ENABLE;# = 'true';
my $SSH_TUNNEL_LISTEN_PORT;# = 12345;
my $SSH_TUNNEL_LISTEN_HOST;# = 'seduction.malevolent.org';
# Constant
my $PROXY_LISTEN_PORT;# = '11611';
my $ADMIN_LISTEN_PORT;# = '11711';
my $MAX_CHILDREN;# = 5;
my $MAX_CLIENTS_PER_CHILD;# = 5;
my $USE_AUTHENTICATED_PROXY;
my $SOCKET_SELECT;
# ==============================================================================================================================
# CONSTANTS
my $BUFFERSIZE = 1024; # In bytes
my $MYPID = "Parent Process";
my $CONFIGURATION_FILE = 'simpleserver.conf';
my $DB_DRIVER = 'DB_File';
my $DB_AUTH = 'db/auth.db';
my $DB_BANDWIDTH = 'db/bandwidth.db';
# Globals
my $PROXY_USERNAME;
my $PROXY_PASSWORD;
# pid's of kids
my %children = ();
# how many kids
my $numberOfChildren = 0;
&setSigHandler();
#print "Parsing config...";
&parseConfigurationFile();
#print "done\n";
# Proxy Client
my $PROXYCLIENT;
# Proxy Server Bind and Listen
my $PROXYSERVER = IO::Socket::INET->new(
LocalPort => $PROXY_LISTEN_PORT,
Type => SOCK_STREAM,
Proto => 'tcp',
Reuse => 1,
Listen => 10)
or die "Can not start proxy server on $PROXY_LISTEN_PORT : $@\n";
# Admin Connection
my $ADMINCLIENT;
# Admin Server Bind and Listen
my $ADMINSERVER = IO::Socket::INET->new(
LocalPort => $ADMIN_LISTEN_PORT,
Type => SOCK_STREAM,
Proto => 'tcp',
Reuse => 1,
Listen => 10)
or die "Can not start admin server on $ADMIN_LISTEN_PORT : $@\n";
my $SOCKET_SELECT = IO::Select->new($ADMINSERVER)
or die "Can't create IO::Select object";
$SOCKET_SELECT->add($PROXYSERVER);
&makeKids();
&maintainChildPopulation();
sub maintainChildPopulation
{
while (1) {
sleep;
for (my $i = $numberOfChildren; $i < $MAX_CHILDREN; $i++) {
&makeChild();
}
}
}
sub setSigHandler
{
$SIG{CHLD} = \&CHILDKILLER;
$SIG{INT} = \&KILLALLCHILDREN;
}
sub makeKids
{
for (1 .. $MAX_CHILDREN) {
&makeChild();
}
}
sub CHILDKILLER # Kill zombie children
{
$SIG{CHLD} = \&CHILDKILLER;
my $pidofchild = wait;
$numberOfChildren--;
delete $children{$pidofchild};
if (defined $DEBUG_PRINT_CHILDDEATHS) {
print "\t\tCHILDKILLER of child $pidofchild\n";
}
}
sub KILLALLCHILDREN # Child Cleanup
{
local($SIG{CHLD}) = 'IGNORE';
# kill our children
kill 'INT' => keys %children;
# and quit
exit;
}
sub makeChild
{
my $pid;
my $sigset;
# block signal for fork
$sigset = POSIX::SigSet->new(SIGINT);
sigprocmask(SIG_BLOCK, $sigset)
or die "can't block SIGINT for fork: $!\n";
#make child
die "fork: $!" unless defined ($pid = fork);
print "the pid of the fork is: $pid\n";
if ($pid) {
# parent stuff
sigprocmask(SIG_UNBLOCK, $sigset)
or die "cannot unblock SIGINT for fork: $!\n";
$children{$pid} = 1;
$MYPID = $pid;
if (defined $DEBUG_PRINT_CHILDBIRTHS) {
print "\t\tMAKING CHILD named $MYPID\n";
}
$numberOfChildren++;
#close($PROXYSERVER);
return;
} else {
# child. cannot return
$SIG{INT} = 'DEFAULT';
sigprocmask(SIG_UNBLOCK, $sigset)
or die "cannot unblock SIGINT for fork: $!\n";
# We dont want CHILD's accepting admin connections
#close($ADMINSERVER);
# handle connection yo!
for (my $i=0;$i<$MAX_CLIENTS_PER_CHILD;)
{
#
# Thanks Derek
#
my @sockets_can_read = $SOCKET_SELECT->can_read;
foreach my $currentval (@sockets_can_read) {
if ($currentval eq $PROXYSERVER) {
if ($PROXYCLIENT = $PROXYSERVER->accept()) {
&handleProxyRequest();
print "Closing PROXYCLIENT...\n";
close($PROXYCLIENT);
$i++;
}
}
elsif ($currentval eq $ADMINSERVER) {
if ($ADMINCLIENT = $ADMINSERVER->accept()) {
print "Doing admin\n";
&handleAdminClient();
print "Closing ADMINCLIENT...\n";
close ($ADMINCLIENT);
$i++;
}
}
}
# $PROXYCLIENT = $PROXYSERVER->accept() or last;
}
# we done, we kill kid
exit
}
}
sub handleProxyRequest
{
# my $CLIENT = shift @_;
print "requestStart - using $MYPID\n";
# read init headers
my $headers;
my $reading = 1;
my $headersize =0;
my $bytesread = 0;
my $dataread;
my $READBUFF = 10;
my $postLength;
while ($reading == 1) {
$bytesread = sysread ($PROXYCLIENT, $dataread, $BUFFERSIZE);
$headersize += $bytesread;
$headers .= $dataread;
if ($DEBUG_PRINT_HEADERS eq 'true') {
print "$dataread";
}
if ($bytesread < $BUFFERSIZE) {
$reading = 0;
}
}
# ============================================================================================================================
# Proxy Authentication
if ($USE_AUTHENTICATED_PROXY eq 'true') {
if ($headers =~ m/Proxy-Authorization/) {
my $stupid;
my $method;
my $userpasshash;
my $hackedHdrs = $headers;
$hackedHdrs =~ s/\r//g;
my @tempHdrs = split(/\n/,$hackedHdrs);
foreach my $line (@tempHdrs) {
if ($line =~ m/Proxy-Authorization/) {
($stupid,$method,$userpasshash) = split(/ /,$line);
}
}
($PROXY_USERNAME,$PROXY_PASSWORD) = split(/:/,&decode_base64($userpasshash));
if (defined $DEBUG_PRINT_PROXY_CREDENTIALS) {
print "Got Proxy Credentials: $PROXY_USERNAME\t$PROXY_PASSWORD\n";
}
&requestProxyAuthorisation and return unless &validProxyUser($PROXY_USERNAME,$PROXY_PASSWORD);
} else {
&requestProxyAuthorisation();
return;
}
}
# ============================================================================================================================
&doProxyRequest($headers);
}
sub doProxyRequest
{
my(@param) = @_;
# my $PROXIED_SELECT;
my @tempheaders1 = split(/\n/,$param[0]);
my($method,$url,$version) = split(/\s+/,(shift(@tempheaders1)));
# We need to get the details
#
# http://HOST:port/url?stuff
$url =~ s/^http:\/\///g;
# Change VERSION
$param[0] =~ s/HTTP\/1\.1/HTTP\/1\.0/g;
my($hostandport,$path) = split(/\//,$url);
# add the leading / back to path
$path = "/" . $path;
my ($host,$port) = split(/\:/,$hostandport);
if ($port eq '') { # non specified
$port = 80;
}
# print "host is $host\n";
# print "port is $port\n";
# print "path is $path\n";
# STRIP OUR PROXY AUTH OFF
if ($USE_AUTHENTICATED_PROXY eq 'true') {
$param[0] =~ eval "s/Proxy-Authorisation: Basic &encode_base64(\"$PROXY_USERNAME:$PROXY_PASSWORD\")\n//g";
}
# PARENT PROXY SUPPORT
if ($PARENT_PROXY_ENABLE eq 'true') {
print "Using Parent Proxy\n";
my $PARENT_PROXY_CREDENTIALS = &encode_base64("$PARENT_PROXY_USERNAME:$PARENT_PROXY_PASSWORD");
$PARENT_PROXY_CREDENTIALS =~ s/\n//g;
print "\n--: $PARENT_PROXY_CREDENTIALS :--\n";
$param[0] =~ s/HTTP\/1\.0\r\n/HTTP\/1\.0\r\nProxy-Authorization: Basic $PARENT_PROXY_CREDENTIALS\r\nConnection: close\r\n/g;
$host = $PARENT_PROXY_HOSTNAME;
$port = $PARENT_PROXY_PORT;
# $param[0] =~ s/\r\n\r\n/\r\nConnection: close\r\n\r\n/g;
}
if ($SSH_TUNNEL_ENABLE eq 'true') {
$host = $SSH_TUNNEL_LISTEN_HOST;
$port = $SSH_TUNNEL_LISTEN_PORT;
}
my $PROXIEDCONNECTION = IO::Socket::INET->new(
PeerAddr => $host,
PeerPort => $port,
Proto => "tcp",
Type => SOCK_STREAM )
or &createHtmlError("Cannot contact $host:$port
\n
\n $@\n") and return;
my $numberOfBytes = 0;
print "===============================================================================\n";
print " Sending only these headers!!!\n\n";
print $param[0];
print "===============================================================================\n";
print "(";
syswrite ($PROXIEDCONNECTION, $param[0]);
print ")";
# if (defined $DEBUG_USE_SELECT_STYLE_PROXY_CODE) {
# $PROXIED_SELECT = IO::Select->new($PROXIEDCONNECTION)
# or die "Can't create IO::Select object for PROXIEDCONNECTION";
# }
my $proxiedfile;
my $reading = 1;
my $READBUFF = 10;
my $bytesread = 0;
my $byteswrote = 0;
my $totalBytesRead = 0;
my $dataread;
my $cl;
my $i = 0;
my $stillInHeaders = 1;
# if (defined $DEBUG_USE_ORIGIGNAL_PROXY_CODE) {
while ($reading == 1) {
print "{";
$bytesread = sysread ($PROXIEDCONNECTION, $dataread, $BUFFERSIZE);
print "}";
$totalBytesRead += $bytesread;
$proxiedfile .= $dataread;
# I am better then andy
print "[";
$byteswrote = syswrite ($PROXYCLIENT, $dataread);
print "]";
# Only wanting to read headers
if ($DEBUG_PRINT_HEADERS eq 'true') {
# No bin data
my ($headers,$undesirable) = split(/.\n.\n/s,$dataread,2);
#print "$headers\n\n";
#print "$dataread\n";
}
if ($dataread eq '') {
$reading = 0;
}
# if ($totalBytesRead < $cl) {
# $reading = 1;
# } else {
# $reading = 0;
# }
# if ($bytesread < $BUFFERSIZE) {
# $reading = 0;
# }
#print "$MYPID - $i\n";
# $i++;
}
# $totalBytesRead = sysread ($PROXIEDCONNECTION, $dataread, $cl);
# print "\n$proxiedfile\n";
# print "fuckkick\n";
# print "$cl\n";
# print "$dataread";
# print "\n.fuckkick\n";
# $byteswrote = syswrite ($PROXYCLIENT, $dataread);
# while (<$REMOTE>) {
# $heh = $_;
# print $heh;
# print $CLIENT $heh;
# }
close($PROXIEDCONNECTION);
print "Closed connection to remote server\n";
# }
# if (defined $DEBUG_USE_SELECT_STYLE_PROXY_CODE) {
# my $shitkick = 1;
# while ($shitkick == 1) {
# foreach my $scon ($PROXIED_SELECT->can_read) {
# # There should be only one
#
# }
#
# }
##
# }
$proxiedfile =~ s/\r//g;
# print "===========\n";
# print $proxiedfile;
print "requestDone using $MYPID\t read $totalBytesRead bytes\n";
}
sub handleAdminClient
{
print "Handling admin connection\n";
# my $reading = 1;
# my $readdata;
# while ($reading == 1) {
# my $bytesread = sysread($ADMINCLIENT,$readdata,$BUFFERSIZE);
# if ($bytesread < $BUFFERSIZE) {
# $reading = 0;
# }
# }
# Because all comms is via ASCII terminated by EOL chars we can use Standard IO. Which makes it easy
my $input;
my $command;
my $params;
while ($input = <$ADMINCLIENT>) {
# print ">> $input\n";
$input =~ s/\:\:\r\n$//g;
# print ">>> $input\n";
($command,$params) = split(/::/,$input,2);
if (defined $DEBUG_SHOW_ADMIN_REQUEST_PAIRS) {
print "---- Pair is -----\n";
print $command . "\n";
print $params . "\n";
print "------------------\n";
}
if ($command =~ m/^CREATE/) {
print $ADMINCLIENT &addCustomer($params);
}
if ($command =~ m/^PASSWD/) {
print $ADMINCLIENT &setPassword($params);
}
if ($command =~ m/^REMOVE/) {
print $ADMINCLIENT &delCustomer($params);
}
if ($command =~ m/^RESET/) {
print $ADMINCLIENT &resetCustomer($params);
}
if ($command =~ m/^LIST/) {
print $ADMINCLIENT &listCustomers($params);
}
if ($command =~ m/^QUIT/) {
close ($ADMINCLIENT);
}
}
return;
}
sub validProxyUser
{ my @proxyCredentials = @_;
if (defined $DEBUG_PRINT_PROXY_AUTHENTICATIONS) {
print "Checking proxy credentials\n";
}
if (defined $DEBUG_USE_DEBUG_CREDENTIALS) {
if (($proxyCredentials[0] eq $DEBUG_PROXY_USERNAME) and ($proxyCredentials[1] eq $DEBUG_PROXY_PASSWORD)) {
$PROXY_USERNAME = $proxyCredentials[0];
$PROXY_PASSWORD = $proxyCredentials[1];
print "hacked auth\n";
return '1'; # true
}
}
# Using the database
if (&database_checkAuth($proxyCredentials[0],$proxyCredentials[1]) eq 'true') {
$PROXY_USERNAME = $proxyCredentials[0];
$PROXY_PASSWORD = $proxyCredentials[1];
return '1'; # true
}
return; # false
}
sub requestProxyAuthorisation
{
if (defined $DEBUG_PRINT_PROXY_AUTHENTICATIONS) {
print "Requesting Proxy Credentials\n";
}
my $request = "HTTP/1.0 407 Proxy Authentication Required\r\n";
$request .= 'Proxy-Authenticate: Basic realm="Enter your proxy username and password' . "\r\n";
$request .=<<"endofrequest";
While trying to retrieve the URL: http://www.google.com/
The following error was encountered:
Sorry, you are not currently allowed to request:
http://www.google.com/from this cache until you have authenticated yourself.
You need to use Netscape version 2.0 or greater, or Microsoft Internet Explorer 3.0, or an HTTP/1.1 compliant browser for this to work. Please contact the Helpdesk if you have difficulties authenticating yourself or change your default password.