sockit 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -c -b -fd
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.3@sockit --create
data/.rvmrc.template ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.3@sockit --create
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - ree-1.8.7
5
+ - 1.8.7
6
+ - 1.9.2
7
+ - 1.9.3
8
+
9
+ branches:
10
+ only:
11
+ - master
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Sockit
2
2
 
3
- TODO: Write a gem description
3
+ Transparent SOCKS 5 support for TCPSockets
4
4
 
5
5
  ## Installation
6
6
 
@@ -18,12 +18,47 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- TODO: Write usage instructions here
21
+ By loading the gem TCPSocket will get monkey patched adding seamless transparent SOCKS proxy support. I favor using SS5 for a SOCKS server, so at this point I'm uncertain of absolute compatibility with other SOCKS servers. I'm following the RFC here; so if (insert other SOCKS server flavor here) follows the RFC everything is in theory compatible.
22
+
23
+ ### Configuration
24
+
25
+ You can configure on the singleton class or an instance of the class. The SOCKS configuration is stored in a class variable; so it is shared across all TCPSocket instances and the singleton, thus changing the configuration in one instance will also affect all other instances. The configuration is stored in an OpenStruct; you can reference `socks` with a block as shown, where the configuration OpenStruct is yielded to the block; or without in which case the configuration OpenStruct itself is returned.
26
+
27
+ The defaults are as follows:
28
+
29
+ TCPSocket.socks do |config|
30
+ config.version = 5
31
+ config.ignore = ["127.0.0.1"]
32
+ config.debug = false
33
+ end
34
+
35
+ Specify your SOCKS server and port:
36
+
37
+ TCPSocket.socks do |config|
38
+ config.host = "127.0.0.1"
39
+ config.port = "1080"
40
+ end
41
+
42
+ If you want to use username/password authentication:
43
+
44
+ TCPSocket.socks do |config|
45
+ config.username = "username"
46
+ config.password = "password"
47
+ end
48
+
49
+ Turn on debug output:
50
+
51
+ TCPSocket.socks do |config|
52
+ config.debug = true
53
+ end
54
+
55
+ Ignore some more hosts:
56
+
57
+ TCPSocket.socks do |config|
58
+ config.ignore << "192.168.0.1"
59
+ end
60
+
22
61
 
23
62
  ## Contributing
24
63
 
25
- 1. Fork it
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Added some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
64
+ I await your pull request.
data/bin/sockit ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pry"
4
+ require "sockit"
5
+
6
+ ##
7
+ #
8
+ # Welcome to the Sockit pry shell!
9
+ #
10
+ ##
11
+ binding.pry
@@ -1,3 +1,3 @@
1
1
  module Sockit
2
- VERSION = "0.0.1" unless const_defined?(:VERSION)
2
+ VERSION = "0.0.2" unless const_defined?(:VERSION)
3
3
  end
data/lib/sockit.rb CHANGED
@@ -1,5 +1,346 @@
1
+ require "socket"
2
+ require "resolv"
3
+
1
4
  require "sockit/version"
2
5
 
6
+ class SockitError < RuntimeError; end
7
+
3
8
  module Sockit
