socksify-with-auth 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ body {
2
+ background-color: #8f001f;
3
+ font-family: sans-serif;
4
+ }
5
+
6
+ a {
7
+ color: green;
8
+ }
9
+
10
+ h1 {
11
+ text-align: center;
12
+ margin: 0px;
13
+ color: white;
14
+ font-weight: bold;
15
+ font-size: 400%;
16
+ }
17
+
18
+ div.content {
19
+ background-color: white;
20
+ color: black;
21
+ max-width: 52em;
22
+ margin-left: auto;
23
+ margin-right: auto;
24
+ padding: 0.2em 1em;
25
+ }
26
+
27
+ p {
28
+ text-align: justify;
29
+ }
30
+
31
+ pre {
32
+ color: #8f001f;
33
+ background-color: #cfffbf;
34
+ padding: 4px;
35
+ margin: 2px 0px;
36
+ border: 1px dashed green;
37
+ }
@@ -0,0 +1,165 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+
4
+ <html xmlns="http://www.w3.org/1999/xhtml">
5
+ <head>
6
+ <title>SOCKSify Ruby</title>
7
+ <link rel="stylesheet" type="text/css" href="index.css"/>
8
+ </head>
9
+ <body>
10
+ <h1>SOCKSify Ruby</h1>
11
+
12
+ <div class="content">
13
+
14
+ <h2>What is it?</h2>
15
+
16
+ <p>
17
+ <b>SOCKSify Ruby</b> redirects any TCP connection initiated by
18
+ a Ruby script through a SOCKS5 proxy. It serves as a small
19
+ drop-in alternative
20
+ to <a href="http://tsocks.sourceforge.net/">tsocks</a>, except
21
+ that it handles Ruby programs only and doesn't leak DNS
22
+ queries.
23
+ </p>
24
+
25
+ <h3>How does it work?</h3>
26
+
27
+ <p>
28
+ Modifications to class <code>TCPSocket</code>:
29
+ </p>
30
+ <ul>
31
+ <li>Alias <code>initialize</code>
32
+ as <code>initialize_tcp</code></li>
33
+ <li>The new <code>initialize</code> calls the old method to
34
+ establish a TCP connection to the SOCKS proxy, sends the
35
+ proxying destination and checks for errors</li>
36
+ </ul>
37
+ <p>
38
+ Additionally, <code>Socksify::resolve</code> can be used to
39
+ resolve hostnames to IPv4 addresses via SOCKS. There is also
40
+ <code>socksify/http</code> enabling Net::HTTP to work
41
+ via SOCKS.
42
+ </p>
43
+
44
+ <h2>Installation</h2>
45
+ <pre>$ gem install socksify</pre>
46
+
47
+ <h2>Usage</h2>
48
+
49
+ <h3>Redirect all TCP connections of a Ruby program</h3>
50
+ <p>
51
+ Run a Ruby script with redirected TCP through a
52
+ local <a href="http://www.torproject.org/">Tor</a>
53
+ anonymizer:
54
+ </p>
55
+ <pre>$ socksify_ruby localhost 9050 script.rb</pre>
56
+
57
+ <h3>Explicit SOCKS usage in a Ruby program</h3>
58
+ <p>
59
+ Set up SOCKS connections for a
60
+ local <a href="http://www.torproject.org/">Tor</a>
61
+ anonymizer, TCPSockets can be used as usual:
62
+ </p>
63
+ <pre>require 'socksify'
64
+ TCPSocket::socks_server = "127.0.0.1"
65
+ TCPSocket::socks_port = 9050
66
+ rubyforge_www = TCPSocket.new("rubyforge.org", 80)
67
+ # => #&lt;TCPSocket:0x...&gt;</pre>
68
+
69
+ <p>
70
+ Using block only:
71
+ <pre>require 'socksify'
72
+ require 'open-uri'
73
+ Socksify::proxy("127.0.0.1", 9050) {
74
+ open('http://rubyforge.org').read
75
+ # => #&lt;String: rubyforge's html&gt;
76
+ }
77
+ </pre>
78
+ </p>
79
+
80
+ <p>
81
+ Please note: <b>socksify is not thread-safe</b> when used this way!
82
+ <code>socks_server</code> and <code>socks_port</code> are stored in class
83
+ <code>@@</code>-variables, and applied to all threads and fibers of application.
84
+ </p>
85
+
86
+ <h3>Use Net::HTTP explicitly via SOCKS</h3>
87
+ <p>
88
+ Require the additional library <code>socksify/http</code>
89
+ and use the <code>Net::HTTP.SOCKSProxy</code> method. It
90
+ is similar to <code>Net:HTTP.Proxy</code> from the Ruby
91
+ standard library:
92
+ </p>
93
+ <pre>
94
+ require 'socksify/http'
95
+ uri = URI.parse('http://rubyforge.org/')
96
+ Net::HTTP.SOCKSProxy('127.0.0.1', 9050).start(uri.host, uri.port) do |http|
97
+ http.get(uri.path)
98
+ end
99
+ # => #&lt;Net::HTTPOK 200 OK readbody=true&gt;</pre>
100
+
101
+ <p>
102
+ Note that <code>Net::HTTP.SOCKSProxy</code> never relies
103
+ on <code>TCPSocket::socks_server</code>/<code>socks_port</code>.
104
+ You should either set <code>SOCKSProxy</code> arguments
105
+ explicitly or use <code>Net::HTTP</code> directly.
106
+ </p>
107
+
108
+ <p>
109
+ <code>Net::HTTP.SOCKSProxy</code> also supports SOCKS authentication:
110
+ </p>
111
+ <pre>
112
+ Net::HTTP.SOCKSProxy('127.0.0.1', 9050, 'username', 'p4ssw0rd')
113
+ </pre>
114
+
115
+
116
+ <h3>Resolve addresses via SOCKS</h3>
117
+ <pre>Socksify::resolve("spaceboyz.net")
118
+ # => "87.106.131.203"</pre>
119
+
120
+ <h3>Debugging</h3>
121
+ <p>
122
+ Colorful diagnostic messages can be enabled via:
123
+ </p>
124
+ <pre>Socksify::debug = true</pre>
125
+
126
+ <h2>Development</h2>
127
+
128
+ <p>
129
+ The <a href="http://github.com/astro/socksify-ruby/">repository</a>
130
+ can be checked out with:
131
+ </p>
132
+ <pre>$ git-clone git://github.com/astro/socksify-ruby.git</pre>
133
+ <p>
134
+ Send patches via E-Mail.
135
+ </p>
136
+
137
+ <h3>Further ideas</h3>
138
+ <ul>
139
+ <li><code>Resolv</code> replacement code, so that programs
140
+ which resolve by themselves don't leak DNS queries</li>
141
+ <li>IPv6 address support</li>
142
+ <li>UDP as soon
143
+ as <a href="http://www.torproject.org/">Tor</a> supports
144
+ it</li>
145
+ <li>Perhaps using standard exceptions for better compatibility
146
+ when acting as a drop-in?</li>
147
+ </ul>
148
+
149
+ <h2>Author</h2>
150
+ <ul>
151
+ <li>
152
+ <a href="mailto:stephan@spaceboyz.net">Stephan Maka</a>
153
+ </li>
154
+ </ul>
155
+
156
+ <h2>License</h2>
157
+ <p>
158
+ SOCKSify Ruby is distributed under the terms of the GNU
159
+ General Public License version 3 (see
160
+ file <code>COPYING</code>) or the Ruby License (see
161
+ file <code>LICENSE</code>) at your option.
162
+ </p>
163
+ </div>
164
+ </body>
165
+ </html>
@@ -0,0 +1,369 @@
1
+ #encoding: us-ascii
2
+ =begin
3
+ Copyright (C) 2007 Stephan Maka <stephan@spaceboyz.net>
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ =end
18
+
19
+ require 'socket'
20
+ require 'resolv'
21
+ require 'socksify/debug'
22
+
23
+ class SOCKSError < RuntimeError
24
+ def initialize(msg)
25
+ Socksify::debug_error("#{self.class}: #{msg}")
26
+ super
27
+ end
28
+
29
+ class ServerFailure < SOCKSError
30
+ def initialize
31
+ super("general SOCKS server failure")
32
+ end
33
+ end
34
+ class NotAllowed < SOCKSError
35
+ def initialize
36
+ super("connection not allowed by ruleset")
37
+ end
38
+ end
39
+ class NetworkUnreachable < SOCKSError
40
+ def initialize
41
+ super("Network unreachable")
42
+ end
43
+ end
44
+ class HostUnreachable < SOCKSError
45
+ def initialize
46
+ super("Host unreachable")
47
+ end
48
+ end
49
+ class ConnectionRefused < SOCKSError
50
+ def initialize
51
+ super("Connection refused")
52
+ end
53
+ end
54
+ class TTLExpired < SOCKSError
55
+ def initialize
56
+ super("TTL expired")
57
+ end
58
+ end
59
+ class CommandNotSupported < SOCKSError
60
+ def initialize
61
+ super("Command not supported")
62
+ end
63
+ end
64
+ class AddressTypeNotSupported < SOCKSError
65
+ def initialize
66
+ super("Address type not supported")
67
+ end
68
+ end
69
+
70
+ def self.for_response_code(code)
71
+ case code
72
+ when 1
73
+ ServerFailure
74
+ when 2
75
+ NotAllowed
76
+ when 3
77
+ NetworkUnreachable
78
+ when 4
79
+ HostUnreachable
80
+ when 5
81
+ ConnectionRefused
82
+ when 6
83
+ TTLExpired
84
+ when 7
85
+ CommandNotSupported
86
+ when 8
87
+ AddressTypeNotSupported
88
+ else
89
+ self
90
+ end
91
+ end
92
+ end
93
+
94
+ class TCPSocket
95
+ @@socks_version ||= "5"
96
+
97
+ def self.socks_version
98
+ (@@socks_version == "4a" or @@socks_version == "4") ? "\004" : "\005"
99
+ end
100
+ def self.socks_version=(version)
101
+ @@socks_version = version.to_s
102
+ end
103
+ def self.socks_server
104
+ @@socks_server ||= nil
105
+ end
106
+ def self.socks_server=(host)
107
+ @@socks_server = host
108
+ end
109
+ def self.socks_port
110
+ @@socks_port ||= nil
111
+ end
112
+ def self.socks_port=(port)
113
+ @@socks_port = port
114
+ end
115
+ def self.socks_username
116
+ @@socks_username ||= nil
117
+ end
118
+ def self.socks_username=(username)
119
+ @@socks_username = username
120
+ end
121
+ def self.socks_password
122
+ @@socks_password ||= nil
123
+ end
124
+ def self.socks_password=(password)
125
+ @@socks_password = password
126
+ end
127
+ def self.socks_ignores
128
+ @@socks_ignores ||= %w(localhost)
129
+ end
130
+ def self.socks_ignores=(ignores)
131
+ @@socks_ignores = ignores
132
+ end
133
+
134
+ class SOCKSConnectionPeerAddress < String
135
+ attr_reader :socks_server, :socks_port, :socks_username, :socks_password
136
+
137
+ def initialize(socks_server, socks_port, peer_host, socks_username = nil, socks_password = nil)
138
+ @socks_server, @socks_port = socks_server, socks_port
139
+ @socks_username, @socks_password = socks_username, socks_password
140
+ super peer_host
141
+ end
142
+
143
+ def inspect
144
+ "#{to_s} (via #{@socks_server}:#{@socks_port})"
145
+ end
146
+
147
+ def peer_host
148
+ to_s
149
+ end
150
+ end
151
+
152
+ alias :initialize_tcp :initialize
153
+
154
+ # See http://tools.ietf.org/html/rfc1928
155
+ def initialize(host=nil, port=0, local_host=nil, local_port=nil)
156
+ if host.is_a?(SOCKSConnectionPeerAddress)
157
+ socks_peer = host
158
+ socks_server = socks_peer.socks_server
159
+ socks_port = socks_peer.socks_port
160
+ socks_ignores = []
161
+ host = socks_peer.peer_host
162
+ socks_username = socks_peer.socks_username
163
+ socks_password = socks_peer.socks_password
164
+ else
165
+ socks_server = self.class.socks_server
166
+ socks_port = self.class.socks_port
167
+ socks_ignores = self.class.socks_ignores
168
+ socks_username = self.class.socks_username
169
+ socks_password = self.class.socks_password
170
+ end
171
+
172
+ if socks_server and socks_port and not socks_ignores.include?(host)
173
+ Socksify::debug_notice "Connecting to SOCKS server #{socks_server}:#{socks_port}"
174
+ initialize_tcp socks_server, socks_port
175
+
176
+ socks_authenticate(socks_username, socks_password) unless @@socks_version =~ /^4/
177
+
178
+ if host
179
+ socks_connect(host, port)
180
+ end
181
+ else
182
+ Socksify::debug_notice "Connecting directly to #{host}:#{port}"
183
+ initialize_tcp host, port, local_host, local_port
184
+ Socksify::debug_debug "Connected to #{host}:#{port}"
185
+ end
186
+ end
187
+
188
+ # Authentication
189
+ def socks_authenticate(socks_username, socks_password)
190
+ if socks_username || socks_password
191
+ Socksify::debug_debug "Sending username/password authentication"
192
+ write "\005\001\002"
193
+ else
194
+ Socksify::debug_debug "Sending no authentication"
195
+ write "\005\001\000"
196
+ end
197
+ Socksify::debug_debug "Waiting for authentication reply"
198
+ auth_reply = recv(2)
199
+ if auth_reply.empty?
200
+ raise SOCKSError.new("Server doesn't reply authentication")
201
+ end
202
+ if auth_reply[0..0] != "\004" and auth_reply[0..0] != "\005"
203
+ raise SOCKSError.new("SOCKS version #{auth_reply[0..0]} not supported")
204
+ end
205
+ if socks_username || socks_password
206
+ if auth_reply[1..1] != "\002"
207
+ raise SOCKSError.new("SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported")
208
+ end
209
+ auth = "\001"
210
+ auth += socks_username.to_s.length.chr
211
+ auth += socks_username.to_s
212
+ auth += socks_password.to_s.length.chr
213
+ auth += socks_password.to_s
214
+ write auth
215
+ auth_reply = recv(2)
216
+ if auth_reply[1..1] != "\000"
217
+ raise SOCKSError.new("SOCKS authentication failed")
218
+ end
219
+ else
220
+ if auth_reply[1..1] != "\000"
221
+ raise SOCKSError.new("SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported")
222
+ end
223
+ end
224
+ end
225
+
226
+ # Connect
227
+ def socks_connect(host, port)
228
+ port = Socket.getservbyname(port) if port.is_a?(String)
229
+ req = String.new
230
+ Socksify::debug_debug "Sending destination address"
231
+ req << TCPSocket.socks_version
232
+ Socksify::debug_debug TCPSocket.socks_version.unpack "H*"
233
+ req << "\001"
234
+ req << "\000" if @@socks_version == "5"
235
+ req << [port].pack('n') if @@socks_version =~ /^4/
236
+
237
+ if @@socks_version == "4"
238
+ host = Resolv::DNS.new.getaddress(host).to_s
239
+ end
240
+ Socksify::debug_debug host
241
+ if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
242
+ req << "\001" if @@socks_version == "5"
243
+ _ip = [$1.to_i,
244
+ $2.to_i,
245
+ $3.to_i,
246
+ $4.to_i
247
+ ].pack('CCCC')
248
+ req << _ip
249
+ elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
250
+ raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
251
+ req << "\004"
252
+ else # to hostname
253
+ if @@socks_version == "5"
254
+ req << "\003" + [host.size].pack('C') + host
255
+ else
256
+ req << "\000\000\000\001"
257
+ req << "\007\000"
258
+ Socksify::debug_notice host
259
+ req << host
260
+ req << "\000"
261
+ end
262
+ end
263
+ req << [port].pack('n') if @@socks_version == "5"
264
+ write req
265
+
266
+ socks_receive_reply
267
+ Socksify::debug_notice "Connected to #{host}:#{port} over SOCKS"
268
+ end
269
+
270
+ # returns [bind_addr: String, bind_port: Fixnum]
271
+ def socks_receive_reply
272
+ Socksify::debug_debug "Waiting for SOCKS reply"
273
+ if @@socks_version == "5"
274
+ connect_reply = recv(4)
275
+ if connect_reply.empty?
276
+ raise SOCKSError.new("Server doesn't reply")
277
+ end
278
+ Socksify::debug_debug connect_reply.unpack "H*"
279
+ if connect_reply[0..0] != "\005"
280
+ raise SOCKSError.new("SOCKS version #{connect_reply[0..0]} is not 5")
281
+ end
282
+ if connect_reply[1..1] != "\000"
283
+ raise SOCKSError.for_response_code(connect_reply.bytes.to_a[1])
284
+ end
285
+ Socksify::debug_debug "Waiting for bind_addr"
286
+ bind_addr_len = case connect_reply[3..3]
287
+ when "\001"
288
+ 4
289
+ when "\003"
290
+ recv(1).bytes.first
291
+ when "\004"
292
+ 16
293
+ else
294
+ raise SOCKSError.for_response_code(connect_reply.bytes.to_a[3])
295
+ end
296
+ bind_addr_s = recv(bind_addr_len)
297
+ bind_addr = case connect_reply[3..3]
298
+ when "\001"
299
+ bind_addr_s.bytes.to_a.join('.')
300
+ when "\003"
301
+ bind_addr_s
302
+ when "\004" # Untested!
303
+ i = 0
304
+ ip6 = ""
305
+ bind_addr_s.each_byte do |b|
306
+ if i > 0 and i % 2 == 0
307
+ ip6 += ":"
308
+ end
309
+ i += 1
310
+
311
+ ip6 += b.to_s(16).rjust(2, '0')
312
+ end
313
+ end
314
+ bind_port = recv(bind_addr_len + 2)
315
+ [bind_addr, bind_port.unpack('n')]
316
+ else
317
+ connect_reply = recv(8)
318
+ unless connect_reply[0] == "\000" and connect_reply[1] == "\x5A"
319
+ Socksify::debug_debug connect_reply.unpack 'H'
320
+ raise SOCKSError.new("Failed while connecting througth socks")
321
+ end
322
+ end
323
+ end
324
+ end
325
+
326
+ module Socksify
327
+ def self.resolve(host)
328
+ s = TCPSocket.new
329
+
330
+ begin
331
+ req = String.new
332
+ Socksify::debug_debug "Sending hostname to resolve: #{host}"
333
+ req << "\005"
334
+ if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
335
+ req << "\xF1\000\001" + [$1.to_i,
336
+ $2.to_i,
337
+ $3.to_i,
338
+ $4.to_i
339
+ ].pack('CCCC')
340
+ elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
341
+ raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
342
+ req << "\004"
343
+ else # to hostname
344
+ req << "\xF0\000\003" + [host.size].pack('C') + host
345
+ end
346
+ req << [0].pack('n') # Port
347
+ s.write req
348
+
349
+ addr, _port = s.socks_receive_reply
350
+ Socksify::debug_notice "Resolved #{host} as #{addr} over SOCKS"
351
+ addr
352
+ ensure
353
+ s.close
354
+ end
355
+ end
356
+
357
+ def self.proxy(server, port)
358
+ default_server = TCPSocket::socks_server
359
+ default_port = TCPSocket::socks_port
360
+ begin
361
+ TCPSocket::socks_server = server
362
+ TCPSocket::socks_port = port
363
+ yield
364
+ ensure
365
+ TCPSocket::socks_server = default_server
366
+ TCPSocket::socks_port = default_port
367
+ end
368
+ end
369
+ end