#!/usr/bin/perl # # proxy_over_ssl -- proxy just about anything over a generic ssl proxy # $rcsid = '$Id: proxy_over_ssl,v 1.14 2004/12/26 17:59:39 dgregor Exp $'; # # Copyright (c) 1998-2000 Daniel J. Gregor, Jr., All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. All advertising materials mentioning features or use of this software # must display the following acknowledgement: # This product includes software developed by Daniel J. Gregor, Jr. # 4. The name of Daniel J. Gregor, Jr. may not be used to endorse or promote # products derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY DANIEL J. GREGOR, JR. ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL DANIEL J. GREGOR, JR. BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # manual auth code by Karl N. Matthias -- 2000 $stdin = \*STDIN; $stdout = \*STDOUT; use IO::Socket; use IO::Select; use FileHandle; use POSIX; use Getopt::Std; $buffsize = 2048; $blockonwrite = 1; $progname = "proxy_over_ssl"; ($ver = $rcsid) =~ s/^.*\S+,v\s+([0-9.]+)\s+.*$/$1/; $usage = << "EOF"; usage: $progname [-a ] [-u ] [-w ] [-W ] [-H ] [-P ] [] $progname -h Display help information $progname -v Display version information EOF $opt_H = "proxy"; $opt_P = 8080; $opt_v = undef; # verbose $opt_d = undef; # debug $opt_q = undef; # quiet $opt_A = undef; # proxy authorization $opt_a = "$progname $ver"; # User-Agent $opt_u = (getpwuid(geteuid()))[0]; # default proxy auth username $opt_w = undef; # proxy auth password (insecure! use -W , instead) $opt_W = undef; Getopt::Std::getopt('APHpauwW'); # || die $usage; if (defined($opt_h)) { die $usage; } if (defined($opt_v)) { print "$progname: $rcsid\n"; exit 0; } if (@ARGV != 1 && @ARGV != 2) { die "$progname: invalid number of arguments\n$usage\n"; } $host = shift(@ARGV); if (@ARGV > 0) { $port = shift(@ARGV); } else { $port = 80; } if (defined($opt_w) && defined($opt_W)) { die "$progname: the options -w and -W are mutually exclusive\n$usage\n"; } if (defined($opt_W)) { if (!defined($passwd_file = new FileHandle($opt_W, "r"))) { die "$progname: Can't open file $opt_W for reading: $!\n"; } $opt_w = <$passwd_file>; chomp($opt_w); $passwd_file->close(); } # Call the routine to try the proxy $status = &ProxyConnect(\$proxy); print(STDERR "Connected to endpoint host $host:$port...\n") unless $opt_q; LOSEHEADERS: while (defined($status = $proxy->getline())) { $status =~ s/[\n\r]+//g; last LOSEHEADERS if $status =~ m/^$/; } fcntl($proxy, F_SETFL(), O_NONBLOCK()); fcntl($stdin, F_SETFL(), O_NONBLOCK()); fcntl($stdout, F_SETFL(), O_NONBLOCK()); $read_set = new IO::Select; $read_set->add($proxy); $read_set->add($stdin); $write_set = new IO::Select; $stdinbuf = ""; $proxybuf = ""; while (1) { ($r_ready, $w_ready) = IO::Select->select($read_set, $write_set, undef, undef); foreach $sock (@$r_ready) { if ($sock == $proxy) { if (!sysread($proxy, $proxybuf, $buffsize)) { die "Connection closed by remote host: $!\n" unless !$proxy->eof; die "Error reading from proxy socket: $!\n" unless $! eq "Resource temporarily unavailable"; } if (length($proxybuf) > 0 && !syswrite($stdout, $proxybuf)) { die "Error writing to stdout: $!\n" unless $! eq "Resource temporarily unavailable"; if ($blockonwrite) { # screw this, block until we can write! warn "Setting non-blocking IO on STDOUT\n" if $opt_v; fcntl($stdout, F_SETFL(), ~O_NONBLOCK()); if (!syswrite($stdout, $proxybuf)) { die "Error writing to stdout: $!\n"; } warn "Clearing non-blocking IO on STDOUT\n" if $opt_v; fcntl($stdout, F_SETFL(), O_NONBLOCK()); } else { # Remove proxy from the $read_set list, so # that we don't overwrite $proxybuf # We'll add it later. ;-0 warn "Removing PROXY from read_set\n" if $opt_v; $read_set->remove($proxy); $write_set->add($stdout); } } } if ($sock == $stdin) { if (!sysread($stdin, $stdinbuf, $buffsize)) { if (eof($stdin)) { print(STDERR "STDIN closed by local host\n") if $opt_v; exit(0); } die "Error reading from STDIN socket: $!\n" unless $! eq "Resource temporarily unavailable"; } if (length($stdinbuf) > 0 && !syswrite($proxy, $stdinbuf)) { die "Error writing to proxy: $!\n" unless $! eq "Resource temporarily unavailable"; if ($blockonwrite) { # screw this, block until we can write! warn "Setting non-blocking IO on PROXY\n" if $opt_v; fcntl($proxy, F_SETFL(), ~O_NONBLOCK()); if (!syswrite($proxy, $stdinbuf)) { die "Error writing to proxy: $!\n"; } warn "Clearing non-blocking IO on PROXY\n" if $opt_v; fcntl($proxy, F_SETFL(), O_NONBLOCK()); } else { # Remove STDIN from the $read_set list, so # that we don't overwrite $stdinbuf # We'll add it later. ;-0 warn "Removing STDIN from read_set\n" if $opt_v; $read_set->remove($stdin); $write_set->add($proxy); } } } } foreach $sock (@$w_ready) { if ($sock == $stdout) { if (!syswrite($stdout, $proxybuf, length($proxybuf))) { die "Error writing to stdout: $!\n" unless $! eq "Resource temporarily unavailable"; # screw this, block until we can write! fcntl($stdout, F_SETFL(), ~O_NONBLOCK()); if (!syswrite($stdout, $proxybuf, length($proxybuf))) { die "Error writing to stdout: $!\n"; } fcntl($stdout, F_SETFL(), O_NONBLOCK()); } else { warn("Adding PROXY to read_set\n") if $opt_v; $read_set->add($proxy); $write_set->remove($stdout); } } if ($sock == $stdin) { if (!syswrite($proxy, $stdinbuf)) { die "Error writing to proxy: $!\n" unless $! eq "Resource temporarily unavailable"; # screw this, block until we can write! fcntl($proxy, F_SETFL(), ~O_NONBLOCK()); if (!syswrite($proxy, $stdinbuf, length($stdinbuf))) { die "Error writing to proxy: $!\n"; } fcntl($proxy, F_SETFL(), O_NONBLOCK()); } else { warn("Adding STDIN to read_set\n") if $opt_v; $read_set->add($stdin); $write_set->remove($proxy); } } } } # b64encode hacked from: # base64.pl -- A perl package to handle MIME-style BASE64 encoding # A. P. Barrett , October 1993 # $Revision: 1.14 $$Date: 2004/12/26 17:59:39 $ # Second-hand hacked from Deej's ez-ip.pl script since he did all the # work of modularizing it. :) --karmat sub b64encode { local ($_) = @_; local ($chunk); local ($result); $base64_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. 'abcdefghijklmnopqrstuvwxyz'. '0123456789+/'; $base64_pad = '='; $uuencode_alphabet = q|`!"#$%&'()*+,-./0123456789:;<=>?|. '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_'; # double that '\\'! # Build some strings for use in tr/// commands. # Some uuencodes use " " and some use "`", so we handle both. # We also need to protect backslashes. ($tr_uuencode = " ".$uuencode_alphabet) =~ s/\\/\\\\/; $tr_base64 = "A".$base64_alphabet; # break into chunks of 45 input chars, use perl's builtin # uuencoder to convert each chunk to uuencode format, # then kill the leading "M", translate to the base64 alphabet, # and finally append a newline. while (s/^((.|\n){45})//) { #warn "in:$&:\n"; $chunk = substr(pack("u", $&), $[+1, 60); #warn "packed :$chunk:\n"; eval qq{ \$chunk =~ tr|$tr_uuencode|$tr_base64|; }; #warn "translated:$chunk:\n"; $result .= $chunk . "\n"; } # any leftover chars go onto a shorter line # with uuencode padding converted to base64 padding if ($_ ne "") { #warn "length ".length($_)." \$_:$_:\n"; #warn "enclen ", int((length($_)+2)/3)*4 - (45-length($_))%3, "\n"; $chunk = substr(pack("u", $_), $[+1, int((length($_)+2)/3)*4 - (45-length($_))%3); #warn "chunk:$chunk:\n"; eval qq{ \$chunk =~ tr|$tr_uuencode|$tr_base64|; }; #warn "translated:$chunk:\n"; $result .= $chunk . ($base64_pad x ((60 - length($chunk)) % 4)) . "\n"; } # return result $result; } # Go about actually connecting to the proxy sub ProxyConnect { my($junk) = @_; local $status; $proxy = \$junk; # first, we build the proxy authentication string if we were provided # with authentication information if (defined($opt_w)) { $proxy_auth = "Basic " . &b64encode($opt_u . ":" . $opt_w); chomp($proxy_auth); } print(STDERR "Trying proxy at $opt_H:$opt_P...\n") unless $opt_q; $proxy = new IO::Socket::INET ( PeerAddr => $opt_H, PeerPort => $opt_P, Proto => 'tcp', ); die "Error connecting to proxy at $opt_H:$opt_P: $!\n" unless $proxy; $proxy->autoflush(1); print(STDERR "Connected to proxy at $opt_H:$opt_P.\n") unless $opt_q; $proxy->print("CONNECT $host:$port HTTP/1.0\r\n"); print(STDERR "CONNECT $host:$port HTTP/1.0\r\n") if defined($opt_d); $proxy->print("Proxy-authorization: $proxy_auth\r\n") if defined($proxy_auth); print(STDERR "Proxy-authorization: $proxy_auth\r\n") if defined($proxy_auth) && defined($opt_d); $proxy->print("User-Agent: $opt_a\r\n") if defined($opt_a); print(STDERR "User-Agent: $opt_a\r\n") if defined($opt_a) && defined($opt_d); $proxy->print("\r\n"); # end of headers $status = $proxy->getline(); $status =~ s/[\n\r]+//g; if (!($status =~ m?^HTTP/1\.. 200?)) { if(($status =~ m?^HTTP/1\.. 407?) or ($status =~ m?^HTTP/1\.. 401?)) { # Open the tty so that we can get input and output from # the terminal itself script. Gets around ssh having # control of the terminal when we are called as a proxy # for ssh. $ttyout = new FileHandle (">/dev/tty") or die "$0: Can't open your TTY for writing: $!\n"; $ttyin = new FileHandle ("print($msg); $ttyout->autoflush(1); # Required or doesn't work system("stty -echo < /dev/tty"); $mypass = $ttyin->getline(); system("stty echo < /dev/tty"); chop($mypass); # erase the message that we printed $ttyout->print("\b \b" x length($msg)); $ttyout->close(); $ttyin->close(); $opt_w = $mypass; $proxy->close(); &ProxyConnect(\$proxy); } elsif ($opt_v) { die "$progname: Error from proxy: \"$status\"\n" . join("", $proxy->getlines) . "\n"; } else { die "$progname: Error from proxy: \"$status\"\n"; } } return $status; }