ddollar-net-ssh 2.0.1

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.
Files changed (103) hide show
  1. data/CHANGELOG.rdoc +42 -0
  2. data/Manifest +101 -0
  3. data/README.rdoc +110 -0
  4. data/Rakefile +26 -0
  5. data/THANKS.rdoc +16 -0
  6. data/lib/net/ssh.rb +199 -0
  7. data/lib/net/ssh/authentication/agent.rb +175 -0
  8. data/lib/net/ssh/authentication/constants.rb +18 -0
  9. data/lib/net/ssh/authentication/key_manager.rb +169 -0
  10. data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
  11. data/lib/net/ssh/authentication/methods/hostbased.rb +71 -0
  12. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +66 -0
  13. data/lib/net/ssh/authentication/methods/password.rb +39 -0
  14. data/lib/net/ssh/authentication/methods/publickey.rb +92 -0
  15. data/lib/net/ssh/authentication/pageant.rb +176 -0
  16. data/lib/net/ssh/authentication/session.rb +127 -0
  17. data/lib/net/ssh/buffer.rb +339 -0
  18. data/lib/net/ssh/buffered_io.rb +149 -0
  19. data/lib/net/ssh/config.rb +173 -0
  20. data/lib/net/ssh/connection/channel.rb +625 -0
  21. data/lib/net/ssh/connection/constants.rb +33 -0
  22. data/lib/net/ssh/connection/session.rb +569 -0
  23. data/lib/net/ssh/connection/term.rb +178 -0
  24. data/lib/net/ssh/errors.rb +85 -0
  25. data/lib/net/ssh/key_factory.rb +85 -0
  26. data/lib/net/ssh/known_hosts.rb +129 -0
  27. data/lib/net/ssh/loggable.rb +61 -0
  28. data/lib/net/ssh/packet.rb +102 -0
  29. data/lib/net/ssh/prompt.rb +93 -0
  30. data/lib/net/ssh/proxy/errors.rb +14 -0
  31. data/lib/net/ssh/proxy/http.rb +94 -0
  32. data/lib/net/ssh/proxy/socks4.rb +70 -0
  33. data/lib/net/ssh/proxy/socks5.rb +128 -0
  34. data/lib/net/ssh/service/forward.rb +267 -0
  35. data/lib/net/ssh/test.rb +89 -0
  36. data/lib/net/ssh/test/channel.rb +129 -0
  37. data/lib/net/ssh/test/extensions.rb +152 -0
  38. data/lib/net/ssh/test/kex.rb +44 -0
  39. data/lib/net/ssh/test/local_packet.rb +51 -0
  40. data/lib/net/ssh/test/packet.rb +81 -0
  41. data/lib/net/ssh/test/remote_packet.rb +38 -0
  42. data/lib/net/ssh/test/script.rb +157 -0
  43. data/lib/net/ssh/test/socket.rb +59 -0
  44. data/lib/net/ssh/transport/algorithms.rb +384 -0
  45. data/lib/net/ssh/transport/cipher_factory.rb +72 -0
  46. data/lib/net/ssh/transport/constants.rb +30 -0
  47. data/lib/net/ssh/transport/hmac.rb +31 -0
  48. data/lib/net/ssh/transport/hmac/abstract.rb +48 -0
  49. data/lib/net/ssh/transport/hmac/md5.rb +12 -0
  50. data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  51. data/lib/net/ssh/transport/hmac/none.rb +15 -0
  52. data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  53. data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  54. data/lib/net/ssh/transport/identity_cipher.rb +40 -0
  55. data/lib/net/ssh/transport/kex.rb +13 -0
  56. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
  58. data/lib/net/ssh/transport/openssl.rb +128 -0
  59. data/lib/net/ssh/transport/packet_stream.rb +230 -0
  60. data/lib/net/ssh/transport/server_version.rb +61 -0
  61. data/lib/net/ssh/transport/session.rb +262 -0
  62. data/lib/net/ssh/transport/state.rb +170 -0
  63. data/lib/net/ssh/verifiers/lenient.rb +30 -0
  64. data/lib/net/ssh/verifiers/null.rb +12 -0
  65. data/lib/net/ssh/verifiers/strict.rb +53 -0
  66. data/lib/net/ssh/version.rb +60 -0
  67. data/net-ssh.gemspec +56 -0
  68. data/setup.rb +1585 -0
  69. data/test/authentication/methods/common.rb +28 -0
  70. data/test/authentication/methods/test_abstract.rb +51 -0
  71. data/test/authentication/methods/test_hostbased.rb +108 -0
  72. data/test/authentication/methods/test_keyboard_interactive.rb +98 -0
  73. data/test/authentication/methods/test_password.rb +50 -0
  74. data/test/authentication/methods/test_publickey.rb +123 -0
  75. data/test/authentication/test_agent.rb +205 -0
  76. data/test/authentication/test_key_manager.rb +100 -0
  77. data/test/authentication/test_session.rb +93 -0
  78. data/test/common.rb +106 -0
  79. data/test/configs/exact_match +8 -0
  80. data/test/configs/wild_cards +14 -0
  81. data/test/connection/test_channel.rb +452 -0
  82. data/test/connection/test_session.rb +483 -0
  83. data/test/test_all.rb +6 -0
  84. data/test/test_buffer.rb +336 -0
  85. data/test/test_buffered_io.rb +63 -0
  86. data/test/test_config.rb +78 -0
  87. data/test/test_key_factory.rb +67 -0
  88. data/test/transport/hmac/test_md5.rb +34 -0
  89. data/test/transport/hmac/test_md5_96.rb +25 -0
  90. data/test/transport/hmac/test_none.rb +34 -0
  91. data/test/transport/hmac/test_sha1.rb +34 -0
  92. data/test/transport/hmac/test_sha1_96.rb +25 -0
  93. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  94. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  95. data/test/transport/test_algorithms.rb +302 -0
  96. data/test/transport/test_cipher_factory.rb +163 -0
  97. data/test/transport/test_hmac.rb +34 -0
  98. data/test/transport/test_identity_cipher.rb +40 -0
  99. data/test/transport/test_packet_stream.rb +433 -0
  100. data/test/transport/test_server_version.rb +55 -0
  101. data/test/transport/test_session.rb +312 -0
  102. data/test/transport/test_state.rb +173 -0
  103. metadata +222 -0
