socksify 1.7.0 → 1.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: de7d71ec280f514fb083bd26f34b76ce14a2c82d
4
- data.tar.gz: 10454a02092c6e14884441f90997b17ed92ee27a
2
+ SHA256:
3
+ metadata.gz: c9c9f42d889fe8506efd5c6d7bbd13484e6dfed208ac5d125cfeb661a7f03d1d
4
+ data.tar.gz: fdb78e9915cb13f17325173d721df354896fb085f82da79a1f7fb2a73b0f8076
5
5
  SHA512:
6
- metadata.gz: 9bb3836afe02eadd52465295fe8428e3e9424a139271abf08951722a3195bf56b0093611c80a557e1d836faf0a498f3795953caf63e9a32ec0175562e9dc4b6c
7
- data.tar.gz: 00deeb904f05fe9be737fbae6b2633ecc234b81c9841c4fd99718076b0b657b3d24399b5e890ff884e7e5899644191b7272a0f59b22e59beb6324230fa109210
6
+ metadata.gz: 5b15e3c4c7b72a03386d0a3f78472d659a44cddd5c9f846d57fc97e3c149a5cedde40958ec78de01a3ac4cb0f828f23f5f7b79adfc4c6d66501973e630f8bb2c
7
+ data.tar.gz: 2717aac706890dd74854a02dac6c714aebf3f54d26861144dfa32b6d684965e95d1e1aa4c8f078748fd92d28085a6dbd7495957ea65254bbdbba2bf92e9e8609
data/bin/socksify_ruby CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  if ARGV.size < 2
4
- puts "Usage: #{$0} <SOCKS host> <SOCKS port> [script args ...]"
4
+ puts "Usage: #{$PROGRAM_NAME} <SOCKS host> <SOCKS port> [script args ...]"
5
5
  exit
6
6
  end
7
7
 
@@ -15,4 +15,3 @@ else
15
15
  require 'irb'
16
16
  IRB.start(__FILE__)
17
17
  end
