##############################################################################
#
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Library General Public
#  License as published by the Free Software Foundation; either
#  version 2 of the License, or (at your option) any later version.
#
#  This library is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Library General Public License for more details.
#
#  You should have received a copy of the GNU Library General Public
#  License along with this library; if not, write to the
#  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA  02111-1307, USA.
#
#  Copyright (C) 2003 Ryan Eatmon
#
##############################################################################
package IO::Socket::Socks;

=head1 NAME

IO::Socket::Socks

=head1 SYNOPSIS

Provides a way to open a connection to a SOCKS v5 proxy and use the object
just like an IO::Socket.

=head1 DESCRIPTION

IO::Socket::Socks connects to a SOCKS v5 proxy, tells it to open a
connection to a remote host/port when the object is created.  The
object you receive can be used directly as a socket for sending and
receiving data from the remote host.

=head1 EXAMPLES

=head2 Client

use IO::Socket::Socks;

my $socks = new IO::Socket::Socks(ProxyAddr=>"proxy host",
                                  ProxyPort=>"proxy port",
                                  ConnectAddr=>"remote host",
                                  ConnectPort=>"remote port",
                                 );

print $socks "foo\n";

$socks->close();

=head2 Server

use IO::Socket::Socks;

my $socks_server = new IO::Socket::Socks(ProxyAddr=>"localhost",
                                         ProxyPort=>"8000",
                                         Listen=>1,
                                         UserAuth=>\&auth,
                                         RequireAuth=>1
                                        );

my $select = new IO::Select($socks_server);
        
while(1)
{
    if ($select->can_read())
    {
        my $client = $socks_server->accept();

        if (!defined($client))
        {
            print "ERROR: $SOCKS_ERROR\n";
            next;
        }

        my $command = $client->command();
        if ($command->[0] == 1)  # CONNECT
        {
            # Handle the CONNECT
            $client->command_reply(0, addr, port);
        }
        
        ...
        #read from the client and send to the CONNECT address
        ...

        $client->close();
    }
}
        

sub auth
{
    my $user = shift;
    my $pass = shift;

    return 1 if (($user eq "foo") && ($pass eq "bar"));
    return 0;
}


=head1 METHODS

=head2 new( %cfg )

Creates a new IO::Socket::Socks object.  It takes the following
config hash:

  ProxyAddr => Hostname of the proxy

  ProxyPort => Port of the proxy
  
  ConnectAddr => Hostname of the remote machine

  ConnectPort => Port of the remote machine

  AuthType => What kind of authentication to support:
                none       - no authentication (default)
                userpass  - Username/Password

  RequireAuth => Do not send, or accept, ANON as a valid
                 auth mechanism.

  UserAuth => Function that takes ($user,$pass) and returns
              1 if they are allowed, 0 otherwise.

  Username => If AuthType is set to userpass, then you must
              provide a username.

  Password => If AuthType is set to userpass, then you must
              provide a password.
              
  SocksDebug => This will cause all of the SOCKS traffic to
                be presented on the command line in a form
                similar to the tables in the RFCs.

  Listen => 0 or 1.  Listen on the ProxyAddr and ProxyPort
            for incoming connections.

=head2 accept( )

Accept an incoming connection and return a new IO::Socket::Socks
object that represents that connection.  You must call command()
on this to find out what the incoming connection wants you to do,
and then call command_reply() to send back the reply.

=head2 command( )

After you call accept() the client has sent the command they want
you to process.  This function returns a reference to an array with
the following format:

  [ COMMAND, HOST, PORT ]

=head2 command_reply( REPLY CODE, HOST, PORT )

After you call command() the client needs to be told what the result
is.  The REPLY CODE is as follows (integer value):

  0: Success
  1: General Failure
  2: Connection Not Allowed
  3: Network Unreachable
  4: Host Unreachable
  5: Connection Refused
  6: TTL Expired
  7: Command Not Supported
  8: Address Not Supported

HOST and PORT are the resulting host and port that you use for the
command.

=head1 VARIABLES

=head2 $SOCKS_ERROR

This scalar behaves like $! in that if undef is returned, this variable
should contain a string reason for the error.

