em-socksify 0.1.0 → 0.2.0

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/README.md CHANGED
@@ -4,23 +4,25 @@ Dealing with SOCKS proxies is pain. EM-Socksify provides a simple shim to setup
4
4
 
5
5
  ## Example: Routing HTTP request via SOCKS5 proxy
6
6
 
7
- class Handler < EM::Connection
8
- include EM::Socksify
9
-
10
- def connection_completed
11
- socksify('google.ca', 80) do
12
- send_data "GET / HTTP/1.1\r\nConnection:close\r\nHost: google.ca\r\n\r\n"
13
- end
14
- end
15
-
16
- def receive_data(data)
17
- p data
18
- end
19
- end
7
+ ```ruby
8
+ class Handler < EM::Connection
9
+ include EM::Socksify
20
10
 
21
- EM.run do
22
- EventMachine.connect SOCKS_HOST, SOCKS_PORT, Handler
11
+ def connection_completed
12
+ socksify('google.ca', 80) do
13
+ send_data "GET / HTTP/1.1\r\nConnection:close\r\nHost: google.ca\r\n\r\n"
23
14
  end
15
+ end
16
+
17
+ def receive_data(data)
18
+ p data
19
+ end
20
+ end
21
+
22
+ EM.run do
23
+ EventMachine.connect SOCKS_HOST, SOCKS_PORT, Handler
24
+ end
25
+ ```
24
26
 
25
27
  What's happening here? First, we open a raw TCP connection to the SOCKS proxy (after all, all data will flow through it). Then, we provide a Handler connection class, which includes "EM::Socksify". Once the TCP connection is established, EventMachine calls the **connection_completed** method in our handler. Here, we call socksify with the actual destination host & port (address that we actually want to get to), and the module does the rest.
26
28
 
@@ -28,8 +30,9 @@ After you call socksify, the module temporarily intercepts your receive_data cal
28
30
 
29
31
  For SOCKS proxies which require authentication, use:
30
32
 
31
- socksify(destination_host, destination_port, username, password)
32
-
33
+ ```ruby
34
+ socksify(destination_host, destination_port, username, password, version)
35
+ ```
33
36
 
34
37
  ## Wishlist
35
38
 
@@ -44,4 +47,4 @@ For SOCKS proxies which require authentication, use:
44
47
  # License
45
48
 
46
49
  (The MIT License)
47
- Copyright © 2011 Ilya Grigorik
50
+ Copyright © 2011 Ilya Grigorik
data/Rakefile CHANGED
@@ -1,2 +1,7 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run all RSpec tests"
7
+ RSpec::Core::RakeTask.new(:spec)
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = "em-socksify"
16
16
 
17
- s.add_dependency "eventmachine"
17
+ s.add_dependency "eventmachine", ">= 1.0.0.beta.4"
18
18
  s.add_development_dependency "rspec"
19
19
 
20
20
  s.files = `git ls-files`.split("\n")
@@ -1,3 +1,5 @@
1
1
  require 'eventmachine'
2
2
 