18
-
data/doc/index.html CHANGED
@@ -77,6 +77,12 @@ Socksify::proxy("127.0.0.1", 9050) {
77
77
  </pre>
78
78
  </p>
79
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
+
80
86
  <h3>Use Net::HTTP explicitly via SOCKS</h3>
81
87
  <p>
82
88
  Require the additional library <code>socksify/http</code>
@@ -99,6 +105,14 @@ end
99
105
  explicitly or use <code>Net::HTTP</code> directly.
100
106
  </p>
101
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
+
102
116
  <h3>Resolve addresses via SOCKS</h3>
103
117
  <pre>Socksify::resolve("spaceboyz.net")
104
118
  # => "87.106.131.203"</pre>
@@ -1,32 +1,47 @@
1
+ # namespace
1
2
  module Socksify
3
+ # rubocop:disable Style/Documentation
2
4
  class Color
3
5
  class Reset
4
- def self::to_s
6
+ def self.to_s
5
7
  "\e[0m\e[37m"
6
8
  end
7
9
  end
8
-
10
+
9
11
  class Red < Color
10
- def num; 31; end
12
+ def num
13
+ 31
14
+ end
11
15
  end
16
+
12
17
  class Green < Color
13
- def num; 32; end
18
+ def num
19
+ 32
20
+ end
14
21
  end
22
+
15
23
  class Yellow < Color
16
- def num; 33; end
24
+ def num
25
+ 33
26
+ end
17
27
  end
18
-
19
- def self::to_s
28
+ # rubocop:enable Style/Documentation
29
+
30
+ def self.to_s
20
31
  new.to_s
21
32
  end
22
-
33
+
34
+ def num
35
+ 0
36
+ end
37
+
23
38
  def to_s
24
39
  "\e[1m\e[#{num}m"
25
40
  end
26
41
  end
27
42
 
28
43
  def self.debug=(enabled)
29
- @@debug = enabled
44
+ @debug = enabled
30
45
  end
31
46
 
32
47
  def self.debug_debug(str)
@@ -41,12 +56,8 @@ module Socksify
41
56
  debug(Color::Red, str)
42
57
  end
43
58
 
44
- private
45
-
46
59
  def self.debug(color, str)
47
- if defined? @@debug
48
- puts "#{color}#{now_s}#{Color::Reset} #{str}"
49
- end
60
+ puts "#{color}#{now_s}#{Color::Reset} #{str}" if defined?(@debug) && @debug
50
61
  end
51
62
 
52
63
  def self.now_s
data/lib/socksify/http.rb CHANGED
@@ -1,59 +1,64 @@
1
- =begin
2
- Copyright (C) 2007 Stephan Maka <stephan@spaceboyz.net>
3
- Copyright (C) 2011 Musy Bite <musybite@gmail.com>
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
1
+ # Copyright (C) 2007 Stephan Maka <stephan@spaceboyz.net>
2
+ # Copyright (C) 2011 Musy Bite <musybite@gmail.com>
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
16
 
19
17
  require 'socksify'
20
18
  require 'net/http'
19
+ require_relative 'ruby3net_http_connectable'
21
20
 
22
21
  module Net
22
+ # patched class
23
23
  class HTTP
24
- def self.SOCKSProxy(p_host, p_port)
25
- delta = SOCKSProxyDelta
26
- proxyclass = Class.new(self)
27
- proxyclass.send(:include, delta)
28
- proxyclass.module_eval {
29
- include delta::InstanceMethods
30
- extend delta::ClassMethods
24
+ def self.socks_proxy(p_host, p_port, username: nil, password: nil)
25
+ proxyclass.module_eval do
26
+ include Ruby3NetHTTPConnectable if RUBY_VERSION.to_f > 3.0 # patch #connect method
27
+ include SOCKSProxyDelta::InstanceMethods
28
+ extend SOCKSProxyDelta::ClassMethods
29
+
31
30
  @socks_server = p_host
32
31
  @socks_port = p_port
33
- }
32
+ @socks_username = username
33
+ @socks_password = password
34
+ end
35
+
34
36
  proxyclass
35
37
  end
36
38
 
39
+ def self.proxyclass
40
+ @proxyclass ||= Class.new(self).tap { |klass| klass.send(:include, SOCKSProxyDelta) }
41
+ end
42
+
43
+ class << self
44
+ alias SOCKSProxy socks_proxy # legacy support for non snake case method name
45
+ end
46
+
37
47
  module SOCKSProxyDelta
48
+ # class methods
38
49
  module ClassMethods
39
- def socks_server
40
- @socks_server
41
- end
42
-
43
- def socks_port
44
- @socks_port
45
- end
50
+ attr_reader :socks_server, :socks_port,
51
+ :socks_username, :socks_password
46
52
  end
47
53
 
54
+ # instance methods - no long supports Ruby < 2
48
55
  module InstanceMethods
49
- if RUBY_VERSION[0..0] >= '2'
50
- def address
51
- TCPSocket::SOCKSConnectionPeerAddress.new(self.class.socks_server, self.class.socks_port, @address)
52
- end
53
- else
54
- def conn_address
55
- TCPSocket::SOCKSConnectionPeerAddress.new(self.class.socks_server, self.class.socks_port, address())
56
- end
56
+ def address
57
+ TCPSocket::SOCKSConnectionPeerAddress.new(
58
+ self.class.socks_server, self.class.socks_port,
59
+ @address,
60
+ self.class.socks_username, self.class.socks_password
61
+ )
57
62
  end
58
63
  end
59
64
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby 3.0 private method Net::HTTP#connect
4
+ module Ruby3NetHTTPConnectable
5
+ # rubocop:disable all - CAN'T LINT RUBY SOURCE CODE!
6
+ def connect
7
+ if use_ssl? # 3.1 - MOVED FROM FURTHER DOWN IN METHOD
8
+ # reference early to load OpenSSL before connecting,
9
+ # as OpenSSL may take time to load.
10
+ @ssl_context = OpenSSL::SSL::SSLContext.new
11
+ end
12
+
13
+ if proxy? then
14
+ conn_addr = proxy_address
15
+ conn_port = proxy_port
16
+ else
17
+ conn_addr = conn_address
18
+ conn_port = port
19
+ end
20
+
21
+ D "opening connection to #{conn_addr}:#{conn_port}..."
22
+ ######## RUBY < 3.1 ########
23
+ s = Timeout.timeout(@open_timeout, Net::OpenTimeout) {
24
+ begin
25
+ TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
26
+ rescue => e
27
+ raise e, "Failed to open TCP connection to " +
28
+ "#{conn_addr}:#{conn_port} (#{e.message})"
29
+ end
30
+ }
31
+ ######## END RUBY < 3.1 ########
32
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
33
+ D "opened"
34
+ if use_ssl?
35
+ if proxy?
36
+ plain_sock = Net::BufferedIO.new(s, read_timeout: @read_timeout, # 3.1 - FULLY QUALIFY CLASS
37
+ write_timeout: @write_timeout,
38
+ continue_timeout: @continue_timeout,
39
+ debug_output: @debug_output)
40
+ buf = "CONNECT #{conn_address}:#{@port} HTTP/#{Net::HTTP::HTTPVersion}\r\n" # 3.1 - FULLY QUALIFY CONSTANT
41
+ buf << "Host: #{@address}:#{@port}\r\n"
42
+ if proxy_user
43
+ credential = ["#{proxy_user}:#{proxy_pass}"].pack('m0')
44
+ buf << "Proxy-Authorization: Basic #{credential}\r\n"
45
+ end
46
+ buf << "\r\n"
47
+ plain_sock.write(buf)
48
+ Net::HTTPResponse.read_new(plain_sock).value # 3.1 - FULLY QUALIFY CLASS
49
+ # assuming nothing left in buffers after successful CONNECT response
50
+ end
51
+
52
+ ssl_parameters = Hash.new
53
+ iv_list = instance_variables
54
+ Net::HTTP::SSL_IVNAMES.each_with_index do |ivname, i| # 3.1 - FULLY QUALIFY CONSTANT
55
+ if iv_list.include?(ivname)
56
+ value = instance_variable_get(ivname)
57
+ unless value.nil?
58
+ ssl_parameters[Net::HTTP::SSL_ATTRIBUTES[i]] = value # 3.1 - FULLY QUALIFY CONSTANT
59
+ end
60
+ end
61
+ end
62
+ # @ssl_context = OpenSSL::SSL::SSLContext.new # 3.1 - MOVED TO TOP OF METHOD
63
+ @ssl_context.set_params(ssl_parameters)
64
+ @ssl_context.session_cache_mode =
65
+ OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
66
+ OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
67
+ @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess }
68
+ D "starting SSL for #{conn_addr}:#{conn_port}..."
69
+ s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
70
+ s.sync_close = true
71
+ # Server Name Indication (SNI) RFC 3546
72
+ s.hostname = @address if s.respond_to? :hostname=
73
+ if @ssl_session and
74
+ Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout
75
+ s.session = @ssl_session
76
+ end
77
+ ssl_socket_connect(s, @open_timeout)
78
+ if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && @ssl_context.verify_hostname
79
+ s.post_connection_check(@address)
80
+ end
81
+ D "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}"
82
+ end
83
+ @socket = Net::BufferedIO.new(s, read_timeout: @read_timeout, # 3.1 - FULLY QUALIFY CLASS
84
+ write_timeout: @write_timeout,
85
+ continue_timeout: @continue_timeout,
86
+ debug_output: @debug_output)
87
+ @last_communicated = nil # 3.1 - NEW
88
+ on_connect
89
+ rescue => exception
90
+ if s
91
+ D "Conn close because of connect error #{exception}"
92
+ s.close
93
+ end
94
+ raise
95
+ end
96
+ # rubocop:enable all
97
+ private :connect
98
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ # decorator methods for socks proxying
4
+ module Socksproxyable
5
+ # class methods
6
+ module ClassMethods
7
+ SOCKS4_VERSIONS = %w[4 4a].freeze
8
+
9
+ attr_accessor :socks_server, :socks_port, :socks_username, :socks_password
10
+
11
+ def socks_version
12
+ @socks_version ||= '5'
13
+ end
14
+
15
+ def socks_ignores
16
+ @socks_ignores ||= %w[localhost]
17
+ end
18
+
19
+ def socks_ignores=(*hosts)
20
+ @socks_ignores = hosts
21
+ end
22
+
23
+ def socks_version_hex
24
+ SOCKS4_VERSIONS.include?(socks_version) ? "\004" : "\005"
25
+ end
26
+ end
27
+
28
+ # instance method #socks_authenticate
29
+ module InstanceMethodsAuthenticate
30
+ # rubocop:disable Metrics
31
+ def socks_authenticate(socks_username, socks_password)
32
+ if socks_username || socks_password
33
+ Socksify.debug_debug 'Sending username/password authentication'
34
+ write "\005\001\002"
35
+ else
36
+ Socksify.debug_debug 'Sending no authentication'
37
+ write "\005\001\000"
38
+ end
39
+ Socksify.debug_debug 'Waiting for authentication reply'
40
+ auth_reply = recv(2)
41
+ raise SOCKSError, "Server doesn't reply authentication" if auth_reply.empty?
42
+
43
+ if auth_reply[0..0] != "\004" && auth_reply[0..0] != "\005"
44
+ raise SOCKSError, "SOCKS version #{auth_reply[0..0]} not supported"
45
+ end
46
+
47
+ if socks_username || socks_password
48
+ if auth_reply[1..1] != "\002"
49
+ raise SOCKSError, "SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported"
50
+ end
51
+
52
+ auth = "\001"
53
+ auth += socks_username.to_s.length.chr
54
+ auth += socks_username.to_s
55
+ auth += socks_password.to_s.length.chr
56
+ auth += socks_password.to_s
57
+ write auth
58
+ auth_reply = recv(2)
59
+ raise SOCKSError, 'SOCKS authentication failed' if auth_reply[1..1] != "\000"
60
+ elsif auth_reply[1..1] != "\000"
61
+ raise SOCKSError, "SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported"
62
+ end
63
+ end
64
+ # rubocop:enable Metrics
65
+ end
66
+
67
+ # instance methods #socks_connect & #socks_receive_reply
68
+ module InstanceMethodsConnect
69
+ # rubocop:disable Metrics
70
+ def socks_connect(host, port)
71
+ port = Socket.getservbyname(port) if port.is_a?(String)
72
+ req = String.new
73
+ Socksify.debug_debug 'Sending destination address'
74
+ req << TCPSocket.socks_version_hex
75
+ Socksify.debug_debug TCPSocket.socks_version_hex.unpack 'H*'
76
+ req << "\001"
77
+ req << "\000" if self.class.socks_version == '5'
78
+ req << [port].pack('n') if self.class.socks_version =~ /^4/
79
+ host = Resolv::DNS.new.getaddress(host).to_s if self.class.socks_version == '4'
80
+ Socksify.debug_debug host
81
+ if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
82
+ req << "\001" if self.class.socks_version == '5'
83
+ ip = (1..4).map { |i| Regexp.last_match(i).to_i }.pack('CCCC')
84
+ req << ip
85
+ elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
86
+ raise 'TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor'
87
+ # req << "\004" # UNREACHABLE
88
+ elsif self.class.socks_version == '5' # to hostname
89
+ # req << "\003" + [host.size].pack('C') + host
90
+ req << "\003#{[host.size].pack('C')}#{host}"
91
+ else
92
+ req << "\000\000\000\001" << "\007\000"
93
+ Socksify.debug_notice host
94
+ req << host << "\000"
95
+ end
96
+ req << [port].pack('n') if self.class.socks_version == '5'
97
+ write req
98
+ socks_receive_reply
99
+ Socksify.debug_notice "Connected to #{host}:#{port} over SOCKS"
100
+ end
101
+ # rubocop:enable Metrics
102
+
103
+ # returns [bind_addr: String, bind_port: Fixnum]
104
+ # rubocop:disable Metrics
105
+ def socks_receive_reply
106
+ Socksify.debug_debug 'Waiting for SOCKS reply'
107
+ if self.class.socks_version == '5'
108
+ connect_reply = recv(4)
109
+ raise SOCKSError, "Server doesn't reply" if connect_reply.empty?
110
+
111
+ Socksify.debug_debug connect_reply.unpack 'H*'
112
+ raise SOCKSError, "SOCKS version #{connect_reply[0..0]} is not 5" if connect_reply[0..0] != "\005"
113
+ raise SOCKSError.for_response_code(connect_reply.bytes.to_a[1]) if connect_reply[1..1] != "\000"
114
+
115
+ Socksify.debug_debug 'Waiting for bind_addr'
116
+ bind_addr_len = case connect_reply[3..3]
117
+ when "\001"
118
+ 4
119
+ when "\003"
120
+ recv(1).bytes.first
121
+ when "\004"
122
+ 16
123
+ else
124
+ raise SOCKSError.for_response_code(connect_reply.bytes.to_a[3])
125
+ end
126
+ bind_addr_s = recv(bind_addr_len)
127
+ bind_addr = case connect_reply[3..3]
128
+ when "\001"
129
+ bind_addr_s.bytes.to_a.join('.')
130
+ when "\003"
131
+ bind_addr_s
132
+ when "\004" # Untested!
133
+ i = 0
134
+ ip6 = ''
135
+ bind_addr_s.each_byte do |b|
136
+ ip6 += ':' if i > 0 && i.even?
137
+ i += 1
138
+ ip6 += b.to_s(16).rjust(2, '0')
139
+ end
140
+ end
141
+ bind_port = recv(bind_addr_len + 2)
142
+ [bind_addr, bind_port.unpack('n')]
143
+ else
144
+ connect_reply = recv(8)
145
+ unless connect_reply[0] == "\000" && connect_reply[1] == "\x5A"
146
+ Socksify.debug_debug connect_reply.unpack 'H'
147
+ raise SOCKSError, 'Failed while connecting througth socks'
148
+ end
149
+ end
150
+ end
151
+ # rubocop:enable Metrics
152
+ end
153
+ end
@@ -0,0 +1,82 @@
1
+ require_relative 'socksproxyable'
2
+
3
+ # monkey patch
4
+ class TCPSocket
5
+ extend Socksproxyable::ClassMethods
6
+ include Socksproxyable::InstanceMethodsAuthenticate
7
+ include Socksproxyable::InstanceMethodsConnect
8
+
9
+ alias initialize_tcp initialize
10
+
11
+ attr_reader :socks_peer
12
+
13
+ # See http://tools.ietf.org/html/rfc1928
14
+ # rubocop:disable Metrics/ParameterLists
15
+ def initialize(host = nil, port = nil, local_host = nil, local_port = nil, **kwargs)
16
+ @socks_peer = host if host.is_a?(SOCKSConnectionPeerAddress)
17
+ host = socks_peer.peer_host if socks_peer
18
+
19
+ if socks_server && socks_port && !socks_ignores.include?(host)
20
+ make_socks_connection(host, port, **kwargs)
21
+ else
22
+ make_direct_connection(host, port, local_host, local_port, **kwargs)
23
+ end
24
+ end
25
+ # rubocop:enable Metrics/ParameterLists
26
+
27
+ # string representation of the peer host address
28
+ class SOCKSConnectionPeerAddress < String
29
+ attr_reader :socks_server, :socks_port, :socks_username, :socks_password
30
+
31
+ def initialize(socks_server, socks_port, peer_host, socks_username = nil, socks_password = nil)
32
+ @socks_server = socks_server
33
+ @socks_port = socks_port
34
+ @socks_username = socks_username
35
+ @socks_password = socks_password
36
+ super(peer_host)
37
+ end
38
+
39
+ def inspect
40
+ "#{self} (via #{@socks_server}:#{@socks_port})"
41
+ end
42
+
43
+ def peer_host
44
+ to_s
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def socks_server
51
+ @socks_server ||= socks_peer ? socks_peer.socks_server : self.class.socks_server
52
+ end
53
+
54
+ def socks_port
55
+ @socks_port ||= socks_peer ? socks_peer.socks_port : self.class.socks_port
56
+ end
57
+
58
+ def socks_username
59
+ @socks_username ||= socks_peer ? socks_peer.socks_username : self.class.socks_username
60
+ end
61
+
62
+ def socks_password
63
+ @socks_password ||= socks_peer ? socks_peer.socks_password : self.class.socks_password
64
+ end
65
+
66
+ def socks_ignores
67
+ @socks_ignores ||= socks_peer ? [] : self.class.socks_ignores
68
+ end
69
+
70
+ def make_socks_connection(host, port, **kwargs)
71
+ Socksify.debug_notice "Connecting to SOCKS server #{socks_server}:#{socks_port}"
72
+ initialize_tcp socks_server, socks_port, **kwargs
73
+ socks_authenticate(socks_username, socks_password) unless @socks_version =~ /^4/
74
+ socks_connect(host, port) if host
75
+ end
76
+
77
+ def make_direct_connection(host, port, local_host, local_port, **kwargs)
78
+ Socksify.debug_notice "Connecting directly to #{host}:#{port}"
79
+ initialize_tcp host, port, local_host, local_port, **kwargs
80
+ Socksify.debug_debug "Connected to #{host}:#{port}"
81
+ end
82
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # namespace
4
+ module Socksify
5
+ VERSION = '1.8.0'
6
+ end
data/lib/socksify.rb CHANGED
@@ -1,360 +1,134 @@
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
1
+ # encoding: us-ascii
2
+
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/>.
18
17
 
