Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/sound5/data/tcl/irk/socks/socks.tcl @ 7305

Last change on this file since 7305 was 5781, checked in by rgrieder, 15 years ago

Reverted trunk again. We might want to find a way to delete these revisions again (x3n's changes are still available as diff in the commit mails).

  • Property svn:eol-style set to native
File size: 9.0 KB
Line 
1# Socks5 Client Library v1.2
2#
3# Original author: (C)2000 Kerem 'Waster_' HADIMLI
4#
5# Author contact information:
6#   E-mail :  waster@iname.com
7#   ICQ#   :  27216346
8#   Jabber :  waster@jabber.org   (New IM System - http://www.jabber.org)
9#
10# Cleaned up a little by Jacob Levy, jyl@best.com (01/07/2001)
11#
12# * Moved everything into the ::socks:: namespace.
13# * Parameterized so that constants do not get reset for every connection.
14# * Uniform error handling and cleanup.
15# * Incremented the version number to 1.2.
16#
17# How to use:
18#
19# Step 1: Create your client socket connected to the SOCKS5 server.
20#
21# Step 2: Call socks::init sock host port ?auth? ?user? ?pass?
22#    where:
23#
24#       sock            The socket from step #1.
25#       host            The remote host to connect to through the proxy.
26#       port            The remote port to connect to through the proxy.
27#       auth            Optional. If 1, it means that we do an
28#                       authentication with the proxy server.
29#       user            Optional. If present, the user name to use for
30#                       authentication with the proxy server.
31#       pass            Optional. If present, the password for the user
32#                       name to use for authentication with the
33#                       proxy server.
34#
35# If auth == 1, user and pass are not present, and socks::state(username)
36# and socks::state(pass) are set, then socks::init uses the values from
37# socks::state(username) and socks::state(password) for user name and
38# password, respectively.
39#
40# If anything goes wrong during the attempt to connect through the
41# proxy server, socks::init throws an appropriate error and closes
42# the socket. If the operation succeeds, socks::init returns $sock.
43#
44# You can set up your socket with fconfigure before calling socks::init,
45# the socks::init procedure is careful to restore the state to what it
46# was before the call upon successful completion. Likewise, you can
47# have fileevent handlers installed for handling socket readable and
48# socket writable events. These will also be preserved.
49
50# This file provides the "socks" package:
51
52package provide socks 1.2
53
54# Global settings:
55
56namespace eval ::socks {
57    variable state
58
59    array set state {
60        protocolversion         "\x05"
61        cmdconnect              "\x01"
62        addresstype             "\x03"
63        reserved                "\x00"
64
65        noauthmethod            "\x00"
66        noauthlengthstr         "\x01"
67
68        authmethod              "\x00\x02"
69        authlengthstr           "\x02"
70
71        nomatchingmethod        "\xFF"
72        nomatchlengthstr        "\x01"
73
74        authenticationversion   "\x01"
75
76        errorheader             "Error: "
77        error1                  "General SOCKS server failure"
78        error2                  "Connection not allowed by ruleset"
79        error3                  "Network unreachable"
80        error4                  "Host unreachable"
81        error5                  "Connection refused"
82        error6                  "TTL expired"
83        error7                  "Command not supported"
84        error8                  "Address type not supported"
85        errorUnknown            "Unknown error"
86
87        errorDisconnect         "SOCKS server disconnected"
88        errorNotSOCKS5          "SOCKS server does not support SOCKS 5"
89        errorMustAuthenticate   "SOCKS server demands user/pass authentication"
90        errorAuthMethodNotSup   "SOCKS server doesn't support user/pass auth"
91        errorWrongUserPass      "Wrong user name or password"
92        errorIncorrectAuthVal   "Incorrect value for $auth, expecting 1 or 0"
93        errorMissingUserAndPass "Missing user and pass, required for auth == 1"
94    }
95}
96
97# Error handling and cleanup:
98
99proc ::socks::Error {sock errorMsg} {
100    variable state
101
102    # Attempt to close the socket. This also cancels any installed
103    # fileevent handlers, so no need to do that explicitly.
104
105    catch {close $sock}
106
107    # Clean up the state we keep about this socket:
108
109    catch {array unset state $sock,*}
110
111    # Report the requested error:
112
113    error "$state(errorheader)$state($errorMsg)"
114}
115   
116# Main entry point: socks::init
117#
118# See comment at head of file for how to use.
119
120proc ::socks::init {sock addr port {auth 0} {user {}} {pass {}}} {
121    variable state
122
123    # Save current configuration state for $sock
124
125    set currentConfiguration [fconfigure $sock]
126
127    # We cannot configure -peername so if its present (it should be)
128    # then remove it from the list of options.
129
130    set idx [lsearch $currentConfiguration -peername]
131    if {$idx != -1} {
132        set currentConfiguration \
133            [lreplace $currentConfiguration $idx [expr $idx + 1]]
134    }
135
136    # Same for -sockname.
137
138    set idx [lsearch $currentConfiguration -sockname]
139    if {$idx != -1} {
140        set currentConfiguration \
141            [lreplace $currentConfiguration $idx [expr $idx + 1]]
142    }
143
144    # Save any currently installed handler for fileevent readable:
145
146    set currentReadableHandler [fileevent $sock readable]
147
148    # If the user has "set and forget" user name and password, and
149    # indicates that she wants to use them, use them now:
150
151    if {($auth == 1) && (![string compare {} $user]) \
152            && (![string compare {} $pass])} {
153        if {[info exists state(username)] && [info exists state(password)]} {
154            set auth 1
155            set user $state(username)
156            set pass $state(password)
157        } else {
158            Error $sock errorMissingUserAndPass
159        }
160    }
161
162    # Figure out the authentication method to use:
163
164    if {$auth == 0} {
165        set nmethods $state(noauthlengthstr)
166        set method $state(noauthmethod)
167    } elseif {$auth == 1} {
168        set nmethods $state(authlengthstr)
169        set method $state(authmethod)
170    } else {
171        Error $sock errorIncorrectAuthVal
172    }
173
174    # Encode the length of the address given (binary 1 byte):
175
176    set domainlen "[binary format c [string length $addr]]"
177
178    # Encode the port (network byte order, 2 bytes):
179
180    set port [binary format S $port]
181
182    if {$auth == 1} {
183        # Encode the length of the user given (binary 1 byte):
184
185        set userlen "[binary format c [string length $user]]"
186
187        # Encode the length of the password given (binary 1 byte):
188
189        set passlen "[binary format c [string length $pass]]"
190    }
191
192    # Set up initial state for the given socket:
193
194    set state($sock,state) $sock
195    set state($sock,data) ""
196
197    # Prepare the socket:
198
199    fconfigure $sock -translation {binary binary} -blocking 0
200    fileevent $sock readable "::socks::readable $sock"
201
202    # Tell the server what version and authentication method we're using:
203
204    puts -nonewline $sock "$state(protocolversion)$nmethods$method"
205    flush $sock
206
207    # Wait for server response and retrieve the information sent by the
208    # server:
209
210    vwait ::socks::state($sock,state)
211    set serverReply $state($sock,data)
212
213    if {[eof $sock]} {
214        Error $sock errorDisconnect
215    }
216
217    # Analyze the server's reply:
218
219    set serverVersion ""
220    set serverMethod $state(nomatchingmethod)
221
222    binary scan $serverReply "cc" serverVersion serverMethod
223
224    # Check for various error conditions:
225
226    if {$serverVersion != 5} {
227
228        # Server does not support SOCKS5 protocol
229
230        Error $sock errorNotSOCKS5
231    }
232
233    # If server demands authentication, do that step now:
234
235    if {$serverMethod == 2} {
236
237        if {$auth == 0} {
238
239            # We didn't supply user/pass but server wants us to authenticate:
240
241            Error $sock errorMustAuthenticate
242        }
243
244        puts -nonewline $sock \
245            "$state(authenticationversion)$userlen$user$passlen$pass"
246        flush $sock
247
248        # Wait again for server reply:
249
250        vwait ::socks::state($sock,state)
251        set serverReply $state($sock,data)
252
253        # Analyze the server reply:
254
255        set authenticationVersion ""
256        set serverStatus "\x00"
257
258        binary scan $serverReply "cc" authenticationVersion serverStatus
259
260        # Deal with errors:
261
262        if {$authenticationVersion != 1} {
263
264            # Server does not support our user/pass authentication method:
265
266            Error $sock errorAuthMethodNotSup
267        }
268
269        if {$serverStatus != 0} {
270
271            # We supplied wrong user/pass combination:
272
273            Error $Sock errorWrongUserPass
274        }
275    } elseif {$serverMethod != 0} {
276
277        # Unknown method. Clean up:
278
279        Error $sock errorUnsupportedMethod
280    }
281
282    # Finally tell the server to connect us to the requested host and port:
283
284    puts -nonewline $sock \
285        "$state(protocolversion)$state(cmdconnect)$state(reserved)"
286    puts -nonewline $sock "$state(addresstype)$domainlen$addr$port"
287    flush $sock
288
289    # Wait again for server response:
290
291    vwait ::socks::state($sock,state)
292    set serverReply $state($sock,data)
293
294    if {[eof $sock]} {
295        Error $sock errorDisconnect
296    }
297
298    # Analyze server reply:
299
300    set serverVersion ""
301    set serverReplyCode ""
302
303    binary scan $serverReply "cc" serverVersion serverReplyCode
304
305    # Deal with errors:
306
307    if {$serverVersion != 5} {
308        Error $sock errorNotSOCKS5
309    }
310
311    if {$serverReplyCode != 0} {
312        if {($serverReplyCode > 0) && ($serverReplyCode < 9)} {
313            Error $sock error$serverReplyCode
314        }
315        Error $sock errorUnknown
316    }
317
318    # All done, clean up state, reconfigure $sock to its original state,
319    # remove our fileevent handler and potentially restore the original
320    # one if one was present.
321
322    fileevent $sock readable {}
323    if {[string compare $currentReadableHandler ""]} {
324        fileevent $sock readable $currentReadableHandler
325    }
326    catch {eval fconfigure $sock $currentConfiguration}
327    catch {array unset state $sock,*}
328
329    # For good measure return the socket:
330
331    return $sock
332}
333
334# This procedure reads input available from the server socket and then
335# changes a state variable so that the main program will be woken up.
336
337proc ::socks::readable {sock} {
338    variable state
339
340    # Wake up the vwait:
341
342    set state($sock,state) $sock
343
344    # Read the data:
345
346    set state($sock,data) [read $sock]
347}
Note: See TracBrowser for help on using the repository browser.