#!/usr/bin/perl -w use strict; no warnings; $ENV{PATH} = join ":", qw(/usr/ucb /bin /usr/bin); $|++; use Cwd qw(abs_path); my $VERSION_ID = q$Id: proxy,v 1.21 1998/xx/xx xx:xx:xx merlyn Exp $; my $VERSION = (qw$Revision: 1.21 $ )[-1]; ## Copyright (c) 1996, 1998 by Randal L. Schwartz ## This program is free software; you can redistribute it ## and/or modify it under the same terms as Perl itself. ### debug management sub prefix { my $now = localtime; join "", map { "[$now] [${$}] $_\n" } split /\n/, join "", @_; } $SIG{__WARN__} = sub { warn prefix @_ }; $SIG{__DIE__} = sub { die prefix @_ }; &setup_signals(); ### logging flags my $LOG_PROC = 1; # begin/end of processes my $LOG_TRAN = 1; # begin/end of each transaction my $LOG_REQ_HEAD = 1; # detailed header of each request my $LOG_REQ_BODY = 1; # header and body of each request my $LOG_RES_HEAD = 1; # detailed header of each response my $LOG_RES_BODY = 1; # header and body of each response my $LWP_DEBUG = 0; # set on full LWP Debuging ### configuration my $HOST = $ARGV[0] || '127.0.0.1'; my $PORT = $ARGV[1] || 8080; # pick next available user-port my $SLAVE_COUNT = 2; # how many slaves to fork my $MAX_PER_SLAVE = 20; # how many transactions per slave my $CERT = abs_path(".")."/certs/cert.pem"; die "cant locate SSL certificate file: $CERT - $!\n" unless -f $CERT; my $KEY = abs_path(".")."/certs/key.pem"; die "cant locate SSL key file: $KEY - $!\n" unless -f $KEY; ### main warn("running version ", $VERSION); #MyHTTP::Daemon::ClientConn::import(); # force the fixing of the meths &main(); exit 0; ### subs sub main { # return void use HTTP::Daemon; my %kids; my $master = HTTP::Daemon->new(LocalPort => $PORT, LocalAddr => $HOST) or die "Cannot create master: $!"; warn("master is ", $master->url); ## fork the right number of children for (1..$SLAVE_COUNT) { $kids{&fork_a_slave($master)} = "slave"; } { # forever: my $pid = wait; my $was = delete ($kids{$pid}) || "?unknown?"; warn("child $pid ($was) terminated status $?") if $LOG_PROC; if ($was eq "slave") { # oops, lost a slave sleep 1; # don't replace it right away (avoid thrash) $kids{&fork_a_slave($master)} = "slave"; } } continue { redo }; # semicolon for cperl-mode } sub setup_signals { # return void setpgrp; # I *am* the leader $SIG{HUP} = $SIG{INT} = $SIG{TERM} = sub { my $sig = shift; $SIG{$sig} = 'IGNORE'; kill $sig, 0; # death to all-comers die "killed by $sig"; }; } sub fork_a_slave { # return int (pid) my $master = shift; # HTTP::Daemon my $pid; defined ($pid = fork) or die "Cannot fork: $!"; &child_does($master) unless $pid; $pid; } sub child_does { # return void my $master = shift; # HTTP::Daemon my $did = 0; # processed count warn("child started") if $LOG_PROC; { flock($master, 2); # LOCK_EX warn("child has lock") if $LOG_TRAN; my $slave = $master->accept or die "accept: $!"; warn("child releasing lock") if $LOG_TRAN; flock($master, 8); # LOCK_UN my @start_times = (times, time); $slave->autoflush(1); warn("connect from ", $slave->peerhost) if $LOG_TRAN; &handle_one_connection($slave); # closes $slave at right time if ($LOG_TRAN) { my @finish_times = (times, time); for (@finish_times) { $_ -= shift @start_times; # crude, but effective } warn(sprintf "times: %.2f %.2f %.2f %.2f %d\n", @finish_times); } } continue { redo if ++$did < $MAX_PER_SLAVE }; warn("child terminating") if $LOG_PROC; exit 0; } my $password = ""; sub callback { return $password; } sub handle_one_connection { # return void use HTTP::Request; use IO::Socket::SSL;# qw(debug4); my $handle = shift; # HTTP::Daemon::ClientConn my $request = $handle->get_request; defined($request) or die "bad request"; # XXX if ( $request->method() =~ /CONNECT/ ){ my $response = new HTTP::Response; $response->protocol("HTTP/1.0"); $response->code("200"); $response->message("Connection established"); $response->header('Proxy-agent' => 'Apache/1.3.x (Unix)'); $response->request($request); $handle->send_response($response); my $myurl = $request->url; my $s = IO::Socket::SSL::socket_to_SSL($handle, SSL_server => 1, SSL_key_file => $KEY, SSL_cert_file => $CERT, ); bless($handle, "MyHTTP::Daemon::ClientConn"); # allow HTTP::Daemon to reprocess ${*$handle}{'httpd_nomore'} = undef; $request = $handle->get_request(undef, $myurl); return unless $request; # Client does a reconnect here $request->uri($myurl.$request->uri()); } if (my ($req, $q) = $request->uri() =~ /^http:\/\/(.*?)(:443.*?)$/ ){ $request->uri("https://$req".$q); } my $response = &fetch_request($request); warn "response code:".$response->code()."\n"; warn "response message:".$response->message()."\n\n\n\n\n"; warn("response: <<<\n", $response->headers_as_string, "\n>>>") if $LOG_RES_HEAD and not $LOG_RES_BODY; warn("response: <<<\n", $response->as_string, "\n>>>") if $LOG_RES_BODY; $handle->send_response($response); close $handle; } sub fetch_request { # return HTTP::Response use HTTP::Response; my $request = shift; # HTTP::Request ## XXXX needs policy here my $url = $request->url; if ($url->scheme !~ /^(https?|gopher|ftp)$/) { warn "problem with request type: ".$url->scheme()."\n"; my $res = HTTP::Response->new(403, "Forbidden"); $res->content("bad scheme: @{[$url->scheme]}\n"); $res; # } elsif (not $url->rel("$url")->netloc) { # } elsif (not $url->rel("$url")->authority) { # my $res = HTTP::Response->new(403, "Forbidden"); # $res->content("relative URL not permitted\n"); # $res; } else { ## validated request, get it! warn("processing url is $url") if $LOG_TRAN; &fetch_validated_request($request); } } BEGIN { # local static block my $agent; # LWP::UserAgent sub fetch_validated_request { # return HTTP::Response my $request = shift; # HTTP::Request $agent ||= do { use LWP::UserAgent; # enable LWP Debuging if ($LWP_DEBUG){ require LWP::Debug; LWP::Debug::level('+'); } my $agent = LWP::UserAgent->new; $agent->agent("proxy/$VERSION " . $agent->agent); $agent->env_proxy; $agent; }; warn("fetch: <<<\n", $request->headers_as_string, "\n>>>") if $LOG_REQ_HEAD and not $LOG_REQ_BODY; warn("fetch: <<<\n", $request->as_string, "\n>>>") if $LOG_REQ_BODY; my $response = $agent->simple_request($request); if ($response->is_success and $response->content_type =~ /text\/(plain|html)/ and not ($response->content_encoding || "") =~ /\S/ and ($request->header("accept-encoding") || "") =~ /gzip/) { require Compress::Zlib; my $content = $response->content; my $new_content = Compress::Zlib::memGzip($content); if (defined $new_content) { $response->content($new_content); $response->content_length(length $new_content); $response->content_encoding("gzip"); warn("gzipping content from ". (length $content)." to ". (length $new_content)) if $LOG_TRAN; } } $response; } } # Alternative connection object for SSL package MyHTTP::Daemon::ClientConn; use base qw(IO::Socket::SSL); use vars qw($DEBUG); *DEBUG = \$HTTP::Daemon::DEBUG; use HTTP::Request (); use HTTP::Response (); use HTTP::Status; use HTTP::Date qw(time2str); use LWP::MediaTypes qw(guess_media_type); use Carp (); my $CRLF = "\015\012"; # "\r\n" is not portable my $HTTP_1_0 = _http_version("HTTP/1.0"); my $HTTP_1_1 = _http_version("HTTP/1.1"); # This is a kind of psuedo inheritence # I wanted to inherit from HTTP::Daemon::ClientConn # but I wanted it to inherit from IO::Socket::SSL use vars qw($AUTOLOAD); my $caller = __PACKAGE__; sub AUTOLOAD { $AUTOLOAD =~ s/^.*:://; no strict 'refs'; # make sure the subroutine exists to alias if ( *{"HTTP::Daemon::ClientConn::${AUTOLOAD}"}{CODE} ){ *{"${caller}::${AUTOLOAD}"} = \&{"HTTP::Daemon::ClientConn::${AUTOLOAD}"}; goto &{"${caller}::${AUTOLOAD}"}; } else { die "HTTP::Daemon::ClientConn does not contain ${AUTOLOAD} as a subroutine\n"; } } 1;