19
18
  require 'socket'
20
19
  require 'resolv'
21
20
  require 'socksify/debug'
21
+ require 'socksify/tcpsocket'
22
22
 
23
+ # error class
23
24
  class SOCKSError < RuntimeError
24
25
  def initialize(msg)
25
- Socksify::debug_error("#{self.class}: #{msg}")
26
+ Socksify.debug_error("#{self.class}: #{msg}")
26
27
  super
27
28
  end
28
29
 
30
+ # rubocop:disable Style/Documentation
29
31
  class ServerFailure < SOCKSError
30
32
  def initialize
31
- super("general SOCKS server failure")
33
+ super('general SOCKS server failure')
32
34
  end
33
35
  end
36
+
34
37
  class NotAllowed < SOCKSError
35
38
  def initialize
36
- super("connection not allowed by ruleset")
39
+ super('connection not allowed by ruleset')
37
40
  end
38
41
  end
42
+
39
43
  class NetworkUnreachable < SOCKSError
40
44
  def initialize
41
- super("Network unreachable")
45
+ super('Network unreachable')
42
46
  end
43
47
  end
48
+
44
49
  class HostUnreachable < SOCKSError
45
50
  def initialize
46
- super("Host unreachable")
51
+ super('Host unreachable')
47
52
  end