3
- require 'em-socksify/socksify'
3
+ require 'em-socksify/socksify'
4
+ require 'em-socksify/errors'
5
+ require 'em-socksify/socks5'
@@ -0,0 +1,38 @@
1
+ module EventMachine
2
+ module Socksify
3
+
4
+ class SOCKSError < Exception
5
+ def self.define (message)
6
+ Class.new(self) do
7
+ def initialize
8
+ super(message)
9
+ end
10
+ end
11
+ end
12
+
13
+ ServerFailure = define('general SOCKS server failure')
14
+ NotAllowed = define('connection not allowed by ruleset')
15
+ NetworkUnreachable = define('Network unreachable')
16
+ HostUnreachable = define('Host unreachable')
17
+ ConnectionRefused = define('Connection refused')
18
+ TTLExpired = define('TTL expired')
19
+ CommandNotSupported = define('Command not supported')
20
+ AddressTypeNotSupported = define('Address type not supported')
21
+
22
+ def self.for_response_code(code)
23
+ case code.is_a?(String) ? code.ord : code
24
+ when 1 then ServerFailure
25
+ when 2 then NotAllowed
26
+ when 3 then NetworkUnreachable
27
+ when 4 then HostUnreachable
28
+ when 5 then ConnectionRefused
29
+ when 6 then TTLExpired
30
+ when 7 then CommandNotSupported
31
+ when 8 then AddressTypeNotSupported
32
+ else self
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,115 @@
1
+ module EventMachine
2
+ module Socksify
3
+
4
+ module SOCKS5
5
+ def socks_send_handshake
6
+ # Method Negotiation as described on
7
+ # http://www.faqs.org/rfcs/rfc1928.html Section 3
8
+ @socks_state = :method_negotiation
9
+
10
+ socks_methods.tap do |methods|
11
+ send_data [5, methods.size].pack('CC') + methods.pack('C*')
12
+ end
13
+ end
14
+
15
+ def socks_send_connect_request
16
+ @socks_state = :connecting
17
+
18
+ send_data [5, 1, 0].pack('CCC')
19
+
20
+ if matches = @socks_target_host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)
21
+ send_data "\xF1\x00\x01" + matches.to_a[1 .. -1].map { |s| s.to_i }.pack('CCCC')
22
+
23
+ elsif @socks_target_host =~ /^[:0-9a-f]+$/
24
+ raise SOCKSError, 'TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor)'
25
+
26
+ else
27
+ send_data [3, @socks_target_host.size, @socks_target_host].pack('CCA*')
28
+ end
29
+
30
+ send_data [@socks_target_port].pack('n')
31
+ end
32
+
33
+ def socks_send_authentication
34
+ @socks_state = :authenticating
35
+
36
+ send_data [5,
37
+ @socks_username.length, @socks_username,
38
+ @socks_password.length, @socks_password
39
+ ].pack('CCA*CA*')
40
+ end
41
+
42
+ private
43
+
44
+ # parses socks 5 server responses as specified
45
+ # on http://www.faqs.org/rfcs/rfc1928.html
46
+ def socks_parse_response
47
+ case @socks_state
48
+ when :method_negotiation
49
+ return unless @socks_data.size >= 2
50
+
51
+ _, method = @socks_data.slice!(0, 2).unpack('CC')
52
+
53
+ if socks_methods.include?(method)
54
+ case method
55
+ when 0 then socks_send_connect_request
56
+ when 2 then socks_send_authentication
57
+ end
58
+ else
59
+ raise SOCKSError, 'proxy did not accept method'
60
+ end
61
+
62
+ when :authenticating
63
+ return unless @socks_data.size >= 2
64
+
65
+ socks_version, status_code = @socks_data.slice!(0, 2).unpack('CC')
66
+
67
+ raise SOCKSError, "SOCKS version 5 not supported" unless socks_version == 5
68
+ raise SOCKSError, 'access denied by proxy' unless status_code == 0
69
+
70
+ send_socks_connect_request
71
+
72
+ when :connecting
73
+ return unless @socks_data.size >= 2
74
+
75
+ socks_version, status_code = @socks_data.slice(0, 2).unpack('CC')
76
+
77
+ raise SOCKSError, "SOCKS version #{socks_version} is not 5" unless socks_version == 5
78
+ raise SOCKSError.for_response_code(status_code) unless status_code == 0
79
+
80
+ min_size = @socks_data[3].ord == 3 ? 5 : 4
81
+
82
+ return unless @socks_data.size >= min_size
83
+
84
+ size = case @socks_data[3].ord
85
+ when 1 then 4
86
+ when 3 then @socks_data[4].ord
87
+ when 4 then 16
88
+ else raise SOCKSError.for_response_code(@socks_data[3])
89
+ end
90
+
91
+ return unless @socks_data.size >= (min_size + size)
92
+
93
+ bind_addr = @socks_data.slice(min_size, size)
94
+
95
+ ip = case @socks_data[3].ord
96
+ when 1 then bind_addr.bytes.to_a.join('.')
97
+ when 3 then bind_addr
98
+ when 4 then # TODO: ???
99
+ end
100
+
101
+ socks_unhook(ip)
102
+ end
103
+ end
104
+
105
+ def socks_methods
106
+ methods = []
107
+ methods << 2 if !@socks_username.nil? # 2 => Username/Password Authentication
108
+ methods << 0 # 0 => No Authentication Required
109
+
110
+ methods
111
+ end
112
+ end
113
+
114
+ end
115
+ end
@@ -1,122 +1,43 @@
1
1
  module EventMachine
