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
 
     |