=head1 AUTHOR

Ryan Eatmon

=head1 COPYRIGHT

This module is free software, you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

#XXX document socks5 rfcs
#XXX document SOCKS_ERROR

use strict;
use IO::Socket;
use Carp;
use base qw( IO::Socket::INET );
use vars qw(@ISA @EXPORT $VERSION %CODES );
require Exporter;
@ISA = qw(Exporter IO::Socket::INET);
@EXPORT = qw( $SOCKS_ERROR );

$VERSION = "0.1";
our $SOCKS_ERROR;

use constant SOCKS5_VER =>  5;

use constant ADDR_IPV4       => 1;
use constant ADDR_DOMAINNAME => 3;
use constant ADDR_IPV6       => 4;

use constant CMD_CONNECT  => 1;
#use constant CMD_BIND     => 2;
#use constant CMD_UDPASSOC => 3;

use constant AUTHMECH_ANON     => 0;
#use constant AUTHMECH_GSSAPI   => 1;
use constant AUTHMECH_USERPASS => 2;
use constant AUTHMECH_INVALID  => 255;

$CODES{AUTHMECH}->[AUTHMECH_INVALID] = "No valid auth mechanisms";

use constant AUTHREPLY_SUCCESS  => 0;
use constant AUTHREPLY_FAILURE  => 1;

$CODES{AUTHREPLY}->[AUTHREPLY_FAILURE] = "Failed to authenticate";

use constant REPLY_SUCCESS             => 0;
use constant REPLY_GENERAL_FAILURE     => 1;
use constant REPLY_CONN_NOT_ALLOWED    => 2;
use constant REPLY_NETWORK_UNREACHABLE => 3;
use constant REPLY_HOST_UNREACHABLE    => 4;
use constant REPLY_CONN_REFUSED        => 5;
use constant REPLY_TTL_EXPIRED         => 6;
use constant REPLY_CMD_NOT_SUPPORTED   => 7;
use constant REPLY_ADDR_NOT_SUPPORTED  => 8;

$CODES{REPLY}->[REPLY_SUCCESS] = "Success";
$CODES{REPLY}->[REPLY_GENERAL_FAILURE] = "General failure";
$CODES{REPLY}->[REPLY_CONN_NOT_ALLOWED] = "Not allowed";
$CODES{REPLY}->[REPLY_NETWORK_UNREACHABLE] = "Network unreachable";
$CODES{REPLY}->[REPLY_HOST_UNREACHABLE] = "Host unreachable";
$CODES{REPLY}->[REPLY_CONN_REFUSED] = "Connection refused";
$CODES{REPLY}->[REPLY_TTL_EXPIRED] = "TTL expired";
$CODES{REPLY}->[REPLY_CMD_NOT_SUPPORTED] = "Command not supported";
$CODES{REPLY}->[REPLY_ADDR_NOT_SUPPORTED] = "Address not supported";


#------------------------------------------------------------------------------
# sub new is handled by IO::Socket::INET
#------------------------------------------------------------------------------

###############################################################################
#
# configure - read in the config hash and populate the object.
#
###############################################################################
sub configure
{
    my $self = shift;
    my $args = shift;

    ${*$self}->{SOCKS}->{ProxyAddr} =
        (exists($args->{ProxyAddr}) ?
         delete($args->{ProxyAddr}) :
         croak("You must provide a ProxyAddr to either connect to, or listen on.")
        );

    ${*$self}->{SOCKS}->{ProxyPort} =
        (exists($args->{ProxyPort}) ?
         delete($args->{ProxyPort}) :
         croak("You must provide a ProxyPort to either connect to, or listen on.")
        );

    ${*$self}->{SOCKS}->{ConnectAddr} =
        (exists($args->{ConnectAddr}) ?
         delete($args->{ConnectAddr}) :
         undef
        );

    ${*$self}->{SOCKS}->{ConnectPort} =
        (exists($args->{ConnectPort}) ?
         delete($args->{ConnectPort}) :
         undef
        );
    
    #${*$self}->{SOCKS}->{BindAddr} =
    #    (exists($args->{BindAddr}) ?
    #     delete($args->{BindAddr}) :
    #     undef
    #    );

    #${*$self}->{SOCKS}->{BindPort} =
    #    (exists($args->{BindPort}) ?
    #     delete($args->{BindPort}) :
    #     undef
    #    );

    ${*$self}->{SOCKS}->{AuthType} =
        (exists($args->{AuthType}) ?
         delete($args->{AuthType}) :
         "none"
        );
    
    ${*$self}->{SOCKS}->{RequireAuth} =
        (exists($args->{RequireAuth}) ?
         delete($args->{RequireAuth}) :
         0
        );
    
    ${*$self}->{SOCKS}->{UserAuth} =
        (exists($args->{UserAuth}) ?
         delete($args->{UserAuth}) :
         undef
        );
    
    ${*$self}->{SOCKS}->{Username} =
        (exists($args->{Username}) ?
         delete($args->{Username}) :
         ((${*$self}->{SOCKS}->{AuthType} eq "none") ?
           undef :
           croak("If you set AuthType to userpass, then you must provide a username.")
         )
        );
    
    ${*$self}->{SOCKS}->{Password} =
        (exists($args->{Password}) ?
         delete($args->{Password}) :
         ((${*$self}->{SOCKS}->{AuthType} eq "none") ?
           undef :
           croak("If you set AuthType to userpass, then you must provide a password.")
         )
        );
    
    ${*$self}->{SOCKS}->{Debug} =
        (exists($args->{SocksDebug}) ?
         delete($args->{SocksDebug}) :
         0
        );
    
    ${*$self}->{SOCKS}->{AuthMethods} = [0,0,0];
    ${*$self}->{SOCKS}->{AuthMethods}->[AUTHMECH_ANON] = 1
        unless ${*$self}->{SOCKS}->{RequireAuth};
    #${*$self}->{SOCKS}->{AuthMethods}->[AUTHMECH_GSSAPI] = 1
    #    if (${*$self}->{SOCKS}->{AuthType} eq "gssapi");
    ${*$self}->{SOCKS}->{AuthMethods}->[AUTHMECH_USERPASS] = 1
        if ((!exists($args->{Listen}) &&
            (${*$self}->{SOCKS}->{AuthType} eq "userpass")) ||
            (exists($args->{Listen}) &&
            defined(${*$self}->{SOCKS}->{UserAuth})));
    
    ${*$self}->{SOCKS}->{COMMAND} = undef;

    if (exists($args->{Listen}))
    {
        $args->{LocalAddr} = ${*$self}->{SOCKS}->{ProxyAddr};
        $args->{LocalPort} = ${*$self}->{SOCKS}->{ProxyPort};
        $args->{Reuse} = 1;
    }
    else
    {
        $args->{PeerAddr} = ${*$self}->{SOCKS}->{ProxyAddr};
        $args->{PeerPort} = ${*$self}->{SOCKS}->{ProxyPort};
    }

    $args->{Proto} = "tcp";
    $args->{Type} = SOCK_STREAM;

    my $status = $self->SUPER::configure($args);
    return unless $status;

    #--------------------------------------------------------------------------
    # We are configured... Return the object.
    #--------------------------------------------------------------------------
    return $status;
}




###############################################################################
#+-----------------------------------------------------------------------------
#| Connect Functions
#+-----------------------------------------------------------------------------
###############################################################################

###############################################################################
#
# connect - On a configure, connect is called to open the connection.  When
#           we do this we have to talk to the SOCKS5 proxy, log in, and
#           connect to the remote host.
#
###############################################################################
sub connect
{
    my $self = shift;

    croak("Undefined IO::Socket::Socks object passed to connect.")
        unless defined($self);

    #--------------------------------------------------------------------------
    # Establish a connection
    #--------------------------------------------------------------------------
    $self = $self->SUPER::connect(@_);

    if (!$self)
    {
        $SOCKS_ERROR = "Connection to proxy failed.";
        return;
    }

    #--------------------------------------------------------------------------
    # Handle any authentication
    #--------------------------------------------------------------------------
    my $auth_mech = $self->_socks5_connect();
    return unless defined $auth_mech;

    if ($auth_mech != AUTHMECH_ANON)
    {
        return unless $self->_socks5_connect_auth();
    }
    
    #--------------------------------------------------------------------------
    # Send the command (CONNECT/BIND/UDP)
    #--------------------------------------------------------------------------
    if (defined(${*$self}->{SOCKS}->{ConnectAddr}) &&
        defined(${*$self}->{SOCKS}->{ConnectPort}))
    {
        return unless $self->_socks5_connect_command(CMD_CONNECT);

        #if (defined(${*$self}->{SOCKS}->{BindPort}))
        #{
        #    ${*$self}->{SOCKS}->{BindAddr} = ${*$self}->{SOCKS}->{ProxyAddr}
        #        unless defined(${*$self}->{SOCKS}->{BindAddr});
        #    return unless $self->_socks5_connect_command(CMD_BIND);
        #}
    }

    return $self;
}