2
+
2
3
  module Socksify
4
+ def socksify(host, port, username = nil, password = nil, version = 5, &blk)
5
+ @socks_target_host = host
6
+ @socks_target_port = port
7
+ @socks_username = username
8
+ @socks_password = password
9
+ @socks_version = version
10
+ @socks_callback = blk
11
+ @socks_data = ''
12
+
13
+ socks_hook
14
+ socks_send_handshake
15
+ end
3
16
 
4
- def socksify(host, port, username = nil, password = nil, &blk)
5
- @host = host
6
- @port = port
7
- @username = username
8
- @password = password
9
- @callback = blk
17
+ def socks_hook
18
+ if @socks_version == 5
19
+ extend SOCKS5
20
+ else
21
+ raise ArgumentError, 'SOCKS version unsupported'
22
+ end
10
23
 
11
24
  class << self
12
- def receive_data(data); proxy_receive_data(data); end
25
+ alias receive_data socks_receive_data
13
26
  end
14
-
15
- send_socks_handshake
16
27
  end
17
28
 
18
- def proxy_receive_data(data)
19
- @data ||= ''
20
- @data << data
21
- parse_socks_response
22
- end
23
-
24
- def send_socks_handshake
25
- # Method Negotiation as described on
26
- # http://www.faqs.org/rfcs/rfc1928.html Section 3
27
- @socks_state = :method_negotiation
29
+ def socks_unhook(ip = nil)
30
+ class << self
31
+ remove_method :receive_data
32
+ end
28
33
 
29
- methods = socks_methods
30
- send_data [5, methods.size].pack('CC') + methods.pack('C*')
34
+ @socks_callback.call(ip)
31
35
  end
32
36
 
33
- def send_socks_connect_request
34
- begin
35
- # TO-DO: Implement address types for IPv6 and Domain
36
- ip_address = Socket.gethostbyname(@host).last
37
- send_data [5, 1, 0, 1, ip_address, @port].flatten.pack('CCCCA4n')
38
-
39
- rescue
40
- fail("could not resolve host")
41
- end
37
+ def socks_receive_data(data)
38
+ @socks_data << data
39
+ socks_parse_response
42
40
  end
43
-
44
- private
45
-
46
- # parses socks 5 server responses as specified
47
- # on http://www.faqs.org/rfcs/rfc1928.html
48
- def parse_socks_response
49
- if @socks_state == :method_negotiation
50
- return if not @data.size >= 2
51
-
52
- _, method = @data.slice!(0,2).unpack('CC')
53
-
54
- if socks_methods.include?(method)
55
- if method == 0
56
- @socks_state = :connecting
57
- send_socks_connect_request
58
-
59
- elsif method == 2
60
- @socks_state = :authenticating
61
- send_data [5, @username.length, @username, @password.length, @password].pack('CCA*CA*')
62
- end
63
-
64
- else
65
- fail("proxy did not accept method")
66
- end
67
-
68
- elsif @socks_state == :authenticating
69
- return if not @data.size >= 2
70
-
71
- _, status_code = @data.slice!(0, 2).unpack('CC')
72
-
73
- if status_code == 0 # success
74
- @socks_state = :connecting
75
- send_socks_connect_request
76
-
77
- else # error
78
- fail "access denied by proxy"
79
- end
80
-
81
- elsif @socks_state == :connecting
82
- return if not @data.size >= 10
83
-
84
- _, response_code, _, address_type, _, _ = @data.slice(0, 10).unpack('CCCCNn')
85
-
86
- if response_code == 0 # success
87
- @socks_state = :connected
88
-
89
- class << self
90
- remove_method :receive_data
91
- end
92
-
93
- @callback.call
94
-
95
- else # error
96
- error_messages = {
97
- 1 => "general socks server failure",
98
- 2 => "connection not allowed by ruleset",
99
- 3 => "network unreachable",
100
- 4 => "host unreachable",
101
- 5 => "connection refused",
102
- 6 => "TTL expired",
103
- 7 => "command not supported",
104
- 8 => "address type not supported"
105
- }
106
-
107
- error_message = error_messages[response_code] || "unknown error (code: #{response_code})"
108
- fail "socks5 connect error: #{error_message}"
109
- end
110
- end
111
- end
112
-
113
- def socks_methods
114
- methods = []
115
- methods << 2 if !@username.nil? # 2 => Username/Password Authentication
116
- methods << 0 # 0 => No Authentication Required
117
-
118
- methods
119
- end
120
-
121
41
  end