4
- # Your code goes here...
9
+ DEFAULT_CONFIG = {
10
+ :version => 5,
11
+ :ignore => ["127.0.0.1"],
12
+ :debug => false
13
+ }
14
+
15
+ COLORS = {
16
+ :reset => "\e[0m\e[37m",
17
+ :red => "\e[1m\e[31m",
18
+ :green => "\e[1m\e[32m",
19
+ :yellow => "\e[1m\e[33m"
20
+ }
21
+
22
+ class << self
23
+
24
+ def debug(color, message)
25
+ timestamp = Time.now.utc
26
+ puts("%s%s.%06d %s%s" % [COLORS[color], timestamp.strftime("%Y-%m-%d|%H:%M:%S"), timestamp.usec, message, COLORS[:reset]])
27
+ end
28
+
29
+ def dump(action, data)
30
+ bytes = Array.new
31
+ chars = Array.new
32
+ for x in 0..(data.length - 1) do
33
+ bytes << ("%03d" % data[x].ord)
34
+ chars << ("%03s" % (data[x] =~ /^\w+$/ ? data[x].chr : "..."))
35
+ end
36
+ debug(:red, "#{action.to_s.upcase}: #{bytes.join(" ")}#{COLORS[:reset]}")
37
+ debug(:red, "#{action.to_s.upcase}: #{chars.join(" ")}#{COLORS[:reset]}")
38
+ end
39
+
40
+ # 0x00 = request granted
41
+ # 0x01 = general failure
42
+ # 0x02 = connection not allowed by ruleset
43
+ # 0x03 = network unreachable
44
+ # 0x04 = host unreachable
45
+ # 0x05 = connection refused by destination host
46
+ # 0x06 = TTL expired
47
+ # 0x07 = command not supported / protocol error
48
+ # 0x08 = address type not supported
49
+ def status_message(status_code)
50
+ case status_code
51
+ when 0x00 then
52
+ "Request granted (Code: 0x%02X)" % status_code
53
+ when 0x01 then
54
+ "General failure (Code: 0x%02X)" % status_code
55
+ when 0x02 then
56
+ "Connection not allowed by ruleset (Code: 0x%02X)" % status_code
57
+ when 0x03 then
58
+ "Network unreachable (Code: 0x%02X)" % status_code
59
+ when 0x04 then
60
+ "Host unreachable (Code: 0x%02X)" % status_code
61
+ when 0x05 then
62
+ "Connection refused by destination host (Code: 0x%02X)" % status_code
63
+ when 0x06 then
64
+ "TTL expired (Code: 0x%02X)" % status_code
65
+ when 0x07 then
66
+ "Command not supported / Protocol error (Code: 0x%02X)" % status_code
67
+ when 0x08 then
68
+ "Address type not supported (Code: 0x%02X)" % status_code
69
+ else
70
+ "Unknown (Code: 0x%02X)" % status_code
71
+ end
72
+ end
73
+
74
+ # The authentication methods supported are numbered as follows:
75
+ # 0x00: No authentication
76
+ # 0x01: GSSAPI[10]
77
+ # 0x02: Username/Password[11]
78
+ # 0x03-0x7F: methods assigned by IANA[12]
79
+ # 0x80-0xFE: methods reserved for private use
80
+ def authentication_method(auth_method)
81
+ case auth_method
82
+ when 0x00 then
83
+ "No authentication (Code: 0x%02X)" % auth_method
84
+ when 0x01 then
85
+ "GSSAPI authentication (Code: 0x%02X)" % auth_method
86
+ when 0x02 then
87
+ "Username/Password authentication (Code: 0x%02X)" % auth_method
88
+ when 0x03..0x7F then
89
+ "Method assigned by IANA (Code: 0x%02X)" % auth_method
90
+ when 0x80..0xFE then
91
+ "Method reserved for private use (Code: 0x%02X)" % auth_method
92
+ when 0xFF then
93
+ "Unsupported (Code: 0x%02X)" % auth_method
94
+ else
95
+ "Unknown (Code: 0x%02X)" % auth_method
96
+ end
97
+ end
98
+
99
+ # 0x00 = success
100
+ # any other value = failure, connection must be closed
101
+ def authentication_status(auth_status)
102
+ case auth_status
103
+ when 0x00 then
104
+ "Authentication success (Code: 0x%02X)" % auth_status
105
+ else
106
+ "Authentication failure (Code: 0x%02X)" % auth_status
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+
113
+ class TCPSocket
114
+
115
+ class << self
116
+
117
+ def socks(&block)
118
+ @@socks ||= OpenStruct.new(Sockit::DEFAULT_CONFIG)
119
+ if block_given?
120
+ yield(@@socks)
121
+ else
122
+ @@socks
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ def socks(&block)
129
+ @@socks ||= OpenStruct.new(Sockit::DEFAULT_CONFIG)
130
+ if block_given?
131
+ yield(@@socks)
132
+ else
133
+ @@socks
134
+ end
135
+ end
136
+
137
+ alias :initialize_tcp :initialize
138
+ def initialize(remote_host, remote_port, local_host=nil, local_port=nil)
139
+ if (socks.host && socks.port && !socks.ignore.include?(remote_host))
140
+ Sockit.debug(:yellow, "Connecting to SOCKS server #{socks.host}:#{socks.port}")
141
+ initialize_tcp(socks.host, socks.port)
142
+
143
+ (socks.version.to_i == 5) and socks_authenticate
144
+ socks.host and socks_connect(remote_host, remote_port)
145
+ Sockit.debug(:green, "Connected to #{remote_host}:#{remote_port} via SOCKS server #{socks.host}:#{socks.port}")
146
+ else
147
+ Sockit.debug(:yellow, "Directly connecting to #{remote_host}:#{remote_port}")
148
+ initialize_tcp(remote_host, remote_port, local_host, local_port)
149
+ Sockit.debug(:green, "Connected to #{remote_host}:#{remote_port}")
150
+ end
151
+ end
152
+
153
+ def socks_authenticate
154
+ # The authentication methods supported are numbered as follows:
155
+ # 0x00: No authentication
156
+ # 0x01: GSSAPI[10]
157
+ # 0x02: Username/Password[11]
158
+ # 0x03-0x7F: methods assigned by IANA[12]
159
+ # 0x80-0xFE: methods reserved for private use
160
+
161
+ # The initial greeting from the client is
162
+ # field 1: SOCKS version number (must be 0x05 for this version)
163
+ # field 2: number of authentication methods supported, 1 byte
164
+ # field 3: authentication methods, variable length, 1 byte per method supported
165
+ if (socks.username || socks.password)
166
+ data = Array.new
167
+ data << [socks.version, 0x02, 0x02, 0x00].pack("C*")
168
+ data = data.flatten.join
169
+
170
+ socks.debug and Sockit.debug(:yellow, "Requesting username/password authentication")
171
+ socks.debug and Sockit.dump(:write, data)
172
+ write(data)
173
+ else
174
+ data = Array.new
175
+ data << [socks.version, 0x01, 0x00].pack("C*")
176
+ data = data.flatten.join
177
+
178
+ socks.debug and Sockit.debug(:yellow, "Requesting no authentication")
179
+ socks.debug and Sockit.dump(:write, data)
180
+ write(data)
181
+ end
182
+
183
+ # The server's choice is communicated:
184
+ # field 1: SOCKS version, 1 byte (0x05 for this version)
185
+ # field 2: chosen authentication method, 1 byte, or 0xFF if no acceptable methods were offered
186
+ socks.debug and Sockit.debug(:yellow, "Waiting for SOCKS authentication reply")
187
+ auth_reply = recv(2).unpack("C*")
188
+ socks.debug and Sockit.dump(:read, auth_reply)
189
+ server_socks_version = auth_reply[0]
190
+ server_auth_method = auth_reply[1]
191
+
192
+ if server_socks_version != socks.version
193
+ raise SockitError, "SOCKS server does not support version #{socks.version}!"
194
+ end
195
+
196
+ if server_auth_method == 0xFF
197
+ raise SockitError, Sockit.authentication_method(server_auth_method)
198
+ else
199
+ socks.debug and Sockit.debug(:green, Sockit.authentication_method(server_auth_method))
200
+ end
201
+
202
+ # The subsequent authentication is method-dependent. Username and password authentication (method 0x02) is described in RFC 1929:
203
+ case server_auth_method
204
+ when 0x00 then
205
+ # No authentication
206
+ when 0x01 then
207
+ # GSSAPI
208
+ raise SockitError, "Authentication method GSSAPI not implemented"
209
+ when 0x02 then
210
+ # For username/password authentication the client's authentication request is
211
+ # field 1: version number, 1 byte (must be 0x01)
212
+ # field 2: username length, 1 byte
213
+ # field 3: username
214
+ # field 4: password length, 1 byte
215
+ # field 5: password
216
+ data = Array.new
217
+ data << [0x01].pack("C*")
218
+ data << [socks.username.length.to_i].pack("C*")
219
+ data << socks.username
220
+ data << [socks.password.length.to_i].pack("C*")
221
+ data << socks.password
222
+ data = data.flatten.join
223
+
224
+ socks.debug and Sockit.debug(:yellow, "Sending username and password")
225
+ socks.debug and Sockit.dump(:write, data)
226
+ write(data)
227
+
228
+ # Server response for username/password authentication:
229
+ # field 1: version, 1 byte
230
+ # field 2: status code, 1 byte.
231
+ # 0x00 = success
232
+ # any other value = failure, connection must be closed
233
+ socks.debug and Sockit.debug(:yellow, "Waiting for SOCKS authentication reply")
234
+ auth_reply = recv(2).unpack("C*")
235
+ socks.debug and Sockit.dump(:read, auth_reply)
236
+ version = auth_reply[0]
237
+ status_code = auth_reply[1]
238
+
239
+ if status_code == 0x00
240
+ socks.debug and Sockit.debug(:green, Sockit.authentication_status(status_code))
241
+ else
242
+ raise SockitError, Sockit.authentication_status(status_code)
243
+ end
244
+ end
245
+
246
+ end
247
+
248
+ def socks_connect(remote_host, remote_port)
249
+ # The client's connection request is
250
+ # field 1: SOCKS version number, 1 byte (must be 0x05 for this version)
251
+ # field 2: command code, 1 byte:
252
+ # 0x01 = establish a TCP/IP stream connection
253
+ # 0x02 = establish a TCP/IP port binding
254
+ # 0x03 = associate a UDP port
255
+ # field 3: reserved, must be 0x00
256
+ # field 4: address type, 1 byte:
257
+ # 0x01 = IPv4 address
258
+ # 0x03 = Domain name
259
+ # 0x04 = IPv6 address
260
+ # field 5: destination address of
261
+ # 4 bytes for IPv4 address
262
+ # 1 byte of name length followed by the name for Domain name
263
+ # 16 bytes for IPv6 address
264
+ # field 6: port number in a network byte order, 2 bytes
265
+ data = Array.new
266
+ data << [ socks.version.to_i, 0x01, 0x00 ].pack("C*")
267
+
268
+ # when doing proxy mode on SS5; we seemingly need to resolve all names first.
269
+ if remote_host !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
270
+ remote_host = Resolv::DNS.new.getaddress(remote_host).to_s
271
+ end
272
+
273
+ if remote_host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
274
+ data << [0x01].pack("C*")
275
+ data << [$1.to_i, $2.to_i, $3.to_i, $4.to_i].pack("C*")
276
+ elsif remote_host =~ /^[:0-9a-f]+$/
277
+ data << [0x04].pack("C*")
278
+ data << [$1].pack("C*")
279
+ else
280
+ data << [0x03].pack("C*")
281
+ data << [remote_host.length.to_i].pack("C*")
282
+ data << remote_host
283
+ end
284
+ data << [remote_port].pack("n")
285
+ data = data.flatten.join
286
+
287
+ Sockit.debug(:yellow, "Requesting SOCKS connection to #{remote_host}:#{remote_port}")
288
+ socks.debug and Sockit.dump(:write, data)
289
+ write(data)
290
+
291
+ # Server response:
292
+ # field 1: SOCKS protocol version, 1 byte (0x05 for this version)
293
+ # field 2: status, 1 byte:
294
+ # 0x00 = request granted
295
+ # 0x01 = general failure
296
+ # 0x02 = connection not allowed by ruleset
297
+ # 0x03 = network unreachable
298
+ # 0x04 = host unreachable
299
+ # 0x05 = connection refused by destination host
300
+ # 0x06 = TTL expired
301
+ # 0x07 = command not supported / protocol error
302
+ # 0x08 = address type not supported
303
+ # field 3: reserved, must be 0x00
304
+ # field 4: address type, 1 byte:
305
+ # 0x01 = IPv4 address
306
+ # 0x03 = Domain name
307
+ # 0x04 = IPv6 address
308
+ # field 5: destination address of
309
+ # 4 bytes for IPv4 address
310
+ # 1 byte of name length followed by the name for Domain name
311
+ # 16 bytes for IPv6 address
312
+ # field 6: network byte order port number, 2 bytes
313
+ socks.debug and Sockit.debug(:yellow, "Waiting for SOCKS connection reply")
314
+ packet = recv(4).unpack("C*")
315
+ socks.debug and Sockit.dump(:read, packet)
316
+ socks_version = packet[0]
317
+ status_code = packet[1]
318
+ reserved = packet[2]
319
+ address_type = packet[3]
320
+
321
+ if status_code == 0x00
322
+ socks.debug and Sockit.debug(:green, Sockit.status_message(status_code))
323
+ else
324
+ raise SockitError, Sockit.status_message(status_code)
325
+ end
326
+
327
+ address_length = case address_type
328
+ when 0x01 then
329
+ 4
330
+ when 0x03 then
331
+ data = recv(1).unpack("C*")
332
+ socks.debug and Sockit.dump(:read, data)
333
+ data[0]
334
+ when 0x04 then
335
+ 16
336
+ end
337
+ address = recv(address_length).unpack("C*")
338
+ socks.debug and Sockit.dump(:read, address)
339
+
340
+ port = recv(2).unpack("n")
341
+ socks.debug and Sockit.dump(:read, port)
342
+
343
+ socks.debug and Sockit.debug(:green, [address, port].inspect)
344
+ end
345
+
5
346
  end
