#!/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"; ERROR: Cache Access Denied

ERROR

Cache Access Denied


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.



Generated Thu, 25 Apr 2002 01:11:38 GMT by proxy3.qut.edu.au (Squid/2.3.STABLE3) endofrequest syswrite($PROXYCLIENT,$request); } sub createHtmlError { my $errormsg = shift @_; print "Returning Error To Client: $errormsg\n"; my $clientErrorMessage = "HTTP/1.0 503 Service Unavailable\n"; $clientErrorMessage .= "Content-type: text/html\n\n"; $clientErrorMessage .=<<"endofclienterrormessage"; ERROR We had error: $errormsg endofclienterrormessage syswrite ($PROXYCLIENT,$clientErrorMessage); close $PROXYCLIENT; } sub parseConfigurationFile { # # DEPEND ON $CONFIGURATION_FILE # # # Could rewrite this so that it uses a regex over # the recieved line, discounting whitespace # and just splitting on =, then making the eval # gives a bit more flexibility. also, insert '' # where needed so we dont need them in text file # ie s/(\w+)/\'$1\'/g; # open(CONFIGFILE,'simpleserver.conf') or die "Can not open configuration file: $CONFIGURATION_FILE\n\t$!\n"; my $currentLine; my $i=1; foreach $currentLine () { if ($currentLine =~ m/^\#/) { # Comment } else { $currentLine =~ s/\n//g; my $eval = '$' . $currentLine . ';'; # Optimise this by evaling straight to the assignment operand eval $eval or die "Error processing configuration file at line: $i\n$currentLine\n\t$!\n"; } $i++; } close CONFIGFILE; } sub database_checkAuth { print "yada\n"; my %dbauth; my @auths = @_; tie %dbauth, "DB_File", $DB_AUTH or die "Can't access Authentication Database: $!\n"; if ($dbauth{$auths[0]} eq $auths[1]) { print "yidi\n"; return 'true'; } untie %dbauth; return; #false } #sub database_checkAuth #{ my %dbauth; # tie %dbauth, "DB_File", $DB_AUTH # or die "Can't access Authentication Database: $!\n"; # return ($dbauth{$_[0]} eq $_[1]) and untie %dbauth; #} sub userExists { my $user = shift @_; my %users; tie %users, "DB_File", $DB_AUTH or die "Can't access Authentication Database: $!\n"; my $return = exists $users{$user}; untie %users; return $return; # return (exists $users{$user}); } # ============================================================================================================================= # ADMIN CLIENT STUFF sub addCustomer { my $username = shift @_; if (&userExists($username)) { # Name Not Unique return ("STATUS::E0\n::\n"); } if ((length $username) > 25) { # Username Too Long return ("STATUS::E0\n::\n"); } if (not $username =~ m/^[\w \d]+$/) { # Only alphanumeric and spaces return ("STATUS::E0\n::\n"); } return ("STATUS::00\n::\n"); } sub setPassword # Should be only called after successful return of addCustomer { my %userdb; my %banddb; my ($user,$pass) = split(/::/,(shift @_),2); if (&userExists($user)) { # Name Not Unique return ("STATUS::E0\n::\n"); } if ((length $user) > 25) { # Username Too Long return ("STATUS::E0\n::\n"); } if (not $user =~ m/^[\w \d]+$/) { # Only alphanumeric and spaces return ("STATUS::E0\n::\n"); } if ((length $pass) != 8) { # Password not 8 chars return ("STATUS::E1\n::\n"); } tie %userdb, $DB_DRIVER, $DB_AUTH or return "INFO::Cannot Connect to User DB: $!\n::\n"; $userdb{$user} = $pass; untie %userdb; tie %banddb, $DB_DRIVER, $DB_BANDWIDTH or return "INFO::Cannot connect to Bandwidth DB: $!\n::\n"; $banddb{$user} = 0; untie %banddb; return ("STATUS::01\n::\n"); } sub delCustomer { my $customerToDelete = shift @_; my %userdb; my %banddb; if (!&userExists($customerToDelete)) { return ("INFO::User $customerToDelete not in list\n::\n"); } tie %userdb, $DB_DRIVER, $DB_AUTH or return "INFO::Cannot Connect to User DB: $!\n::\n"; delete $userdb{$customerToDelete}; untie %userdb; tie %banddb, $DB_DRIVER, $DB_BANDWIDTH or return "INFO::Cannot connect to Bandwidth DB: $!\n::\n"; delete $banddb{$customerToDelete}; untie %banddb; return "INFO::User $customerToDelete deleted\n::\n"; } sub resetCustomer { my $customerToReset = shift @_; my %banddb; if (!&userExists($customerToReset)) { return ("INFO::User $customerToReset not in list\n::\n"); } tie %banddb, $DB_DRIVER, $DB_BANDWIDTH or return "INFO::Cannot connect to Bandwidth DB: $!\n::\n"; $banddb{$customerToReset} = 0; untie %banddb; return ("INFO::Reset bytes to $customerToReset account. Current total 0 bytes\n::\n"); } sub listCustomers { my $customerToShow = shift @_; print "\n--> $customerToShow <--\n"; my %banddb; my %userdb; my $return = ''; if ($customerToShow ne '') { # Specific Customer if (!&userExists($customerToShow)) { return ("INFO::User Does Not Exist\n::\n"); } tie %userdb, $DB_DRIVER, $DB_AUTH or return "INFO::Cannot Connect to User DB: $!\n::\n"; tie %banddb, $DB_DRIVER, $DB_BANDWIDTH or return "INFO::Cannot connect to Bandwidth DB: $!\n::\n"; $return .= "User\t\t\tPass\t\tDownloaded\n"; $return .= "$customerToShow\t\t\t$userdb{$customerToShow}\t$banddb{$customerToShow}\n"; $return .= "::\n"; untie %userdb; untie %banddb; return $return; } else { # All Customers $return .= "User\t\t\tPass\t\tDownloaded\n"; tie %userdb, $DB_DRIVER, $DB_AUTH or return "INFO::Cannot Connect to User DB: $!\n::\n"; tie %banddb, $DB_DRIVER, $DB_BANDWIDTH or return "INFO::Cannot connect to Bandwidth DB: $!\n::\n"; foreach my $customer (keys %userdb) { $return .= "$customer\t\t\t$userdb{$customer}\t$banddb{$customer}\n"; } $return .= "::\n"; untie %userdb; untie %banddb; return $return; } } # =============================================================================================================================