48
53
  end
54
+
49
55
  class ConnectionRefused < SOCKSError
50
56
  def initialize
51
- super("Connection refused")
57
+ super('Connection refused')
52
58
  end
53
59
  end
60
+
54
61
  class TTLExpired < SOCKSError
55
62
  def initialize
56
- super("TTL expired")
63
+ super('TTL expired')
57
64
  end
58
65
  end
66
+
59
67
  class CommandNotSupported < SOCKSError
60
68
  def initialize
61
- super("Command not supported")
69
+ super('Command not supported')
62
70
  end
63
71
  end
72
+
64
73
  class AddressTypeNotSupported < SOCKSError
65
74
  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
136
-
137
- def initialize(socks_server, socks_port, peer_host)
138
- @socks_server, @socks_port = socks_server, socks_port
139
- super peer_host
75
+ super('Address type not supported')
140
76
  end
141
-
142
- def inspect
143
- "#{to_s} (via #{@socks_server}:#{@socks_port})"
144
- end
145
-
146
- def peer_host
147
- to_s
148
- end
149
- end
150
-
151
- alias :initialize_tcp :initialize
152
-
153
- # See http://tools.ietf.org/html/rfc1928
154
- def initialize(host=nil, port=0, local_host=nil, local_port=nil)
155
- if host.is_a?(SOCKSConnectionPeerAddress)
156
- socks_peer = host
157
- socks_server = socks_peer.socks_server
158
- socks_port = socks_peer.socks_port
159
- socks_ignores = []
160
- host = socks_peer.peer_host
161
- else
162
- socks_server = self.class.socks_server
163
- socks_port = self.class.socks_port
164
- socks_ignores = self.class.socks_ignores
165
- end
166
-
167
- if socks_server and socks_port and not socks_ignores.include?(host)
168
- Socksify::debug_notice "Connecting to SOCKS server #{socks_server}:#{socks_port}"
169
- initialize_tcp socks_server, socks_port
170
-
171
- socks_authenticate unless @@socks_version =~ /^4/
172
-
173
- if host
174
- socks_connect(host, port)
175
- end
176
- else
177
- Socksify::debug_notice "Connecting directly to #{host}:#{port}"
178
- initialize_tcp host, port, local_host, local_port
179
- Socksify::debug_debug "Connected to #{host}:#{port}"
180
- end
181
- end
182
-
183
- # Authentication
184
- def socks_authenticate
185
- if self.class.socks_username || self.class.socks_password
186
- Socksify::debug_debug "Sending username/password authentication"
187
- write "\005\001\002"
188
- else
189
- Socksify::debug_debug "Sending no authentication"
190
- write "\005\001\000"
191
- end
192
- Socksify::debug_debug "Waiting for authentication reply"
193
- auth_reply = recv(2)
194
- if auth_reply.empty?
195
- raise SOCKSError.new("Server doesn't reply authentication")
196
- end
197
- if auth_reply[0..0] != "\004" and auth_reply[0..0] != "\005"
198
- raise SOCKSError.new("SOCKS version #{auth_reply[0..0]} not supported")
199
- end
200
- if self.class.socks_username || self.class.socks_password
201
- if auth_reply[1..1] != "\002"
202
- raise SOCKSError.new("SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported")
203
- end
204
- auth = "\001"
205
- auth += self.class.socks_username.to_s.length.chr
206
- auth += self.class.socks_username.to_s
207
- auth += self.class.socks_password.to_s.length.chr
208
- auth += self.class.socks_password.to_s
209
- write auth
210
- auth_reply = recv(2)
211
- if auth_reply[1..1] != "\000"
212
- raise SOCKSError.new("SOCKS authentication failed")
213
- end
214
- else
215
- if auth_reply[1..1] != "\000"
216
- raise SOCKSError.new("SOCKS authentication method #{auth_reply[1..1]} neither requested nor supported")
217
- end
218
- end
219
- end
220
-
221
- # Connect
222
- def socks_connect(host, port)
223
- port = Socket.getservbyname(port) if port.is_a?(String)
224
- Socksify::debug_debug "Sending destination address"
225
- write TCPSocket.socks_version
226
- Socksify::debug_debug TCPSocket.socks_version.unpack "H*"
227
- write "\001"
228
- write "\000" if @@socks_version == "5"
229
- write [port].pack('n') if @@socks_version =~ /^4/
230
-
231
- if @@socks_version == "4"
232
- host = Resolv::DNS.new.getaddress(host).to_s
233
- end
234
- Socksify::debug_debug host
235
- if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
236
- write "\001" if @@socks_version == "5"
237
- _ip = [$1.to_i,
238
- $2.to_i,
239
- $3.to_i,
240
- $4.to_i
241
- ].pack('CCCC')
242
- write _ip
243
- elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
244
- raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
245
- write "\004"
246
- else # to hostname
247
- if @@socks_version == "5"
248
- write "\003" + [host.size].pack('C') + host
249
- else
250
- write "\000\000\000\001"
251
- write "\007\000"
252
- Socksify::debug_notice host
253
- write host
254
- write "\000"
255
- end
256
- end
257
- write [port].pack('n') if @@socks_version == "5"
258
-
259
- socks_receive_reply
260
- Socksify::debug_notice "Connected to #{host}:#{port} over SOCKS"
261
77
  end