data/sockit.gemspec CHANGED
@@ -4,9 +4,9 @@ require File.expand_path('../lib/sockit/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Zachary Patten"]
6
6
  gem.email = ["zachary@jovelabs.com"]
7
- gem.description = %q{SOCKS 4/5 support for TCPSockets}
8
- gem.summary = %q{SOCKS 4/5 support for TCPSockets}
9
- gem.homepage = ""
7
+ gem.description = %q{Transparent SOCKS 5 support for TCPSockets}
8
+ gem.summary = %q{Transparent SOCKS 5 support for TCPSockets}
9
+ gem.homepage = "https://github.com/jovelabs/sockit"
10
10
 
11
11
  gem.files = `git ls-files`.split($\)
12
12
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -14,4 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "sockit"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Sockit::VERSION
17
+
18
+ gem.add_development_dependency("pry")
17
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sockit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,46 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-03 00:00:00.000000000 Z
13
- dependencies: []
14
- description: SOCKS 4/5 support for TCPSockets
12
+ date: 2012-09-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pry
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Transparent SOCKS 5 support for TCPSockets
15
31
  email:
16
32
  - zachary@jovelabs.com
17
- executables: []
33
+ executables:
34
+ - sockit
18
35
  extensions: []
19
36
  extra_rdoc_files: []
20
37
  files:
21
38
  - .gitignore
39
+ - .rspec
40
+ - .rvmrc
41
+ - .rvmrc.template
42
+ - .travis.yml
22
43
  - Gemfile
23
44
  - LICENSE
24
45
  - README.md
25
46
  - Rakefile
47
+ - bin/sockit
26
48
  - lib/sockit.rb
27
49
  - lib/sockit/version.rb
28
50
  - sockit.gemspec
29
- homepage: ''
51
+ homepage: https://github.com/jovelabs/sockit
30
52
  licenses: []
31
53
  post_install_message:
32
54
  rdoc_options: []
@@ -49,5 +71,5 @@ rubyforge_project:
49
71
  rubygems_version: 1.8.24
50
72
  signing_key:
51
73
  specification_version: 3
52
- summary: SOCKS 4/5 support for TCPSockets
74
+ summary: Transparent SOCKS 5 support for TCPSockets
53
75
  test_files: []