1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
require 'socket'
require 'net/ssh/proxy/errors'
module Net
module SSH
module Proxy
# An implementation of a SOCKS5 proxy. To use it, instantiate it, then
# pass the instantiated object via the :proxy key to Net::SSH.start:
#
# require 'net/ssh/proxy/socks5'
#
# proxy = Net::SSH::Proxy::SOCKS5.new('proxy.host', proxy_port,
# :user => 'user', :password => "password")
# Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
# ...
# end
class SOCKS5
# The SOCKS protocol version used by this class
VERSION = 5
# The SOCKS authentication type for requests without authentication
METHOD_NO_AUTH = 0
# The SOCKS authentication type for requests via username/password
METHOD_PASSWD = 2
# The SOCKS authentication type for when there are no supported
# authentication methods.
METHOD_NONE = 0xFF
# The SOCKS packet type for requesting a proxy connection.
CMD_CONNECT = 1
# The SOCKS address type for connections via IP address.
ATYP_IPV4 = 1
# The SOCKS address type for connections via domain name.
ATYP_DOMAIN = 3
# The SOCKS response code for a successful operation.
SUCCESS = 0
# The proxy's host name or IP address
attr_reader :proxy_host
# The proxy's port number
attr_reader :proxy_port
# The map of options given at initialization
attr_reader :options
# Create a new proxy connection to the given proxy host and port.
# Optionally, :user and :password options may be given to
# identify the username and password with which to authenticate.
def initialize(proxy_host, proxy_port = 1080, options = {})
@proxy_host = proxy_host
@proxy_port = proxy_port
@options = options
end
# Return a new socket connected to the given host and port via the
# proxy that was requested when the socket factory was instantiated.
def open(host, port, connection_options)
socket = Socket.tcp(proxy_host, proxy_port, nil, nil,
connect_timeout: connection_options[:timeout])
methods = [METHOD_NO_AUTH]
methods << METHOD_PASSWD if options[:user]
packet = [VERSION, methods.size, *methods].pack("C*")
socket.send packet, 0
version, method = socket.recv(2).unpack("CC")
if version != VERSION
socket.close
raise Net::SSH::Proxy::Error, "invalid SOCKS version (#{version})"
end
if method == METHOD_NONE
socket.close
raise Net::SSH::Proxy::Error, "no supported authorization methods"
end
negotiate_password(socket) if method == METHOD_PASSWD
packet = [VERSION, CMD_CONNECT, 0].pack("C*")
if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
packet << [ATYP_IPV4, $1.to_i, $2.to_i, $3.to_i, $4.to_i].pack("C*")
else
packet << [ATYP_DOMAIN, host.length, host].pack("CCA*")
end
packet << [port].pack("n")
socket.send packet, 0
version, reply, = socket.recv(2).unpack("C*")
socket.recv(1)
address_type = socket.recv(1).getbyte(0)
case address_type
when 1
socket.recv(4) # get four bytes for IPv4 address
when 3
len = socket.recv(1).getbyte(0)
hostname = socket.recv(len)
when 4
ipv6addr hostname = socket.recv(16)
else
socket.close
raise ConnectError, "Illegal response type"
end
portnum = socket.recv(2)
unless reply == SUCCESS
socket.close
raise ConnectError, "#{reply}"
end
return socket
end
private
# Simple username/password negotiation with the SOCKS5 server.
def negotiate_password(socket)
packet = [0x01, options[:user].length, options[:user],
options[:password].length, options[:password]].pack("CCA*CA*")
socket.send packet, 0
version, status = socket.recv(2).unpack("CC")
if status != SUCCESS
socket.close
raise UnauthorizedError, "could not authorize user"
end
end
end
end
end
end
|