78
+ # rubocop:enable Style/Documentation
262
79
 
263
- # returns [bind_addr: String, bind_port: Fixnum]
264
- def socks_receive_reply
265
- Socksify::debug_debug "Waiting for SOCKS reply"
266
- if @@socks_version == "5"
267
- connect_reply = recv(4)
268
- if connect_reply.empty?
269
- raise SOCKSError.new("Server doesn't reply")
270
- end
271
- Socksify::debug_debug connect_reply.unpack "H*"
272
- if connect_reply[0..0] != "\005"
273
- raise SOCKSError.new("SOCKS version #{connect_reply[0..0]} is not 5")
274
- end
275
- if connect_reply[1..1] != "\000"
276
- raise SOCKSError.for_response_code(connect_reply.bytes.to_a[1])
277
- end
278
- Socksify::debug_debug "Waiting for bind_addr"
279
- bind_addr_len = case connect_reply[3..3]
280
- when "\001"
281
- 4
282
- when "\003"
283
- recv(1).bytes.first
284
- when "\004"
285
- 16
286
- else
287
- raise SOCKSError.for_response_code(connect_reply.bytes.to_a[3])
288
- end
289
- bind_addr_s = recv(bind_addr_len)
290
- bind_addr = case connect_reply[3..3]
291
- when "\001"
292
- bind_addr_s.bytes.to_a.join('.')
293
- when "\003"
294
- bind_addr_s
295
- when "\004" # Untested!
296
- i = 0
297
- ip6 = ""
298
- bind_addr_s.each_byte do |b|
299
- if i > 0 and i % 2 == 0
300
- ip6 += ":"
301
- end
302
- i += 1
80
+ RESPONSE_CODE_CLASSES = { 1 => ServerFailure,
81
+ 2 => NotAllowed,
82
+ 3 => NetworkUnreachable,
83
+ 4 => HostUnreachable,
84
+ 5 => ConnectionRefused,
85
+ 6 => TTLExpired,
86
+ 7 => CommandNotSupported,
87
+ 8 => AddressTypeNotSupported }.freeze
303
88
 
