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 | |
---|
52 | package provide socks 1.2 |
---|
53 | |
---|
54 | # Global settings: |
---|
55 | |
---|
56 | namespace 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 | |
---|
99 | proc ::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 | |
---|
120 | proc ::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 | |
---|
337 | proc ::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 | } |
---|