PageRenderTime 51ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/net/ssh/proxy/socks5.rb

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