304
- ip6 += b.to_s(16).rjust(2, '0')
305
- end
306
- end
307
- bind_port = recv(bind_addr_len + 2)
308
- [bind_addr, bind_port.unpack('n')]
309
- else
310
- connect_reply = recv(8)
311
- unless connect_reply[0] == "\000" and connect_reply[1] == "\x5A"
312
- Socksify::debug_debug connect_reply.unpack 'H'
313
- raise SOCKSError.new("Failed while connecting througth socks")
314
- end
315
- end
89
+ def self.for_response_code(code)
90
+ (resp = RESPONSE_CODE_CLASSES[code]) ? resp : self
316
91
  end
317
92
  end
318
93
 
94
+ # namespace
319
95
  module Socksify
320
96
  def self.resolve(host)
321
- s = TCPSocket.new
322
-
323
- begin
324
- Socksify::debug_debug "Sending hostname to resolve: #{host}"
325
- s.write "\005"
326
- if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
327
- s.write "\xF1\000\001" + [$1.to_i,
328
- $2.to_i,
329
- $3.to_i,
330
- $4.to_i
331
- ].pack('CCCC')
332
- elsif host =~ /^[:0-9a-f]+$/ # to IPv6 address
333
- raise "TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor"
334
- s.write "\004"
335
- else # to hostname
336
- s.write "\xF0\000\003" + [host.size].pack('C') + host
337
- end
338
- s.write [0].pack('n') # Port
339
-
340
- addr, _port = s.socks_receive_reply
341
- Socksify::debug_notice "Resolved #{host} as #{addr} over SOCKS"
342
- addr
343
- ensure
344
- s.close
345
- end
97
+ socket = TCPSocket.new # no args?
98
+ Socksify.debug_debug "Sending hostname to resolve: #{host}"
99
+ req = request(host)
100
+ socket.write req
101
+ addr, _port = socket.socks_receive_reply
102
+ Socksify.debug_notice "Resolved #{host} as #{addr} over SOCKS"
103
+ addr
104
+ ensure
105
+ socket.close
346
106
  end