###############################################################################
#
# _socks5_connect - Send the opening handsake, and process the reply.
#
###############################################################################
sub _socks5_connect
{
    my $self = shift;

    #--------------------------------------------------------------------------
    # Send the auth mechanisms
    #--------------------------------------------------------------------------
    my %connect;
    $connect{version} = SOCKS5_VER;
    my @methods;
    foreach my $method (0..$#{${*$self}->{SOCKS}->{AuthMethods}})
    {
        push(@methods,$method)
            if (${*$self}->{SOCKS}->{AuthMethods}->[$method] == 1);
    }
    $connect{num_methods} = $#methods + 1;
    $connect{methods} = \@methods;
    
    $self->_debug_connect("Send",\%connect);

    $self->_socks_send($connect{version});
    $self->_socks_send($connect{num_methods});
    foreach my $method (@{$connect{methods}})
    {
        $self->_socks_send($method);
    }

    #--------------------------------------------------------------------------
    # Read the reply
    #--------------------------------------------------------------------------
    my %connect_reply;
    $connect_reply{version} = $self->_socks_read();
    $connect_reply{auth_method} = $self->_socks_read();

    $self->_debug_connect_reply("Recv",\%connect_reply);
    
    if ($connect_reply{auth_method} == AUTHMECH_INVALID)
    {
        $SOCKS_ERROR = $CODES{AUTHMECH}->[$connect_reply{auth_method}];
        return;
    }

    return $connect_reply{auth_method};
}


###############################################################################
#
# _socks5_connect_auth - Send and receive a SOCKS5 auth handshake
#
###############################################################################
sub _socks5_connect_auth
{
    my $self = shift;
    
    #--------------------------------------------------------------------------
    # Send the auth
    #--------------------------------------------------------------------------
    my %auth;
    $auth{version} = 1;
    $auth{user_length} = length(${*$self}->{SOCKS}->{Username});
    $auth{user} = ${*$self}->{SOCKS}->{Username};
    $auth{pass_length} = length(${*$self}->{SOCKS}->{Password});
    $auth{pass} = ${*$self}->{SOCKS}->{Password};
    
    $self->_debug_auth("Send",\%auth);
        
    $self->_socks_send($auth{version});
    $self->_socks_send($auth{user_length});
    $self->_socks_send_raw($auth{user});
    $self->_socks_send($auth{pass_length});
    $self->_socks_send_raw($auth{pass});
    
    #--------------------------------------------------------------------------
    # Read the reply
    #--------------------------------------------------------------------------
    my %auth_reply;
    $auth_reply{version} = $self->_socks_read();
    $auth_reply{status} = $self->_socks_read();
    
    $self->_debug_auth_reply("Recv",\%auth_reply);
        
    if ($auth_reply{status} != AUTHREPLY_SUCCESS)
    {
        $SOCKS_ERROR = "Authentication failed with SOCKS5 proxy.";
        return;
    }

    return 1;
}


###############################################################################
#
# _socks_connect_command - Process a SOCKS5 command request
#
###############################################################################
sub _socks5_connect_command
{
    my $self = shift;
    my $command = shift;

    #--------------------------------------------------------------------------
    # Send the command
    #--------------------------------------------------------------------------
    my %command;
    $command{version} = SOCKS5_VER;
    $command{command} = $command;
    $command{reserved} = 0;
    $command{atype} = ADDR_DOMAINNAME;
    $command{host_length} = length(${*$self}->{SOCKS}->{ConnectAddr});
    $command{host} = ${*$self}->{SOCKS}->{ConnectAddr};
    $command{port} = ${*$self}->{SOCKS}->{ConnectPort};

    $self->_debug_command("Send",\%command);
        
    $self->_socks_send($command{version});
    $self->_socks_send($command{command});
    $self->_socks_send($command{reserved});
    $self->_socks_send($command{atype});
    $self->_socks_send($command{host_length});
    $self->_socks_send_raw($command{host});
    $self->_socks_send_raw(pack("n",$command{port}));

    #--------------------------------------------------------------------------
    # Read the reply
    #--------------------------------------------------------------------------
    my %command_reply;
    $command_reply{version} = $self->_socks_read();
    $command_reply{status} = $self->_socks_read();
    
    if ($command_reply{status} == REPLY_SUCCESS)
    {
        $command_reply{reserved} = $self->_socks_read();
        $command_reply{atype} = $self->_socks_read();

        if ($command_reply{atype} == ADDR_DOMAINNAME)
        {
            $command_reply{host_length} = $self->_socks_read();
            $command_reply{host} = $self->_socks_read_raw($command_reply{host_length});
        }
        elsif ($command_reply{atype} == ADDR_IPV4)
        {
            $command_reply{host} = unpack("N",$self->_socks_read_raw(4));
        }
        
        $command_reply{port} = unpack("n",$self->_socks_read_raw(2));
    }
    
    $self->_debug_command_reply("Recv",\%command_reply);
        
    if ($command_reply{status} != REPLY_SUCCESS)
    {
        $SOCKS_ERROR = $CODES{REPLY}->[$command_reply{status}];
        return;
    }

    return 1;
}
    



###############################################################################
#+-----------------------------------------------------------------------------
#| Accept Functions
#+-----------------------------------------------------------------------------
###############################################################################

###############################################################################
#
# accept - When we are accepting new connections, we need to do the SOCKS
#          handshaking before we return a usable socket.
#
###############################################################################
sub accept
{
    my $self = shift;

    croak("Undefined IO::Socket::Socks object passed to accept.")
        unless defined($self);

    my $client = $self->SUPER::accept(@_);

    if (!$self)
    {
        $SOCKS_ERROR = "Proxy accept new client failed.";
        return;
    }

    my $authmech = $self->_socks5_accept($client);
    return unless defined($authmech);

    if ($authmech == AUTHMECH_USERPASS)
    {
        return unless $self->_socks5_accept_auth($client);
    }

    return unless $self->_socks5_accept_command($client);

    return $client;
}


###############################################################################
#
# _socks5_accept - Wait for an opening handsake, and reply.
#
###############################################################################
sub _socks5_accept
{
    my $self = shift;
    my $client = shift;

    #--------------------------------------------------------------------------
    # Read the auth mechanisms
    #--------------------------------------------------------------------------
    my %accept;
    $accept{version} = $client->_socks_read();
    $accept{num_methods} = $client->_socks_read();
    $accept{methods} = [];
    foreach (0..($accept{num_methods}-1))
    {
        push(@{$accept{methods}},$client->_socks_read());
    }
    
    $self->_debug_connect("Recv",\%accept);

    if ($accept{num_methods} == 0)
    {
        $SOCKS_ERROR = "No auth methods sent.";
        return;
    }

    my $authmech;
    
    foreach my $method (@{$accept{methods}})
    {
        if (${*$self}->{SOCKS}->{AuthMethods}->[$method] == 1)
        {
            $authmech = $method;
            last;
        }
    }

    if (!defined($authmech))
    {
        $authmech = AUTHMECH_INVALID;
    }

    #--------------------------------------------------------------------------
    # Send the reply
    #--------------------------------------------------------------------------
    my %accept_reply;
    $accept_reply{version} = SOCKS5_VER;
    $accept_reply{auth_method} = AUTHMECH_INVALID;
    $accept_reply{auth_method} = $authmech if defined($authmech);

    $client->_socks_send($accept_reply{version});
    $client->_socks_send($accept_reply{auth_method});
    
    $self->_debug_connect_reply("Send",\%accept_reply);

    if ($authmech == AUTHMECH_INVALID)
    {
        $SOCKS_ERROR = "No available auth methods.";
        return;
    }
    
    return $authmech;
}


###############################################################################
#
# _socks5_accept_auth - Send and receive a SOCKS5 auth handshake
#
###############################################################################
sub _socks5_accept_auth
{
    my $self = shift;
    my $client = shift;
    
    #--------------------------------------------------------------------------
    # Send the auth
    #--------------------------------------------------------------------------
    my %auth;
    $auth{version} = $client->_socks_read();
    $auth{user_length} = $client->_socks_read();
    $auth{user} = $client->_socks_read_raw($auth{user_length});
    $auth{pass_length} = $client->_socks_read();
    $auth{pass} = $client->_socks_read_raw($auth{pass_length});
    
    $self->_debug_auth("Recv",\%auth);
    
    my $status = 0;
    if (defined(${*$self}->{SOCKS}->{UserAuth}))
    {
        $status = &{${*$self}->{SOCKS}->{UserAuth}}($auth{user},$auth{pass});
    }

    #--------------------------------------------------------------------------
    # Read the reply
    #--------------------------------------------------------------------------
    my %auth_reply;
    $auth_reply{version} = 1;
    $auth_reply{status} = AUTHREPLY_SUCCESS;
    $auth_reply{status} = AUTHREPLY_FAILURE if !$status;
    
    $client->_socks_send($auth_reply{version});
    $client->_socks_send($auth_reply{status});
    
    $self->_debug_auth_reply("Send",\%auth_reply);
        
    if ($auth_reply{status} != AUTHREPLY_SUCCESS)
    {
        $SOCKS_ERROR = "Authentication failed with SOCKS5 proxy.";
        return;
    }

    return 1;
}


###############################################################################
#
# _socks5_acccept_command - Process a SOCKS5 command request.  Since this is
#                           a library and not a server, we cannot process the
#                           command.  Let the parent program handle that.
#
###############################################################################
sub _socks5_accept_command
{
    my $self = shift;
    my $client = shift;

    #--------------------------------------------------------------------------
    # Read the command
    #--------------------------------------------------------------------------
    my %command;
    $command{version} = $client->_socks_read();
    $command{command} = $client->_socks_read();
    $command{reserved} = $client->_socks_read();
    $command{atype} = $client->_socks_read();

    if ($command{atype} == ADDR_DOMAINNAME)
    {
        $command{host_length} =  $client->_socks_read();
        $command{host} = $client->_socks_read_raw($command{host_length});
    }
    elsif ($command{atype} == ADDR_IPV4)
    {
        $command{host} = unpack("N",$client->_socks_read_raw(4));
    }
    else
    {
        $client->_socks_accept_command_reply(REPLY_ADDR_NOT_SUPPORTED);
        $SOCKS_ERROR = $CODES{REPLY}->[REPLY_ADDR_NOT_SUPPORTED];
        return;
    }
    
    $command{port} = unpack("n",$client->_socks_read_raw(2));

    $self->_debug_command("Recv",\%command);

    ${*$client}->{SOCKS}->{COMMAND} = [$command{command},$command{host},$command{port}];

    return 1;
}


###############################################################################
#
# _socks5_acccept_command_reply - Answer a SOCKS5 command request.  Since this
#                                 is a library and not a server, we cannot
#                                 process the command.  Let the parent program
#                                 handle that.
#
###############################################################################
sub _socks5_accept_command_reply
{
    my $self = shift;
    my $reply = shift;
    my $host = shift;
    my $port = shift;

    if (!defined($reply) || !defined($host) || !defined($port))
    {
        croak("You must provide a reply, host, and port on the command reply.");
    }

    #--------------------------------------------------------------------------
    # Send the reply
    #--------------------------------------------------------------------------
    my %command_reply;
    $command_reply{version} = SOCKS5_VER;
    $command_reply{status} = $reply;
    $command_reply{reserved} = 0;
    $command_reply{atype} = ADDR_DOMAINNAME;
    $command_reply{host_length} = length($host);
    $command_reply{host} = $host;
    $command_reply{port} = $port;
    
    $self->_debug_command_reply("Send",\%command_reply);

    $self->_socks_send($command_reply{version});
    $self->_socks_send($command_reply{status});
    $self->_socks_send($command_reply{reserved});
    $self->_socks_send($command_reply{atype});
    $self->_socks_send($command_reply{host_length});
    $self->_socks_send_raw($command_reply{host});
    $self->_socks_send_raw(pack("n",$command_reply{port}));
}


###############################################################################
#
# command - return the command the user request along with the host and
#           port to operate on.
#
###############################################################################
sub command
{
    my $self = shift;

    return ${*$self}->{SOCKS}->{COMMAND};
}


###############################################################################
#
# command_reply - public reply wrapper to the client.
#
###############################################################################
sub command_reply
{
    my $self = shift;
    $self->_socks5_accept_command_reply(@_);
}





###############################################################################
#+-----------------------------------------------------------------------------
#| Helper Functions
#+-----------------------------------------------------------------------------
###############################################################################

###############################################################################
#
# _socks_read - send over the socket after packing according to the rules.
#
###############################################################################
sub _socks_send
{
    my $self = shift;
    my $data = shift;
    
    $data = pack("C",$data);
    $self->_socks_send_raw($data);
} 


###############################################################################
#
# _socks_send_raw - send raw data across the socket.
#
###############################################################################
sub _socks_send_raw
{
    my $self = shift;
    my $data = shift;

    $self->syswrite($data,length($data));
}


###############################################################################
#
# _socks_read - read from the socket, and then unpack according to the rules.
#
###############################################################################
sub _socks_read
{
    my $self = shift;
    my $length = shift;
    $length = 1 unless defined($length);
    
    my $data = $self->_socks_read_raw($length);
    $data = unpack("C",$data);
    return $data;
}


###############################################################################
#
# _socks_read_raw - read raw bytes off of the socket
#
###############################################################################
sub _socks_read_raw
{
    my $self = shift;
    my $length = shift;
    $length = 1 unless defined($length);

    my $data;
    $self->sysread($data,$length);
    return $data;
}




###############################################################################
#+-----------------------------------------------------------------------------
#| Debug Functions
#+-----------------------------------------------------------------------------
###############################################################################

sub _debug_connect
{
    my $self = shift;
    my $tag = shift;
    my $connect = shift;

    return unless ${*$self}->{SOCKS}->{Debug};

    print "$tag: +------+------+-","-"x(4*$connect->{num_methods}),"-+\n";
    print "$tag: | Vers | Auth |";
    if ($connect->{num_methods} > 0)
    {
        print " Meth "," "x(4*($connect->{num_methods}-1)),"|\n";
    }
    print "$tag: +------+------+-","-"x(4*$connect->{num_methods}),"-+\n";

    print "$tag: | ";
    printf("\\%02X",$connect->{version});
    print "  | ";
    printf("\\%02X",$connect->{num_methods});
    print "  | ";
    if ($connect->{num_methods} > 0)
    {
        foreach my $method (@{$connect->{methods}})
        {
            printf("\\%02X ",$method);
        }
        print " |";
    }
    
    print "\n";
    print "$tag: +------+------+-","-"x(4*$connect->{num_methods}),"-+\n";
    print "\n";
}


sub _debug_connect_reply
{
    my $self = shift;
    my $tag = shift;
    my $connect_reply = shift;
    
    return unless ${*$self}->{SOCKS}->{Debug};

    print "$tag: +------+------+\n";
    print "$tag: | Vers | Auth |\n";
    print "$tag: +------+------+\n";
    print "$tag: | ";
    
    printf("\\%02X",$connect_reply->{version});
    print "  | ";
    printf("\\%02X",$connect_reply->{auth_method});
    print "  |\n";

    print "$tag: +------+------+\n";
    print "\n";
}


sub _debug_auth
{
    my $self = shift;
    my $tag = shift;
    my $auth = shift;

    return unless ${*$self}->{SOCKS}->{Debug};

    print "$tag: +------+------+------","-"x($auth->{user_length}-4),"+------+-----","-"x($auth->{pass_length}-4),"-+\n";
    print "$tag: | Vers | UsrL | User "," "x($auth->{user_length}-4),"| PasL | Pass"," "x($auth->{pass_length}-4)," |\n";
    print "$tag: +------+------+------","-"x($auth->{user_length}-4),"+------+-----","-"x($auth->{pass_length}-4),"-+\n";
    print "$tag: | ";

    printf("\\%02X",$auth->{version});
    print "  | ";
    printf("\\%02d",$auth->{user_length});
    print "  | ";
    print $auth->{user}," "x(4-$auth->{user_length});
    print " | ";
    printf("\\%02d",$auth->{pass_length});
    print "  | ";
    print $auth->{pass}," "x(4-$auth->{pass_length});

    print " |\n";
    print "$tag: +------+------+------","-"x($auth->{user_length}-4),"+------+-----","-"x($auth->{pass_length}-4),"-+\n";
    print "\n";
}  


sub _debug_auth_reply
{
    my $self = shift;
    my $tag = shift;
    my $auth_reply = shift;
    
    return unless ${*$self}->{SOCKS}->{Debug};

    print "$tag: +------+------+\n";
    print "$tag: | Vers | Stat |\n";
    print "$tag: +------+------+\n";
    print "$tag: | ";
    
    printf("\\%02X",$auth_reply->{version});
    print "  | ";
    printf("\\%02X",$auth_reply->{status});
    print "  |\n";

    print "$tag: +------+------+\n";
    print "\n";
}


sub _debug_command
{
    my $self = shift;
    my $tag = shift;
    my $command = shift;

    return unless ${*$self}->{SOCKS}->{Debug};

    print "$tag: +------+------+------+------+-------","-"x$command->{host_length},"-+-------+\n";
    print "$tag: | Vers | Comm | Resv | ATyp | Host  "," "x$command->{host_length}," | Port  |\n";
    print "$tag: +------+------+------+------+-------","-"x$command->{host_length},"-+-------+\n";
    print "$tag: | "; 

    printf("\\%02X",$command->{version});
    print "  | ";
    printf("\\%02X",$command->{command});
    print "  | ";
    printf("\\%02X",$command->{reserved});
    print "  | ";
    printf("\\%02X",$command->{atype});
    print "  | ";
    printf("\\%02d",$command->{host_length});
    print " - ";
    print $command->{host};
    print " | ";
    printf("%-5d",$command->{port});

    print " |\n";
    print "$tag: +------+------+------+------+-------","-"x$command->{host_length},"-+-------+\n";
    print "\n";
}


sub _debug_command_reply
{
    my $self = shift;
    my $tag = shift;
    my $command_reply = shift;

    return unless ${*$self}->{SOCKS}->{Debug};

    print "$tag: +------+------+";
    print "------+------+-------","-"x$command_reply->{host_length},"-+-------+"
        if ($command_reply->{status} == 0);
    print "\n";

    print "$tag: | Vers | Stat |";
    print " Resv | ATyp | Host  "," "x$command_reply->{host_length}," | Port  |"
        if ($command_reply->{status} == 0);
    print "\n";

    print "$tag: +------+------+";
    print "------+------+-------","-"x$command_reply->{host_length},"-+-------+"
        if ($command_reply->{status} == 0);
    print "\n";
    
    print "$tag: | "; 

    printf("\\%02X",$command_reply->{version});
    print "  | ";
    printf("\\%02X",$command_reply->{status});
    if ($command_reply->{status} == 0)
    {
        print "  | ";
        printf("\\%02X",$command_reply->{reserved});
        print "  | ";
        printf("\\%02X",$command_reply->{atype});
        print "  | ";
        printf("\\%02d",$command_reply->{host_length});
        print " - ";
        print $command_reply->{host};
        print " | ";
        printf("%-5d",$command_reply->{port});
    }
    else
    {
        print " ";
    }
    print " |\n";
    
    print "$tag: +------+------+";
    print "------+------+-------","-"x$command_reply->{host_length},"-+-------+"
        if ($command_reply->{status} == 0);
    print "\n";
    print "\n";
}


1;