122
- end
42
+
43
+ end
@@ -1,5 +1,7 @@
1
1
  module EventMachine
2
- module Socksify
3
- VERSION = "0.1.0"
4
- end
2
+
3
+ module Socksify
4
+ VERSION = "0.2.0"
5
+ end
6
+
5
7
  end
@@ -8,7 +8,7 @@ describe EventMachine do
8
8
  include EM::Socksify
9
9
 
10
10
  def connection_completed
11
- socksify('google.ca', 80) do
11
+ socksify('google.ca', 80) do |ip|
12
12
  send_data "GET / HTTP/1.1\r\nConnection:close\r\nHost: google.ca\r\n\r\n"
13
13
  end
14
14
  end
@@ -30,4 +30,4 @@ describe EventMachine do
30
30
  end
31
31
  end
32
32
 
33
- end
33
+ end
metadata CHANGED
@@ -1,56 +1,45 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: em-socksify
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 1
8
- - 0
9
- version: 0.1.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Ilya Grigorik
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2011-01-23 00:00:00 -05:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-03-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: eventmachine
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- segments:
28
- - 0
29
- version: "0"
16
+ requirement: &2156395280 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0.beta.4
30
22
  type: :runtime
31
- version_requirements: *id001
32
- - !ruby/object:Gem::Dependency
33
- name: rspec
34
23
  prerelease: false
35
- requirement: &id002 !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- segments:
40
- - 0
41
- version: "0"
24
+ version_requirements: *2156395280
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2156394860 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
42
33
  type: :development
43
- version_requirements: *id002
44
- description: "EventMachine SOCKSify shim: adds SOCKS support to any protocol"
45
- email:
34
+ prerelease: false
35
+ version_requirements: *2156394860
36
+ description: ! 'EventMachine SOCKSify shim: adds SOCKS support to any protocol'
37
+ email:
46
38
  - ilya@igvita.com
47
39
  executables: []
48
-
49
40
  extensions: []
50
-
51
41
  extra_rdoc_files: []
52
-
53
- files:
42
+ files:
54
43
  - .gitignore
55
44
  - .rspec
56
45
  - Gemfile
@@ -58,40 +47,37 @@ files:
58
47
  - Rakefile
59
48
  - em-socksify.gemspec
60
49
  - lib/em-socksify.rb
50
+ - lib/em-socksify/errors.rb
51
+ - lib/em-socksify/socks5.rb
61
52
  - lib/em-socksify/socksify.rb
62
53
  - lib/em-socksify/version.rb
63
54
  - spec/helper.rb
64
55
  - spec/socksify_spec.rb
65
- has_rdoc: true
66
56
  homepage: http://github.com/igrigorik/em-socksify
67
57
  licenses: []
68
-
69
58
  post_install_message:
70
59
  rdoc_options: []
71
-
72
- require_paths:
60
+ require_paths:
73
61
  - lib
74
- required_ruby_version: !ruby/object:Gem::Requirement
75
- requirements:
76
- - - ">="
77
- - !ruby/object:Gem::Version
78
- segments:
79
- - 0
80
- version: "0"
81
- required_rubygems_version: !ruby/object:Gem::Requirement
82
- requirements:
83
- - - ">="
84
- - !ruby/object:Gem::Version
85
- segments:
86
- - 0
87
- version: "0"
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
88
74
  requirements: []
89
-
90
75
  rubyforge_project: em-socksify
91
- rubygems_version: 1.3.6
76
+ rubygems_version: 1.8.10
92
77
  signing_key:
93
78
  specification_version: 3
94
- summary: "EventMachine SOCKSify shim: adds SOCKS support to any protocol"
95
- test_files:
79
+ summary: ! 'EventMachine SOCKSify shim: adds SOCKS support to any protocol'
80
+ test_files:
96
81
  - spec/helper.rb
97
82
  - spec/socksify_spec.rb
83
+ has_rdoc: