{"id":140,"date":"2013-12-13T08:31:12","date_gmt":"2013-12-13T08:31:12","guid":{"rendered":"http:\/\/tastaturkind.ch\/?p=140"},"modified":"2013-12-13T08:31:12","modified_gmt":"2013-12-13T08:31:12","slug":"traverse-a-www-proxy-via-ssl-connect-with-basic-or-digest-authentication","status":"publish","type":"post","link":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/2013\/12\/13\/traverse-a-www-proxy-via-ssl-connect-with-basic-or-digest-authentication\/","title":{"rendered":"Traverse a www-proxy via SSL Connect with Basic or Digest authentication"},"content":{"rendered":"<p><code>#!\/usr\/bin\/perl<\/p>\n<p># Ross Lonstein <lonstein@bigfoot.com><br \/>\n# This program is free software; you can redistribute it and\/or<br \/>\n# modify it under the same terms as Perl itself.<\/p>\n<p>=head1 NAME<\/p>\n<p>tunnel-auth.pl - traverse a www-proxy via SSL Connect with Basic or Digest authentication.<\/p>\n<p>=head1 DESCRIPTION<\/p>\n<p>This non-forking script can be used to traverse a www-proxy that<br \/>\nsupports the HTTP CONNECT command. It negotiates HTTP authentication,<br \/>\nif necessary, then steps aside and acts as a simple port forwarder. It<br \/>\nreports a User-Agent string so that nothing suspicious appears in the<br \/>\nlogs.<\/p>\n<p>=cut<\/p>\n<p>use IO::Socket::INET;<br \/>\nuse IO::Select;<br \/>\nuse Getopt::Std;<br \/>\nuse strict;<\/p>\n<p>my $VERSION=0.01;<\/p>\n<p># check for MD5 support<br \/>\nmy $md5avail=1;<br \/>\neval {'use Digest::MD5;'};<br \/>\nif ($@) {<br \/>\n    $md5avail=0;<br \/>\n}<br \/>\nif ($md5avail) {<br \/>\n    print \"MD5 Avail. Enabling support for Digest Authentication\\n\";<br \/>\n    use Digest::MD5 qw(md5_base64);<br \/>\n}<\/p>\n<p>my ($dport,$dhost,$proxyhost,$proxyport);<br \/>\nmy ($remotehost,$remoteport,$auth,$useragent);<br \/>\nmy ($fhin,$fhout,$server,$proxy);<\/p>\n<p>#<br \/>\n# handle args<br \/>\n#<br \/>\nmy %opts;<br \/>\ngetopts('a:l:p:r:u:',\\%opts);<br \/>\nusage('Missing remote host:port') unless $opts{'r'};<br \/>\nusage('Missing local host:port')  unless $opts{'l'};<br \/>\nusage('Missing proxy host:port')  unless $opts{'p'};<\/p>\n<p>($proxyhost,$proxyport)=split(':', $opts{'p'});<br \/>\n($remotehost,$remoteport)=split(':', $opts{'r'});<\/p>\n<p>if ( $opts{'l'}=~\/:\/ ) {<br \/>\n    # use specific interface & port<br \/>\n    ($dhost,$dport) = split(':',$opts{'l'});<br \/>\n}<br \/>\nelse {<br \/>\n    # use localhost and specified port<br \/>\n    $dport=$opts{'l'};<br \/>\n    $dhost='127.0.0.1';<br \/>\n}<\/p>\n<p>$auth=$opts{'a'};<\/p>\n<p> # sample user agent identifiers:<br \/>\n #<br \/>\n #   Mozilla\/4.0 (compatible; MSIE 5.01; Windows NT)<br \/>\n #   Mozilla\/4.0 (compatible; MSIE 5.5; Windows NT 5.0)<br \/>\n #   Mozilla\/4.73 [en] (Win98; U)<br \/>\n #   Mozilla\/4.76 [en] (WinNT; U)<br \/>\n #   Mozilla\/4.7 [en] (X11; I; Linux 2.2.12-20 i686)<br \/>\n #   Mozilla\/4.5 (compatible; iCab Pre2.4; Macintosh; I; PPC)<br \/>\n #<br \/>\n # choose one appropriate for your environment so as not to<br \/>\n # arouse suspicion in the logs...<br \/>\n$useragent = $opts{'u'} || 'Mozilla\/4.0 (compatible; MSIE 5.01; Windows NT)';<\/p>\n<p>#<br \/>\n# Set up network communication<br \/>\n#<br \/>\nif ( $dport ) { # do \"daemon\" thing.<br \/>\n    $server = IO::Socket::INET-> new( Proto     => 'tcp',<br \/>\n                                      LocalAddr => $dhost,<br \/>\n                                      LocalPort => $dport,<br \/>\n                                      Listen    => SOMAXCONN,<br \/>\n                                      Type      => SOCK_STREAM,<br \/>\n                                      Reuse     => 1 )<br \/>\n            || die \"Error creating daemon socket: $!\";<br \/>\n    $fhin = $server->accept() || die \"Socket accept failed: $!\";<br \/>\n    $fhout = $fhin;<br \/>\n}<br \/>\nelse { # STDIN\/STDOUT used for ProxyCommand support<br \/>\n\t$fhin = \\*STDIN;<br \/>\n\t$fhout = \\*STDOUT;<br \/>\n}<\/p>\n<p>#<br \/>\n# connect to proxy server ...<br \/>\n#<br \/>\nmy %socket_hash=( PeerAddr => $proxyhost,<br \/>\n                  PeerPort => $proxyport,<br \/>\n                  Proto    => 'tcp',<br \/>\n                  Type     =>  SOCK_STREAM);<\/p>\n<p>$proxy = getPeerSocket( \\%socket_hash );<\/p>\n<p># Force flushing of socket buffers... (not necessary >5.005 on sockets? --rl)<br \/>\nforeach ( \\*$proxy, $fhin, $fhout ) {<br \/>\n\tselect($_);<br \/>\n    $|=1;<br \/>\n}<\/p>\n<p>#<br \/>\n# Negotiation with Proxy<br \/>\n#<\/p>\n<p>print $proxy \"CONNECT $remotehost:$remoteport HTTP\/1.0\\r\\n\";<br \/>\nprint $proxy \"User-Agent: $useragent\\r\\n\" if $useragent;<br \/>\nprint $proxy \"\\r\\n\";<\/p>\n<p># Wait for HTTP status code<br \/>\nmy $status;<br \/>\n($status) = (split(\/\\s+\/,<$proxy>))[1];<\/p>\n<p>#<br \/>\n# handle authenticating proxy<br \/>\n#<br \/>\nif ($status=407 && $auth) {<\/p>\n<p>    # skip lines waiting for type of authentication<br \/>\n    $_ = <$proxy> while ($_ =! \/Proxy-authenticate: (\\w+) \/);<br \/>\n    print STDERR \"Proxy authentication required...\";<\/p>\n<p>    # ignore rest, close connection and try again<br \/>\n    $proxy -> close() || die(\"Error closing proxy connection: $!\");<br \/>\n    print STDERR \"Closed proxy.\\n Reconnecting...\";<br \/>\n    $proxy = getPeerSocket( \\%socket_hash );<br \/>\n    print $proxy \"CONNECT $remotehost:$remoteport HTTP\/1.0\\r\\n\";<br \/>\n    print $proxy \"User-Agent: $useragent\\r\\n\" if $useragent;<\/p>\n<p>    # determine type of authentication...<br \/>\n    CASE: {<br \/>\n            auth_basic(),  last CASE     if $1 =~ \/basic\/i;<br \/>\n            auth_digest(), last CASE     if $1 =~ \/digest\/i && $md5avail;<br \/>\n            # add support for other auth schemes here...<br \/>\n            auth_unsupt(),  last CASE;<br \/>\n    }<\/p>\n<p>    print $proxy \"\\r\\n\";<\/p>\n<p>    # get new status<br \/>\n    ($status) = (split(\/\\s+\/,<$proxy>))[1];<br \/>\n}<\/p>\n<p>die \"Bad status code \\\"$status\\\" from proxy server.\"<br \/>\n    if ( int($status\/100) != 2 );<\/p>\n<p># Skip through remaining part of HTTP header (until blank line)<br \/>\n1 until ( <$proxy> =~ \/^[\\r\\n]+$\/ );<\/p>\n<p>#<br \/>\n# Shuffle packets between sockets<br \/>\n#<br \/>\nmy $s = IO::Select->new($fhin,\\*$proxy);<br \/>\nmy $num;<br \/>\nLOOP: for (;;) {<br \/>\n\tforeach my $fh ( $s->can_read(10) ) {<br \/>\n\t\tlast LOOP unless ( defined($num = sysread($fh,$_,4096)) );<br \/>\n\t\tlast LOOP if $num == 0;<br \/>\n\t\tlast LOOP unless ( defined(syswrite( ((fileno($fh)==fileno($fhin))?<br \/>\n\t\t\t$proxy:$fhout),$_,$num)) );<br \/>\n\t}<br \/>\n}<br \/>\n# good housekeeping seal...<br \/>\n$proxy -> close() || die \"Error closing connection to Proxy: $!\";<br \/>\n$server-> close() || die \"Error closing local connection: $!\";<br \/>\nexit 0;<\/p>\n<p>#<br \/>\n# SUBROUTINES<br \/>\n#<br \/>\nsub auth_basic {<br \/>\n    print STDERR \"using BASIC authentication.\\n\";<br \/>\n    print $proxy \"Proxy-Authorization: Basic \",encode_base64($auth),\"\\r\\n\";<br \/>\n}<\/p>\n<p>sub auth_digest {<br \/>\n    print STDERR \"using DIGEST authentication.\\n\";<\/p>\n<p>    # skip lines waiting for challenge<br \/>\n    $_ = <$proxy> while ($_ =~ \/digest\/i);<br \/>\n    my ($challenge) = (split(':',$_))[1];<\/p>\n<p>    # send response<br \/>\n    my ($user,$pass) = split(':',$auth);<br \/>\n    my $response=md5_base64(\"$user:$pass:$challenge\");<br \/>\n    print $proxy \"Proxy-Authorization:$response\\r\\n\";<br \/>\n}<\/p>\n<p>sub auth_unsupt {<br \/>\n    print STDERR \"Unsupportted authentication type '$1' requested!\\n\";<br \/>\n}<\/p>\n<p>sub getPeerSocket {<br \/>\n    my $hash_ref=shift;<br \/>\n    my $socket = IO::Socket::INET->new( PeerAddr    => $$hash_ref{'PeerAddr'},<br \/>\n                                        PeerPort    => $$hash_ref{'PeerPort'},<br \/>\n                                        Proto       => $$hash_ref{'Proto'} || 'tcp',<br \/>\n                                        Type        => $$hash_ref{'Type'} || SOCK_STREAM)<br \/>\n                               || die \"Socket setup failed: $!\";<br \/>\n    return $socket;<br \/>\n}<\/p>\n<p>sub encode_base64 {<br \/>\n# stolen from MIME::Base64, thanks Gisle Aas<br \/>\n# note we don't bother with requirement of line length < 76 chars\n    use integer;\n    my $res=\"\";\n    pos( $_[0] )=0;\n    while ( $_[0] =~ \/(.{1,45})\/gs ) {\n        $res .=substr( pack('u',$1), 1 );\n        chop($res);\n    }\n    $res =~ tr |` -_|AA-Za-z0-9+\/|;\n    my $padding = ( 3-length($_[0]) % 3) %3;\n    $res =~ s\/.{$padding}$\/'=' x $padding\/e if $padding;\n    return $res;\n}\n\nsub usage {\n    print \"\\n!! \",@_,\" !!\\n\";\n    print <<USAGE;\n\nUsage $0 -p <proxy>:<port> -l [<local>:]<port> -r <remote>:<port> [-a <proxyid:password>] [-u <user-agent>]<br \/>\n Connect to a remote host through a proxy supporting CONNECT.\n   <proxy>:<port>       --  ip or hostname and port of your http proxy<br \/>\n   <local>:<port>       --  port to listen on. ip\/hostname optional.<br \/>\n   <remote>:<port>      --  remote host and port (likely port 443\/563)\n   <proxyid>:<password> --  userid\/password for authenticating proxies<br \/>\n   <user-agent>         --  header string send to proxy. Defaults to<br \/>\n                            'Mozilla\/4.0 (compatible; MSIE 5.01; Windows NT)'<br \/>\nExample:<br \/>\n $0 -p proxy.example.com:8080 -l 2222 -r myhost.nowhere.com:443 \\<br \/>\n    -a joe:h4ckm3 -u 'Mozilla\/4.76 [en] (WinNT; U)'<\/p>\n<p>USAGE<br \/>\n    exit 1;<br \/>\n}<\/p>\n<p>=head1 README<\/p>\n<p>tunnel-auth.pl - traverse a www-proxy via SSL Connect with Basic or<br \/>\nDigest authentication.<\/p>\n<p>This script can be used to traverse a www-proxy that supports the HTTP<br \/>\nCONNECT command as is done for SSL. It negotiates HTTP authentication,<br \/>\nif necessary, then steps aside and acts as a simple port forwarder.<br \/>\nIt reports a User-Agent string during negotiation so that nothing<br \/>\nsuspicious appears in the proxy logs. Compatibility with Win32 was<br \/>\nretained by not forking, limiting it to a single connection per<br \/>\ninstance.<\/p>\n<p>Should work against any RFC-compliant proxy. Tested against:<br \/>\n    Netscape Enterprise\/3.52<br \/>\n    Squid 2.2<\/p>\n<p>Note: Properly configured proxies that allow CONNECT will only permit<br \/>\nconnection to standard SSL ports (443 and 563 according to squid). You<br \/>\ncan expect they will also have connection and idle timeout limits.<\/p>\n<p>=head2 EXAMPLE<\/p>\n<p>Assuming that you have a machine on the public internet accepting<br \/>\nSSH connections on port 443 you can do the following:<br \/>\n tunnel-auth.pl -p proxy.example.com:8080 \\<br \/>\n                -l 2222 -r myhost.nowhere.com:443 \\<br \/>\n                -a joe:h4ckm3 \\<br \/>\n                -u 'Mozilla\/4.76 [en] (WinNT; U)'<\/p>\n<p>And in another window:<br \/>\n ssh -p 2222 blockhead@localhost<\/p>\n<p>Which will, if all goes well, negotiate the proxy and connect to<br \/>\nthe remote machine via secure shell.<\/p>\n<p>=head1 PREREQUISITES<\/p>\n<p>Requires the C<IO::Socket::INET>, C<IO::Select>, C<Getopt:Std> modules.<\/p>\n<p>=head1 COREQUISITES<\/p>\n<p>Digest Authentication support uses C<Digest::MD5>.<\/p>\n<p>=head1 TODO<\/p>\n<p>In no particular order:<\/p>\n<p>- Logging. An ASCII\/HEX dump would be nice for diagnosis.<\/p>\n<p>- Forking for each connection now that Perl 5.6-Win32 supports it.<\/p>\n<p>- Support for NTLM authentication with Microsoft Proxy Server (that's a<br \/>\nhole with no bottom).<\/p>\n<p>=head1 AUTHOR<\/p>\n<p>Ross Lonstein <lonstein@bigfoot.com><\/p>\n<p>based upon work by Urban Kaveus, Theo Van Dinter, Gisle Aas, and<br \/>\nprobably others but don't contact them because the bugs are all mine.<\/p>\n<p>=head1 COPYRIGHT<\/p>\n<p>This program is free software; you can redistribute it and\/or modify it<br \/>\nunder the same terms as Perl itself.<\/p>\n<p>=pod OSNAMES<\/p>\n<p>any<\/p>\n<p>=pod SCRIPT CATEGORIES<\/p>\n<p>Networking<\/p>\n<p>=cut<br \/>\n<\/code><\/p>\n","protected":false},"excerpt":{"rendered":"<p>#!\/usr\/bin\/perl # Ross Lonstein # This program is free software; you can redistribute it and\/or # modify it under the same terms as Perl itself. =head1 NAME tunnel-auth.pl &#8211; traverse a www-proxy via SSL Connect with Basic or Digest authentication. &hellip; <a href=\"https:\/\/oliver-frick.ch\/wordpress\/index.php\/2013\/12\/13\/traverse-a-www-proxy-via-ssl-connect-with-basic-or-digest-authentication\/\">Weiterlesen <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-140","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/140","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/wp-json\/wp\/v2\/comments?post=140"}],"version-history":[{"count":0,"href":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/140\/revisions"}],"wp:attachment":[{"href":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=140"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=140"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oliver-frick.ch\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=140"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}