347
107
 
348
108
  def self.proxy(server, port)
349
- default_server = TCPSocket::socks_server
350
- default_port = TCPSocket::socks_port
109
+ default_server = TCPSocket.socks_server
110
+ default_port = TCPSocket.socks_port
351
111
  begin
352
- TCPSocket::socks_server = server
353
- TCPSocket::socks_port = port
112
+ TCPSocket.socks_server = server
113
+ TCPSocket.socks_port = port
354
114
  yield
355
- ensure
356
- TCPSocket::socks_server = default_server
357
- TCPSocket::socks_port = default_port
115
+ ensure # failback
116
+ TCPSocket.socks_server = default_server
117
+ TCPSocket.socks_port = default_port
118
+ end
119
+ end
120
+
121
+ def self.request(host)
122
+ req = String.new << "\005"
123
+ case host
124
+ when /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ # to IPv4 address
125
+ req << "\xF1\000\001#{(1..4).map { |i| Regexp.last_match(i).to_i }.pack('CCCC')}"
126
+ when /^[:0-9a-f]+$/ # to IPv6 address
127
+ raise 'TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor)'
128
+ # # req << "\004" # UNREACHABLE
129
+ else # to hostname
130
+ req << "\xF0\000\003#{[host.size].pack('C')}#{host}"
358
131
  end
