sockit 0.0.1 → 0.0.2

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.
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: []