Tk Library Source Code

Artifact [8814437dea]
Login

Artifact 8814437dea7e4dd71ef85d55ff721f9abf9a382b:

Attachment "socks5.diff" to ticket [3501392fff] added by dongola7 2012-03-11 06:19:29.
diff --git a/examples/socks5/test_client.tcl b/examples/socks5/test_client.tcl
new file mode 100755
index 0000000..040f494
--- /dev/null
+++ b/examples/socks5/test_client.tcl
@@ -0,0 +1,67 @@
+#!/bin/sh
+# This line continues for Tcl, but is a single line for 'sh' \
+exec tclsh "$0" ${1+"$@"}
+
+# test_client.tcl --
+#
+#   Implements a simple TCP client program utilizing the SOCKS 5 library
+#
+# Copyright (c) 2011 Blair Kitchen
+#
+# See the file "license.terms" for information on usage and
+# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+#
+
+set proxyIP "localhost"
+set proxyPort "1080"
+set serverHost "localhost"
+set serverIP "127.0.0.1"
+set serverPort "30000"
+
+set root [file join [file dirname [info script]] ..]
+source [file join $root socks5.tcl]
+
+set data ""
+proc handleConnect {result arg} {
+    if {$result != "ok"} {
+        puts "SOCKS error accepting incoming connection: $arg"
+        return
+    }
+
+    set ::data $arg
+}
+
+::socks5::configure -proxy $proxyIP -proxyport $proxyPort -username foo -password bar
+
+foreach server [list $serverHost $serverIP] {
+    puts "Attempting CNTRL connection to $server:$serverPort using proxy $proxyIP:$proxyPort"
+    set cntrl [::socks5::connect $server $serverPort]
+    puts "CNTRL connection established"
+
+    puts "Attempting to create SOCKS5 binding for DATA connection"
+    set bindInfo [::socks5::bind $server $serverPort handleConnect]
+    lassign $bindInfo host port
+    puts "SOCKS server listening for DATA connection on $host:$port"
+
+    puts "Sending details via CNTRL connection"
+    puts $cntrl $host
+    puts $cntrl $port
+    flush $cntrl
+
+    puts "Waiting for DATA connection"
+    vwait data
+    puts "DATA connection established"
+
+    puts $cntrl "Hello World (via CNTRL)"
+    flush $cntrl
+    puts [gets $data]
+
+    puts $data "Hello World (via DATA)"
+    flush $data
+    puts [gets $cntrl]
+
+    close $data
+    close $cntrl
+
+    puts "---------------"
+}
diff --git a/examples/socks5/test_server.tcl b/examples/socks5/test_server.tcl
new file mode 100755
index 0000000..ac31249
--- /dev/null
+++ b/examples/socks5/test_server.tcl
@@ -0,0 +1,53 @@
+#!/bin/sh
+# This line continues for Tcl, but is a single line for 'sh' \
+exec tclsh "$0" ${1+"$@"}
+
+# socks5.tcl --
+#
+#   Implements a simple TCP server for use with test_client.tcl
+#
+# Copyright (c) 2011 Blair Kitchen
+#
+# See the file "license.terms" for information on usage and
+# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+#
+
+set listenPort "30000"
+
+set root [file join [file dirname [info script]] ..]
+source [file join $root socks5.tcl]
+
+set cntrl ""
+
+proc handleConnect {cntrl addr port} {
+    puts "CNTRL connection from $addr:$port"
+    set host [gets $cntrl]
+    set port [gets $cntrl]
+
+    puts "Attempting DATA connection to $host:$port"
+    set data [socket $host $port]
+    puts "DATA connection established"
+
+    fconfigure $cntrl -blocking 0 -translation binary -encoding binary
+    fconfigure $data -blocking 0 -translation binary -encoding binary
+    fileevent $cntrl readable [list handleRead $cntrl $data]
+    fileevent $data readable [list handleRead $data $cntrl]
+}
+
+proc handleRead {src dst} {
+    if {[eof $src]} {
+        close $src
+        close $dst
+        puts "Client disconnected"
+        return
+    }
+
+    puts "Echoing data"
+    set data [read $src]
+    puts -nonewline $dst $data
+    flush $dst
+}
+
+set server_sock [socket -server handleConnect $listenPort]
+puts "Listening for CNTRL connections on port $listenPort"
+vwait forever
diff --git a/examples/socks5/test_tor.tcl b/examples/socks5/test_tor.tcl
new file mode 100755
index 0000000..27a5505
--- /dev/null
+++ b/examples/socks5/test_tor.tcl
@@ -0,0 +1,35 @@
+#!/bin/sh
+# This line continues for Tcl, but is a single line for 'sh' \
+exec tclsh "$0" ${1+"$@"}
+
+# test_tor.tcl --
+#
+#   Implements a simple script that connects to the Internet through a
+#   Tor proxy.  Assumes you have a tor proxy running on localhost:9050.
+#
+# Copyright (c) 2012 Blair Kitchen
+#
+# See the file "license.terms" for information on usage and
+# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+#
+
+package require Tcl 8.5
+package require tls 1.6
+package require http 2.7
+
+set root [file join [file dirname [info script]] ..]
+source [file join $root socks5.tcl]
+
+proc httpsConnect {host port} {
+    set sock [::socks5::connect $host $port]
+    ::tls::import $sock
+    return $sock
+}
+
+::http::register http 80 ::socks5::connect
+::http::register https 443 ::httpsConnect
+
+::socks5::configure -proxy localhost -proxyport 9050
+
+set token [::http::geturl "https://check.torproject.org" -channel stdout]
+::http::cleanup $token
diff --git a/modules/socks5/pkgIndex.tcl b/modules/socks5/pkgIndex.tcl
new file mode 100644
index 0000000..3ece077
--- /dev/null
+++ b/modules/socks5/pkgIndex.tcl
@@ -0,0 +1,11 @@
+# Tcl package index file, version 1.1
+# This file is generated by the "pkg_mkIndex" command
+# and sourced either when an application starts up or
+# by a "package unknown" script.  It invokes the
+# "package ifneeded" command to set up package-related
+# information so that packages will be loaded automatically
+# in response to "package require" commands.  When this
+# script is sourced, the variable $dir must contain the
+# full path name of this file's directory.
+
+package ifneeded socks5 1.0 [list source [file join $dir socks5.tcl]]
diff --git a/modules/socks5/socks5.man b/modules/socks5/socks5.man
new file mode 100644
index 0000000..e44b002
--- /dev/null
+++ b/modules/socks5/socks5.man
@@ -0,0 +1,170 @@
+[manpage_begin socks5 n 1.0]
+[copyright {2012 Blair Kitchen <[email protected]>}]
+[moddesc {Networking}]
+[moddesc {Implements client support for SOCKS 5 proxies}]
+[require Tcl 8.5]
+[require socks5 [opt 1.0]]
+[description]
+[para]
+
+This package provides routines for connecting to remote servers via a SOCKS 5
+proxy server.  Support for both CONNECT and BIND methods is included.
+
+[section COMMANDS]
+
+[list_begin definitions]
+
+[call [cmd ::socks5::configure] [opt [arg options]]]
+
+Configure the library for use by specifying information such as the proxy
+server, port number, etc.
+
+[list_begin definitions]
+[def "[cmd -proxy] [arg hostname]"] 
+    [term Hostname] specifies the domain-style name or numerical IP address
+    of the proxy server.
+[def "[cmd -proxyport] [arg port]"]
+    [term Port] specifies an integer port number on which the proxy server
+    will respond to incoming requests.
+[def "[cmd -bindtimeout] [arg timeout]"]
+    [term Timeout] specifies a timeout (in milliseconds) when waiting for 
+    an incoming connection via [cmd ::socks5::bind]
+[def "[cmd -username] [arg username]"]
+    [term Username] specifies the username to be used when the proxy
+    server requires username/password authentication.  Providing an empty
+    value for this option disables the use of authentication.
+[def "[cmd -password] [arg password]"]
+    [term Password] specifies the password to be used when the proxy
+    server requires username/password authentication.  Providing an empty
+    value for this option disables the use of authentication.
+[list_end]
+
+[para]
+The following example illustrates use of the [cmd ::socks5::configure] command.
+
+[example {::socks5::configure -proxy localhost -proxyport 1080 -bindtimeout 2000}]
+
+[para]
+[call [cmd ::socks5::connect] [opt [arg options]] [arg hostname] [arg port]]
+
+Establishes a connection with a remote host through the configured proxy
+
+[list_begin definitions]
+[def "[cmd -myaddr] [arg addr]"]
+    [term Addr] gives the domain-style name or numerical IP address of the 
+    client-side network interface to use for the connection.  This option 
+    may be useful if the client machine has multiple network interfaces.
+    If the option is omitted then the client-side interface will be chosen
+    by the system software.
+[def "[cmd -myport] [arg port]"]
+    [term Port] specifies an integer port number (or service name, where
+    supported and understood by the host operating system) to use for the
+    client's side of the connection.  If this option is omitted, the client's
+    port number will be chosen at random by the system software.
+[list_end]
+
+[para]
+The following example illustrates use of the [cmd ::socks5::connect] command.
+
+[example {::socks5::connect www.google.com 80}]
+
+[para]
+[call [cmd ::socks5::bind] [opt [arg options]] [arg hostname] [arg port] [arg command]]
+
+Requests the proxy open a TCP port for listening and wait for an incoming
+connection from the specified host and port. [term Hostname] gives the 
+anticipated domain-style name or numerical IP address of the incoming
+connection.  [term Port] specifies the integer port number of the incoming
+connection.
+
+[para]
+The [term command] parameter will be evaluated at the global scope when the
+incoming connection is established.  Two parameters will be appended.  The
+first indicates the type of response from the proxy, while the second depends
+on the response.
+
+[list_begin definitions]
+[def "[cmd -myaddr] [arg addr]"]
+    [term Addr] gives the domain-style name or numerical IP address of the
+    client-side network interface to use for the connection.  This option
+    may be useful if the client machine has multiple network interfaces.
+    If the option is omitted then the client-side interface will be chosen
+    by the system software.
+[def "[cmd -myport] [arg port]"]
+    [term Port] specifies an integer port number (or service name, where
+    supported and understood by the host operating system) to use for the
+    client's side of the connection.  If this option is omitted, the client's
+    port number will be chosen at random by the system software.
+[list_end]
+
+[para]
+The following example illustrates use of the [cmd ::socks5::bind] command.
+
+[example {
+proc handle_connect {result arg} {
+    if {$result eq "ok"} {
+        puts "connection established via channel $arg"
+    } elseif {$result eq "timeout"} {
+        puts "timeout expired while waiting for connection"
+    } elseif {$result eq "error"} {
+        puts "error from proxy while waiting for connection: $arg"
+    }
+}
+
+::socks5::bind ftp.cdrom.com 21 handle_connect
+}]
+
+[list_end]
+
+[section EXAMPLES]
+The [term socks5] package can be used to SOCKS enable the [term http] package.
+This is useful for passing all HTTP requests through the Tor network, for
+example.  Assuming you have Tor running on your local system and listening for
+connections on port 9050, the following sample code demonstrates how to enable
+http use of Tor.
+
+[example {
+package require Tcl 8.5
+package require tls 1.6
+package require http 2.7
+package require socks5
+
+proc httpsConnect {host port} {
+    # Establish connection with SOCKS 5
+    set sock [::socks5::connect $host $port]
+    # Enable SSL on open socket
+    ::tls::import $sock
+    return $sock
+}
+
+::http::register http 80 ::socks5::connect
+::http::register https 443 ::httpsConnect
+
+::socks5::configure -proxy localhost -port 9050
+
+# Retrieve https://check.torproject.org to confirm we are using Tor
+set token [::http::geturl "https://check.torproject.org" -channel stdout]
+::http::cleanup $token
+}]
+
+[section "KNOWN LIMITATIONS"]
+The following known limitations exist:
+
+[list_begin bullet]
+[bullet] No support for IPv6
+[bullet] Support for username/password authentication only
+[bullet] No support for the UDP ASSOCIATE command
+[list_end]
+
+[section {BUGS, IDEAS, FEEDBACK}]
+
+This document, and the package it describes, will undoubtedly contain
+bugs and other problems.
+
+Please report such in the category [emph socks5] of the
+[uri {http://sourceforge.net/tracker/?group_id=12883} {Tcllib SF Trackers}].
+
+Please also report any ideas for enhancements you may have for either
+package and/or documentation.
+
+[manpage_end]
diff --git a/modules/socks5/socks5.tcl b/modules/socks5/socks5.tcl
new file mode 100644
index 0000000..6f2a9ad
--- /dev/null
+++ b/modules/socks5/socks5.tcl
@@ -0,0 +1,427 @@
+# socks5.tcl --
+#
+#   Tcl implementation of a SOCKS 5 client
+#
+# Copyright (c) 2011 Blair Kitchen
+#
+# See the file "license.terms" for information on usage and
+# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+#
+
+package require Tcl 8.5
+package require cmdline 1.3
+
+package provide socks5 1.0
+
+namespace eval ::socks5 { 
+    namespace export configure bind connect
+
+    array set Config {
+        proxy {} 
+        proxyport 1080 
+        bindtimeout 2000 
+        username {} 
+        password {}
+    }
+
+    array set ResponseCodes {
+        0 "succeeded"
+        1 "general SOCKS server failure"
+        2 "connection not allowed by ruleset"
+        3 "network unreachable"
+        4 "host unreachable"
+        5 "connection refused"
+        6 "TTL expired"
+        7 "command not supported"
+        8 "address type not supported"
+    }
+
+    array set MethodCodes {
+        0 "no authentication required"
+        1 "gssapi"
+        2 "username/password"
+        255 "no acceptable methods"
+    }
+}
+
+# ::socks5::configure
+#
+#   Configures the SOCKS 5 client library
+#
+# Arguments:
+#   args    List of parameters to configure (see ::socks5::Config for list)
+#
+# Results:
+#   None
+#
+proc ::socks5::configure {args} {
+    variable Config
+
+    set options [list proxy.arg \
+        proxyport.arg \
+        bindtimeout.arg \
+        username.arg \
+        password.arg]
+
+    while {[::cmdline::getopt args $options option value] == 1} {
+        switch -- $option {
+            proxyport {
+                if {($value <= 0) || ($value >= 65565)} {
+                    return -code error "proxyport requires a value between 1 and 65565"
+                }
+            }
+            bindtimeout {
+                if {($value < 0)} {
+                    return -code error "bindtimeout requires a value greater than 1"
+                }
+            }
+        }
+
+        set Config($option) $value
+    }
+
+    if {[llength $args] > 0} {
+        return -code error "unknown option '[lindex $args 0]'"
+    }
+}
+
+# ::socks5::bind
+#
+#   Requests the SOCKS 5 server open a port for listening and await a
+#   connection from the specified host and port.
+#
+# Arguments:
+#   options List of options to pass to the socket call
+#           ?-myaddr addr? ?-myport port?
+#   host    The hostname or IP from which a connection will be established
+#   port    The port number from which a connection will be established
+#   command Command that will be executed when the connection is established
+#
+# Results:
+#   List consisting of IP and port on the proxy server opened and listening
+#   for an incoming connection
+#
+#   command is executed when the connection is established and two arguments
+#   appended.  The first is the success indication and is one of: ok, timeout,
+#   or error.  The second is an argument related to the success indicator.  For
+#   ok, it is the channel handle on which communication may take place.  For
+#   error and timeout, it is an error message.
+#
+proc ::socks5::bind {args} {
+    variable Config
+
+    set errorCode [catch {ProxyConnect args} sock]
+    if {$errorCode == -1} {
+        return -code error $sock
+    } elseif {$errorCode != 0} {
+        return -code $errorCode -errorinfo $::errorInfo $sock
+    }
+
+    if {[llength $args] != 3} {
+        return -code "wrong # args: should be \"bind ?-myaddr addr? ?-myport myport? host port command"
+    }
+
+    lassign $args host port command
+
+    set cmd [binary format ccc 5 2 0]
+    append cmd [FormatAddress $host $port]
+
+    puts -nonewline $sock $cmd
+    flush $sock
+
+    set errorCode [catch {ReadResponse $sock} result]
+    if {$errorCode != 0} {
+        chan close $sock
+        if {$errorCode == -1} {
+            return -code error $result
+        } else {
+            return -code $errorCode -errorinfo $::errorInfo $result
+        }
+    }
+
+    set timeout_id [after $Config(bindtimeout) \
+        [list ::socks5::BindCallback timeout {} $sock $command]]
+    chan event $sock readable \
+        [list ::socks5::BindCallback readable $timeout_id $sock $command]
+
+    return $result
+}
+
+# ::socks5::connect
+#
+#   Requests the SOCKS 5 server establish an outgoing connection to
+#   the named host on the named port.
+#
+# Arguments:
+#   options List of options to pass to the socket call
+#           ?-myaddr addr? ?-myport port?
+#   host    The hostname or IP to which a connection should be established
+#   port    The port number to which a connection should be established
+#
+# Results:
+#   Returns a channel handle to the socket used for communication.
+#   The channel is ready for normal use when returned.
+#
+proc ::socks5::connect {args} {
+    set errorCode [catch {ProxyConnect args} sock]
+    if {$errorCode == -1} {
+        return -code error $sock
+    } elseif {$errorCode != 0} {
+        return -code $errorCode -errorinfo $::errorInfo $sock
+    }
+
+    if {[llength $args] != 2} {
+        return -code "wrong # args: should be \"connect ?-myaddr addr? ?-myport myport? host port command"
+    }
+
+    lassign $args host port
+
+    set cmd [binary format ccc 5 1 0]
+    append cmd [FormatAddress $host $port]
+
+    puts -nonewline $sock $cmd
+    flush $sock
+
+
+    set errorCode [catch {ReadResponse $sock} msg]
+    if {$errorCode != 0} {
+        chan close $sock
+        if {$errorCode == -1} {
+            return -code error $msg
+        } else {
+            return -code $errorCode -errorinfo $::errorInfo
+        }
+    }
+
+    return $sock
+}
+
+# ::sock5::BindCallback
+#
+#   Callback procedure registered by ::socks5::bind on the
+#   SOCKS 5 client socket for the readable event.
+#
+# Arguments:
+#   reason  The reason the callback is firing (timeout or else)
+#   timeout_id  after callback identifier for cancelling the timeout event
+#   sock    Channel handle to the open socket
+#   command User command for callback (passed to ::socks5::bind)
+#
+# Results:
+#   Calls "command" in the global namespace to provide information
+#   to the user logic.
+#
+proc ::socks5::BindCallback {reason timeout_id sock command} {
+    after cancel $timeout_id
+
+    if {$reason eq "timeout"} {
+        chan close $sock
+        uplevel #0 [list $command timeout "timeout occurred while waiting for connection"]
+    } else {
+        if {[catch {ReadResponse $sock} result]} {
+            chan close $sock
+            uplevel #0 [list $command error $result]
+        } else {
+            uplevel #0 [list $command ok $sock]
+        }
+    }
+}
+
+# ::socks5::FormatAddress
+#
+#   Formats a hostname and port into the format defined by the SOCKS 5
+#   specification.
+#
+# Arguments:
+#   host    The hostname or IP address
+#   port    The port number
+#
+# Results:
+#   Returns the hostname/IP and port number formatted as a binary message
+#   for transmission to the SOCKS 5 server.
+#
+proc ::socks5::FormatAddress {host port} {
+    if {[regexp -- {^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$} $host]} {
+        set parts [split $host .]
+        set result [binary format cccccS 1 {*}$parts $port]
+    } else {
+        if {[string length $host] > 255} {
+            return -code -1 "host must be 255 characters or less"
+        }
+
+        set result [binary format cca*S 3 [string length $host] $host $port]
+    }
+
+    return $result
+}
+
+# ::socks5::ReadResponse
+#
+#   Reads a SOCKS 5 server response message from the given channel handle.
+#
+# Arguments:
+#   sock    The channel handle from which to read
+#
+# Results:
+#   Returns an error if there is a problem reading the response.  Otherwise
+#   returns a two element list consisting of the hostname/IP and port number
+#   from the response.
+#
+proc ::socks5::ReadResponse {sock} {
+    variable ResponseCodes
+
+    set rsp [read $sock 3]
+    if {[string length $rsp] != 3} {
+        return -code -1 "unable to read response from proxy"
+    }
+
+    binary scan $rsp ccx version reply
+    if {$reply != 0} {
+        if {[info exists ResponseCodes($reply)]} {
+            return -code -1 "error from proxy: $ResponseCodes($reply) ($reply)"
+        }
+
+        return -code -1 "error from proxy: $reply"
+    }
+
+    set rsp [read $sock 1]
+    binary scan $rsp c addr_type
+
+    if {$addr_type == 1} {
+        set rsp [read $sock 6]
+        binary scan $rsp "cucucucuSu" ip1 ip2 ip3 ip4 port
+        set result [list "$ip1.$ip2.$ip3.$ip4" $port]
+    } elseif {$addr_type == 3} {
+        set len [read $sock 1]
+        set len [binary scan $len c]
+        
+        set host [read $sock $len]
+        set port [binary scan S [read $sock 2]]
+        set result [list $host $port]
+    } else {
+        return -code -1 "invalid address type from proxy: $addr_type"
+    }
+
+    return $result
+}
+
+#
+# ::socks5::ProxyConnect
+#
+#   Helper procedure to connect and perform handshaking with a SOCKS 5 proxy 
+#   server.  The proxy server address and port are taken from the global
+#   configuration and may be specified via a call to ::socks5::configure.
+#
+# Arguments:
+#   args    List of arguments to pass to the socket call 
+#           ?-myaddr addr? ?-myport port?
+#
+# Results:
+#   Returns a channel handle to the open connection on success.  Returns an
+#   error otherwise.  The channel is authenticated and ready for use in SOCKS 5
+#   client requests on return.
+#
+proc ::socks5::ProxyConnect { argsVar } {
+    variable Config
+    variable MethodCodes
+
+    if {$Config(proxy) eq {} || $Config(proxyport) eq {}} { 
+        return -code -1 "no proxy or proxy port specified"
+    }
+
+    # Parse the command line options for socket related options.  Leave the
+    # remaining arguments for the parent
+    upvar $argsVar args
+    set socketOptions [list]
+    while {[set result [cmdline::getopt args {myaddr.arg myport.arg} option value]] == 1} {
+        lappend socketOptions "-$option" $value
+    }
+
+    if {$result == -1} {
+        return -code -1 "unknown option '[lindex $args 0]'"
+    }
+
+    set errorCode [catch {socket {*}$socketOptions $Config(proxy) $Config(proxyport)} sock]
+    if {$errorCode != 0} {
+        return -code -1 "unable to connect to proxy: $sock"
+    }
+    fconfigure $sock -translation binary -encoding binary -blocking 1
+
+    set numMethods 1
+    set methods [list 0]
+
+    if {$Config(username) != {} || $Config(password) != {}} {
+        incr numMethods
+        lappend methods 5
+    }
+
+    puts -nonewline $sock [binary format ccc* 5 $numMethods $methods]
+    flush $sock
+
+    set rsp [read $sock 2]
+    if {[string length $rsp] != 2} {
+        chan close $sock
+        return -code -1 "unable to read handshake response from proxy"
+    }
+
+    binary scan $rsp cc version method
+    if {$version != 5} {
+        chan close $sock
+        return -code -1 "unsupported version: $version"
+    } elseif {$method == 255} {
+        chan close $sock
+        return -code -1 "unsupported method from proxy: $MethodCodes($method) ($method)"
+    } elseif {$method == 5} {
+        PerformUserPassAuth $sock
+    }
+
+    return $sock
+}
+
+# ::socks5::PerformUserPassAuth
+#
+#   Performs username/password authentication on the given channel.  The
+#   username and password are taken from the global configuration and may
+#   be specified via a call to ::socks5::configure.
+#
+# Arguments:
+#   sock    The socket on which to perform authentication
+#
+# Results:
+#   Returns an error on authentication failure.
+#
+proc ::socks5::PerformUserPassAuth {sock} {
+    variable Config
+
+    if {[string length $Config(username)] > 255]} {
+        chan close $sock
+        return -code -1 "username must be 255 characters or less"
+    }
+
+    if {[string length $Config(password)] > 255]} {
+        chan close $sock
+        return -code -1 "password must be 255 characters or less"
+    }
+
+    puts -nonewline $sock [binary scan cca*ca* 1 \
+        [string length $Config(username)] $Config(username) \
+        [string length $Config(password)] $Config(password)]
+    flush $sock
+
+    set rsp [read $sock 2]
+    if {[string length $rsp] != 2} {
+        chan close $sock
+        return -code -1 "unable to read auth response from proxy"
+    }
+
+    binary scan cc $rsp version reply
+    if {$version != 1} {
+        chan close $sock
+        return -code -1 "unsupported username/password auth version ($version)"
+    } elseif {$reply != 0} {
+        chan close $sock
+        return -code -1 "proxy denied presented auth tokens"
+    }
+
+    return
+}
diff --git a/support/installation/modules.tcl b/support/installation/modules.tcl
index 7ed5b68..a07db19 100755
--- a/support/installation/modules.tcl
+++ b/support/installation/modules.tcl
@@ -118,6 +118,7 @@ Module  sha1        _tcl  _man  _null
 Module  simulation  _tcl  _man  _null
 Module  smtpd       _tcl  _man _exa
 Module  snit        _tcl  _man  _null
+Module  socks5      _tcl  _man  _exa
 Module  soundex     _tcl  _man  _null
 Module  stooop      _tcl  _man  _null
 Module  stringprep  _tcl  _man  _null