@@ -0,0 +1,61 @@
1
+ module Net; module SSH
2
+
3
+ # A simple module to make logging easier to deal with. It assumes that the
4
+ # logger instance (if not nil) quacks like a Logger object (in Ruby's
5
+ # standard library). Although used primarily internally by Net::SSH, it
6
+ # can easily be used to add Net::SSH-like logging to your own programs.
7
+ #
8
+ # class MyClass
9
+ # include Net::SSH::Loggable
10
+ # end
11
+ #
12
+ # Net::SSH.start(...) do |ssh|
13
+ # obj = MyClass.new
14
+ # obj.logger = ssh.logger
15
+ # ...
16
+ # end
17
+ module Loggable
18
+ # The logger instance that will be used to log messages. If nil, nothing
19
+ # will be logged.
20
+ attr_accessor :logger
21
+
22
+ # Displays the result of yielding if the log level is Logger::DEBUG or
23
+ # greater.
24
+ def debug
25
+ logger.add(Logger::DEBUG, nil, facility) { yield } if logger
26
+ end
27
+
28
+ # Displays the result of yielding if the log level is Logger::INFO or
29
+ # greater.
30
+ def info
31
+ logger.add(Logger::INFO, nil, facility) { yield } if logger
32
+ end
33
+
34
+ # Displays the result of yielding if the log level is Logger::WARN or
35
+ # greater. (Called lwarn to avoid shadowing with Kernel#warn.)
36
+ def lwarn
37
+ logger.add(Logger::WARN, nil, facility) { yield } if logger
38
+ end
39
+
40
+ # Displays the result of yielding if the log level is Logger:ERROR or
41
+ # greater.
42
+ def error
43
+ logger.add(Logger::ERROR, nil, facility) { yield } if logger
44
+ end
45
+
46
+ # Displays the result of yielding if the log level is Logger::FATAL or
47
+ # greater.
48
+ def fatal
49
+ logger.add(Logger::FATAL, nil, facility) { yield } if logger
50
+ end
51
+
52
+ private
53
+
54
+ # Sets the "facility" value, used for reporting where a log message
55
+ # originates. It defaults to the name of class with the object_id
56
+ # appended.
57
+ def facility
58
+ @facility ||= self.class.name.gsub(/::/, ".").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + "[%x]" % object_id
59
+ end
60
+ end
61
+ end; end
@@ -0,0 +1,102 @@
1
+ require 'net/ssh/buffer'
2
+ require 'net/ssh/transport/constants'
3
+ require 'net/ssh/authentication/constants'
4
+ require 'net/ssh/connection/constants'
5
+
6
+ module Net; module SSH
7
+
8
+ # A specialization of Buffer that knows the format of certain common
9
+ # packet types. It auto-parses those packet types, and allows them to
10
+ # be accessed via the #[] accessor.
11
+ #
12
+ # data = some_channel_request_packet
13
+ # packet = Net::SSH::Packet.new(data)
14
+ #
15
+ # p packet.type #-> 98 (CHANNEL_REQUEST)
16
+ # p packet[:request]
17
+ # p packet[:want_reply]
18
+ #
19
+ # This is used exclusively internally by Net::SSH, and unless you're doing
20
+ # protocol-level manipulation or are extending Net::SSH in some way, you'll
21
+ # never need to use this class directly.
22
+ class Packet < Buffer
23
+ @@types = {}
24
+
25
+ # Register a new packet type that should be recognized and auto-parsed by
26
+ # Net::SSH::Packet. Note that any packet type that is not preregistered
27
+ # will not be autoparsed.
28
+ #
29
+ # The +pairs+ parameter must be either empty, or an array of two-element
30
+ # tuples, where the first element of each tuple is the name of the field,
31
+ # and the second is the type.
32
+ #
33
+ # register DISCONNECT, [:reason_code, :long], [:description, :string], [:language, :string]
34
+ def self.register(type, *pairs)
35
+ @@types[type] = pairs
36
+ end
37
+
38
+ include Transport::Constants, Authentication::Constants, Connection::Constants
39
+
40
+ #--
41
+ # These are the recognized packet types. All other packet types will be
42
+ # accepted, but not auto-parsed, requiring the client to parse the
43
+ # fields using the methods provided by Net::SSH::Buffer.
44
+ #++
45
+
46
+ register DISCONNECT, [:reason_code, :long], [:description, :string], [:language, :string]
47
+ register IGNORE, [:data, :string]
48
+ register UNIMPLEMENTED, [:number, :long]
49
+ register DEBUG, [:always_display, :bool], [:message, :string], [:language, :string]
50
+ register SERVICE_ACCEPT, [:service_name, :string]
51
+ register USERAUTH_BANNER, [:message, :string], [:language, :string]
52
+ register USERAUTH_FAILURE, [:authentications, :string], [:partial_success, :bool]
53
+ register GLOBAL_REQUEST, [:request_type, :string], [:want_reply, :bool], [:request_data, :buffer]
54
+ register CHANNEL_OPEN, [:channel_type, :string], [:remote_id, :long], [:window_size, :long], [:packet_size, :long]
55
+ register CHANNEL_OPEN_CONFIRMATION, [:local_id, :long], [:remote_id, :long], [:window_size, :long], [:packet_size, :long]
56
+ register CHANNEL_OPEN_FAILURE, [:local_id, :long], [:reason_code, :long], [:description, :string], [:language, :string]
57
+ register CHANNEL_WINDOW_ADJUST, [:local_id, :long], [:extra_bytes, :long]
58
+ register CHANNEL_DATA, [:local_id, :long], [:data, :string]
59
+ register CHANNEL_EXTENDED_DATA, [:local_id, :long], [:data_type, :long], [:data, :string]
60
+ register CHANNEL_EOF, [:local_id, :long]
61
+ register CHANNEL_CLOSE, [:local_id, :long]
62
+ register CHANNEL_REQUEST, [:local_id, :long], [:request, :string], [:want_reply, :bool], [:request_data, :buffer]
63
+ register CHANNEL_SUCCESS, [:local_id, :long]
64
+ register CHANNEL_FAILURE, [:local_id, :long]
65
+
66
+ # The (integer) type of this packet.
67
+ attr_reader :type
68
+
69
+ # Create a new packet from the given payload. This will automatically
70
+ # parse the packet if it is one that has been previously registered with
71
+ # Packet.register; otherwise, the packet will need to be manually parsed
72
+ # using the methods provided in the Net::SSH::Buffer superclass.
73
+ def initialize(payload)
74
+ @named_elements = {}
75
+ super
76
+ @type = read_byte
77
+ instantiate!
78
+ end
79
+
80
+ # Access one of the auto-parsed fields by name. Raises an error if no
81
+ # element by the given name exists.
82
+ def [](name)
83
+ name = name.to_sym
84
+ raise ArgumentError, "no such element #{name}" unless @named_elements.key?(name)
85
+ @named_elements[name]
86
+ end
87
+
88
+ private
89
+
90
+ # Parse the packet's contents and assign the named elements, as described
91
+ # by the registered format for the packet.
92
+ def instantiate!
93
+ (@@types[type] || []).each do |name, datatype|
94
+ @named_elements[name.to_sym] = if datatype == :buffer
95
+ remainder_as_buffer
96
+ else
97
+ send("read_#{datatype}")
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end; end
@@ -0,0 +1,93 @@
1
+ module Net; module SSH
2
+
3
+ # A basic prompt module that can be mixed into other objects. If HighLine is
4
+ # installed, it will be used to display prompts and read input from the
5
+ # user. Otherwise, the termios library will be used. If neither HighLine
6
+ # nor termios is installed, a simple prompt that echos text in the clear
7
+ # will be used.
8
+
9
+ module PromptMethods
10
+
11
+ # Defines the prompt method to use if the Highline library is installed.
12
+ module Highline
13
+ # Uses Highline#ask to present a prompt and accept input. If +echo+ is
14
+ # +false+, the characters entered by the user will not be echoed to the
15
+ # screen.
16
+ def prompt(prompt, echo=true)
17
+ @highline ||= ::HighLine.new
18
+ @highline.ask(prompt + " ") { |q| q.echo = echo }
19
+ end
20
+ end
21
+
22
+ # Defines the prompt method to use if the Termios library is installed.
23
+ module Termios
24
+ # Displays the prompt to $stdout. If +echo+ is false, the Termios
25
+ # library will be used to disable keystroke echoing for the duration of
26
+ # this method.
27
+ def prompt(prompt, echo=true)
28
+ $stdout.print(prompt)
29
+ $stdout.flush
30
+
31
+ set_echo(false) unless echo
32
+ $stdin.gets.chomp
33
+ ensure
34
+ if !echo
35
+ set_echo(true)
36
+ $stdout.puts
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # Enables or disables keystroke echoing using the Termios library.
43
+ def set_echo(enable)
44
+ term = ::Termios.getattr($stdin)
45
+
46
+ if enable
47
+ term.c_lflag |= (::Termios::ECHO | ::Termios::ICANON)
48
+ else
49
+ term.c_lflag &= ~::Termios::ECHO
50
+ end
51
+
52
+ ::Termios.setattr($stdin, ::Termios::TCSANOW, term)
53
+ end
54
+ end
55
+
56
+ # Defines the prompt method to use when neither Highline nor Termios are
57
+ # installed.
58
+ module Clear
59
+ # Displays the prompt to $stdout and pulls the response from $stdin.
60
+ # Text is always echoed in the clear, regardless of the +echo+ setting.
61
+ # The first time a prompt is given and +echo+ is false, a warning will
62
+ # be written to $stderr recommending that either Highline or Termios
63
+ # be installed.
64
+ def prompt(prompt, echo=true)
65
+ @seen_warning ||= false
66
+ if !echo && !@seen_warning
67
+ $stderr.puts "Text will be echoed in the clear. Please install the HighLine or Termios libraries to suppress echoed text."
68
+ @seen_warning = true
69
+ end
70
+
71
+ $stdout.print(prompt)
72
+ $stdout.flush
73
+ $stdin.gets.chomp
74
+ end
75
+ end
76
+ end
77
+
78
+ # Try to load Highline and Termios in turn, selecting the corresponding
79
+ # PromptMethods module to use. If neither are available, choose PromptMethods::Clear.
80
+ Prompt = begin
81
+ require 'highline'
82
+ HighLine.track_eof = false
83
+ PromptMethods::Highline
84
+ rescue LoadError
85
+ begin
86
+ require 'termios'
87
+ PromptMethods::Termios
88
+ rescue LoadError
89
+ PromptMethods::Clear
90
+ end
91
+ end
92
+
93
+ end; end
@@ -0,0 +1,14 @@
1
+ require 'net/ssh/errors'
2
+
3
+ module Net; module SSH; module Proxy
4
+
5
+ # A general exception class for all Proxy errors.
6
+ class Error < Net::SSH::Exception; end
7
+
8
+ # Used for reporting proxy connection errors.
9
+ class ConnectError < Error; end
10
+
11
+ # Used when the server doesn't recognize the user's credentials.
12
+ class UnauthorizedError < Error; end
13
+
14
+ end; end; end
@@ -0,0 +1,94 @@
1
+ require 'socket'
2
+ require 'net/ssh/proxy/errors'
3
+
4
+ module Net; module SSH; module Proxy
5
+
6
+ # An implementation of an HTTP proxy. To use it, instantiate it, then
7
+ # pass the instantiated object via the :proxy key to Net::SSH.start:
8
+ #
9
+ # require 'net/ssh/proxy/http'
10
+ #
11
+ # proxy = Net::SSH::Proxy::HTTP.new('proxy.host', proxy_port)
12
+ # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
13
+ # ...
14
+ # end
15
+ #
16
+ # If the proxy requires authentication, you can pass :user and :password
17
+ # to the proxy's constructor:
18
+ #
19
+ # proxy = Net::SSH::Proxy::HTTP.new('proxy.host', proxy_port,
20
+ # :user => "user", :password => "password")
21
+ #
22
+ # Note that HTTP digest authentication is not supported; Basic only at
23
+ # this point.
24
+ class HTTP
25
+
26
+ # The hostname or IP address of the HTTP proxy.
27
+ attr_reader :proxy_host
28
+
29
+ # The port number of the proxy.
30
+ attr_reader :proxy_port
31
+
32
+ # The map of additional options that were given to the object at
33
+ # initialization.
34
+ attr_reader :options
35
+
36
+ # Create a new socket factory that tunnels via the given host and
37
+ # port. The +options+ parameter is a hash of additional settings that
38
+ # can be used to tweak this proxy connection. Specifically, the following
39
+ # options are supported:
40
+ #
41
+ # * :user => the user name to use when authenticating to the proxy
42
+ # * :password => the password to use when authenticating
43
+ def initialize(proxy_host, proxy_port=80, options={})
44
+ @proxy_host = proxy_host
45
+ @proxy_port = proxy_port
46
+ @options = options
47
+ end
48
+
49
+ # Return a new socket connected to the given host and port via the
50
+ # proxy that was requested when the socket factory was instantiated.
51
+ def open(host, port)
52
+ socket = TCPSocket.new(proxy_host, proxy_port)
53
+ socket.write "CONNECT #{host}:#{port} HTTP/1.0\r\n"
54
+
55
+ if options[:user]
56
+ credentials = ["#{options[:user]}:#{options[:password]}"].pack("m*").gsub(/\s/, "")
57
+ socket.write "Proxy-Authorization: Basic #{credentials}\r\n"
58
+ end
59
+
60
+ socket.write "\r\n"
61
+
62
+ resp = parse_response(socket)
63
+
64
+ return socket if resp[:code] == 200
65
+
66
+ socket.close
67
+ raise ConnectError, resp.inspect
68
+ end
69
+
70
+ private
71
+
72
+ def parse_response(socket)
73
+ version, code, reason = socket.gets.chomp.split(/ /, 3)
74
+ headers = {}
75
+
76
+ while (line = socket.gets.chomp) != ""
77
+ name, value = line.split(/:/, 2)
78
+ headers[name.strip] = value.strip
79
+ end
80
+
81
+ if headers["Content-Length"]
82
+ body = socket.read(headers["Content-Length"].to_i)
83
+ end
84
+
85
+ return { :version => version,
86
+ :code => code.to_i,
87
+ :reason => reason,
88
+ :headers => headers,
89
+ :body => body }
90
+ end
91
+
92
+ end
93
+
94
+ end; end; end
@@ -0,0 +1,70 @@
1
+ require 'socket'
2
+ require 'resolv'
3
+ require 'ipaddr'
4
+ require 'net/ssh/proxy/errors'
5
+
6
+ module Net
7
+ module SSH
8
+ module Proxy
9
+
10
+ # An implementation of a SOCKS4 proxy. To use it, instantiate it, then
11
+ # pass the instantiated object via the :proxy key to Net::SSH.start:
12
+ #
13
+ # require 'net/ssh/proxy/socks4'
14
+ #
15
+ # proxy = Net::SSH::Proxy::SOCKS4.new('proxy.host', proxy_port, :user => 'user')
16
+ # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
17
+ # ...
18
+ # end
19
+ class SOCKS4
20
+
21
+ # The SOCKS protocol version used by this class
22
+ VERSION = 4
23
+
24
+ # The packet type for connection requests
25
+ CONNECT = 1
26
+
27
+ # The status code for a successful connection
28
+ GRANTED = 90
29
+
30
+ # The proxy's host name or IP address, as given to the constructor.
31
+ attr_reader :proxy_host
32
+
33
+ # The proxy's port number.
34
+ attr_reader :proxy_port
35
+
36
+ # The additional options that were given to the proxy's constructor.
37
+ attr_reader :options
38
+
39
+ # Create a new proxy connection to the given proxy host and port.
40
+ # Optionally, a :user key may be given to identify the username
41
+ # with which to authenticate.
42
+ def initialize(proxy_host, proxy_port=1080, options={})
43
+ @proxy_host = proxy_host
44
+ @proxy_port = proxy_port
45
+ @options = options
46
+ end
47
+
48
+ # Return a new socket connected to the given host and port via the
49
+ # proxy that was requested when the socket factory was instantiated.
50
+ def open(host, port)
51
+ socket = TCPSocket.new(proxy_host, proxy_port)
52
+ ip_addr = IPAddr.new(Resolv.getaddress(host))
53
+
54
+ packet = [VERSION, CONNECT, port.to_i, ip_addr.to_i, options[:user]].pack("CCnNZ*")
55
+ socket.send packet, 0
56
+
57
+ version, status, port, ip = socket.recv(8).unpack("CCnN")
58
+ if status != GRANTED
59
+ socket.close
60
+ raise ConnectError, "error connecting to proxy (#{status})"
61
+ end
62
+
63
+ return socket
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,128 @@
1
+ require 'socket'
2
+ require 'net/ssh/proxy/errors'
3
+
4
+ module Net
5
+ module SSH
6
+ module Proxy
7
+
8
+ # An implementation of a SOCKS5 proxy. To use it, instantiate it, then
9
+ # pass the instantiated object via the :proxy key to Net::SSH.start:
10
+ #
11
+ # require 'net/ssh/proxy/socks5'
12
+ #
13
+ # proxy = Net::SSH::Proxy::SOCKS5.new('proxy.host', proxy_port,
14
+ # :user => 'user', :password => "password")
15
+ # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
16
+ # ...
17
+ # end
18
+ class SOCKS5
19
+ # The SOCKS protocol version used by this class
20
+ VERSION = 5
21
+
22
+ # The SOCKS authentication type for requests without authentication
23
+ METHOD_NO_AUTH = 0
24
+
25
+ # The SOCKS authentication type for requests via username/password
26
+ METHOD_PASSWD = 2
27
+
28
+ # The SOCKS authentication type for when there are no supported
29
+ # authentication methods.
30
+ METHOD_NONE = 0xFF
31
+
32
+ # The SOCKS packet type for requesting a proxy connection.
33
+ CMD_CONNECT = 1
34
+
35
+ # The SOCKS address type for connections via IP address.
36
+ ATYP_IPV4 = 1
37
+
38
+ # The SOCKS address type for connections via domain name.
39
+ ATYP_DOMAIN = 3
40
+
41
+ # The SOCKS response code for a successful operation.
42
+ SUCCESS = 0
43
+
44
+ # The proxy's host name or IP address
45
+ attr_reader :proxy_host
46
+
47
+ # The proxy's port number
48
+ attr_reader :proxy_port
49
+
50
+ # The map of options given at initialization
51
+ attr_reader :options
52
+
53
+ # Create a new proxy connection to the given proxy host and port.
54
+ # Optionally, :user and :password options may be given to
55
+ # identify the username and password with which to authenticate.
56
+ def initialize(proxy_host, proxy_port=1080, options={})
57
+ @proxy_host = proxy_host
58
+ @proxy_port = proxy_port
59
+ @options = options
60
+ end
61
+
62
+ # Return a new socket connected to the given host and port via the
63
+ # proxy that was requested when the socket factory was instantiated.
64
+ def open(host, port)
65
+ socket = TCPSocket.new(proxy_host, proxy_port)
66
+
67
+ methods = [METHOD_NO_AUTH]
68
+ methods << METHOD_PASSWD if options[:user]
69
+
70
+ packet = [VERSION, methods.size, *methods].pack("C*")
71
+ socket.send packet, 0
72
+
73
+ version, method = socket.recv(2).unpack("CC")
74
+ if version != VERSION
75
+ socket.close
76
+ raise Net::SSH::Proxy::Error, "invalid SOCKS version (#{version})"
77
+ end
78
+
79
+ if method == METHOD_NONE
80
+ socket.close
81
+ raise Net::SSH::Proxy::Error, "no supported authorization methods"
82
+ end
83
+
84
+ negotiate_password(socket) if method == METHOD_PASSWD
85
+
86
+ packet = [VERSION, CMD_CONNECT, 0].pack("C*")
87
+
88
+ if host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
89
+ packet << [ATYP_IPV4, $1.to_i, $2.to_i, $3.to_i, $4.to_i].pack("C*")
90
+ else
91
+ packet << [ATYP_DOMAIN, host.length, host].pack("CCA*")
92
+ end
93
+
94
+ packet << [port].pack("n")
95
+ socket.send packet, 0
96
+
97
+ version, reply, = socket.recv(4).unpack("C*")
98
+ len = socket.recv(1)[0]
99
+ socket.recv(len + 2)
100
+
101
+ unless reply == SUCCESS
102
+ socket.close
103
+ raise ConnectError, "#{reply}"
104
+ end
105
+
106
+ return socket
107
+ end
108
+
109
+ private
110
+
111
+ # Simple username/password negotiation with the SOCKS5 server.
112
+ def negotiate_password(socket)
113
+ packet = [0x01, options[:user].length, options[:user],
114
+ options[:password].length, options[:password]].pack("CCA*CA*")
115
+ socket.send packet, 0
116
+
117
+ version, status = socket.recv(2).unpack("CC")
118
+
119
+ if status != SUCCESS
120
+ socket.close
121
+ raise UnauthorizedError, "could not authorize user"
122
+ end
123
+ end
124
+ end
125
+
126
+ end
127
+ end
128
+ end