132
+ req << [0].pack('n') # Port
359
133
  end
360
134
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: socksify
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephan Maka
@@ -10,19 +10,17 @@ authors:
10
10
  - Musy Bite
11
11
  - Yuichi Tateno
12
12
  - David Dollar
13
- autorequire:
14
13
  bindir: bin
15
14
  cert_chain: []
16
- date: 2015-07-31 00:00:00.000000000 Z
15
+ date: 2025-07-17 00:00:00.000000000 Z
17
16
  dependencies: []
18
- description:
19
17
  email: stephan@spaceboyz.net
20
18
  executables:
21
19
  - socksify_ruby
22
20
  extensions: []
23
21
  extra_rdoc_files:
24
- - doc/index.html
25
22
  - doc/index.css
23
+ - doc/index.html
26
24
  - COPYING
27
25
  files:
28
26
  - COPYING
@@ -32,10 +30,16 @@ files:
32
30
  - lib/socksify.rb
33
31
  - lib/socksify/debug.rb
34
32
  - lib/socksify/http.rb
35
- homepage: http://socksify.rubyforge.org/
36
- licenses: []
37
- metadata: {}
38
- post_install_message:
33
+ - lib/socksify/ruby3net_http_connectable.rb
34
+ - lib/socksify/socksproxyable.rb
35
+ - lib/socksify/tcpsocket.rb
36
+ - lib/socksify/version.rb
37
+ homepage: https://github.com/astro/socksify-ruby
38
+ licenses:
39
+ - Ruby
40
+ - GPL-3.0-only
41
+ metadata:
42
+ funding_uri: https://github.com/sponsors/astro
39
43
  rdoc_options: []
40
44
  require_paths:
41
45
  - lib
@@ -43,16 +47,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
43
47
  requirements:
44
48
  - - ">="
45
49
  - !ruby/object:Gem::Version
46
- version: '0'
50
+ version: '2.0'
47
51
  required_rubygems_version: !ruby/object:Gem::Requirement
48
52
  requirements:
49
53
  - - ">="
50
54
  - !ruby/object:Gem::Version
51
55
  version: '0'
52
56
  requirements: []
53
- rubyforge_project: socksify
54
- rubygems_version: 2.4.5
55
- signing_key:
57
+ rubygems_version: 3.6.6
56
58
  specification_version: 4
57
59
  summary: Redirect all TCPSockets through a SOCKS5 proxy
58
60
  test_files: []