em-socksify 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: