lib/sockit.rb in sockit-0.0.4 vs lib/sockit.rb in sockit-1.0.0

- old
+ new

@@ -16,350 +16,80 @@ # See the License for the specific language governing permissions and # limitations under the License. # ################################################################################ -require "socket" -require "resolv" +require 'socket' +require 'resolv' +require 'ostruct' -require "sockit/version" +require 'sockit/version' +require 'sockit/v4/connection' +require 'sockit/v4/support' + +require 'sockit/v5/authentication' +require 'sockit/v5/connection' +require 'sockit/v5/support' + +require 'sockit/connection' +require 'sockit/support' + class SockitError < RuntimeError; end module Sockit DEFAULT_CONFIG = { :version => 5, - :ignore => ["127.0.0.1"], - :debug => false + :ignore => %w( 127.0.0.1 ), + :debug => false } COLORS = { - :reset => "\e[0m\e[37m", - :red => "\e[1m\e[31m", - :green => "\e[1m\e[32m", - :yellow => "\e[1m\e[33m" + :reset => "\e[0m\e[37m", + :red => "\e[1m\e[31m", + :green => "\e[1m\e[32m", + :yellow => "\e[1m\e[33m", + :blue => "\e[1m\e[34m" } - class << self - - def debug(color, message) - timestamp = Time.now.utc - puts("%s%s.%06d %s%s" % [COLORS[color], timestamp.strftime("%Y-%m-%d|%H:%M:%S"), timestamp.usec, message, COLORS[:reset]]) - end - - def dump(action, data) - bytes = Array.new - chars = Array.new - for x in 0..(data.length - 1) do - bytes << ("%03d" % data[x].ord) - chars << ("%03s" % (data[x] =~ /^\w+$/ ? data[x].chr : "...")) - end - debug(:red, "#{action.to_s.upcase}: #{bytes.join(" ")}#{COLORS[:reset]}") - debug(:red, "#{action.to_s.upcase}: #{chars.join(" ")}#{COLORS[:reset]}") - end - - # 0x00 = request granted - # 0x01 = general failure - # 0x02 = connection not allowed by ruleset - # 0x03 = network unreachable - # 0x04 = host unreachable - # 0x05 = connection refused by destination host - # 0x06 = TTL expired - # 0x07 = command not supported / protocol error - # 0x08 = address type not supported - def status_message(status_code) - case status_code - when 0x00 then - "Request granted (Code: 0x%02X)" % status_code - when 0x01 then - "General failure (Code: 0x%02X)" % status_code - when 0x02 then - "Connection not allowed by ruleset (Code: 0x%02X)" % status_code - when 0x03 then - "Network unreachable (Code: 0x%02X)" % status_code - when 0x04 then - "Host unreachable (Code: 0x%02X)" % status_code - when 0x05 then - "Connection refused by destination host (Code: 0x%02X)" % status_code - when 0x06 then - "TTL expired (Code: 0x%02X)" % status_code - when 0x07 then - "Command not supported / Protocol error (Code: 0x%02X)" % status_code - when 0x08 then - "Address type not supported (Code: 0x%02X)" % status_code - else - "Unknown (Code: 0x%02X)" % status_code - end - end - - # The authentication methods supported are numbered as follows: - # 0x00: No authentication - # 0x01: GSSAPI[10] - # 0x02: Username/Password[11] - # 0x03-0x7F: methods assigned by IANA[12] - # 0x80-0xFE: methods reserved for private use - def authentication_method(auth_method) - case auth_method - when 0x00 then - "No authentication (Code: 0x%02X)" % auth_method - when 0x01 then - "GSSAPI authentication (Code: 0x%02X)" % auth_method - when 0x02 then - "Username/Password authentication (Code: 0x%02X)" % auth_method - when 0x03..0x7F then - "Method assigned by IANA (Code: 0x%02X)" % auth_method - when 0x80..0xFE then - "Method reserved for private use (Code: 0x%02X)" % auth_method - when 0xFF then - "Unsupported (Code: 0x%02X)" % auth_method - else - "Unknown (Code: 0x%02X)" % auth_method - end - end - - # 0x00 = success - # any other value = failure, connection must be closed - def authentication_status(auth_status) - case auth_status - when 0x00 then - "Authentication success (Code: 0x%02X)" % auth_status - else - "Authentication failure (Code: 0x%02X)" % auth_status - end - end - - end -end - -class TCPSocket - - class << self - - def socks(&block) - @@socks ||= OpenStruct.new(Sockit::DEFAULT_CONFIG) - if block_given? - yield(@@socks) - else - @@socks - end - end - - end - - def socks(&block) - @@socks ||= OpenStruct.new(Sockit::DEFAULT_CONFIG) + def self.config(&block) + @@config ||= OpenStruct.new(Sockit::DEFAULT_CONFIG) if block_given? - yield(@@socks) + yield(@@config) else - @@socks + @@config end end - alias :initialize_tcp :initialize - def initialize(remote_host, remote_port, local_host=nil, local_port=nil) - if (socks.host && socks.port && !socks.ignore.flatten.include?(remote_host)) - Sockit.debug(:yellow, "Connecting to SOCKS server #{socks.host}:#{socks.port}") - initialize_tcp(socks.host, socks.port) - (socks.version.to_i == 5) and socks_authenticate - socks.host and socks_connect(remote_host, remote_port) - Sockit.debug(:green, "Connected to #{remote_host}:#{remote_port} via SOCKS server #{socks.host}:#{socks.port}") + def config(&block) + @@config ||= OpenStruct.new(Sockit::DEFAULT_CONFIG) + if block_given? + yield(@@config) else - Sockit.debug(:yellow, "Directly connecting to #{remote_host}:#{remote_port}") - initialize_tcp(remote_host, remote_port, local_host, local_port) - Sockit.debug(:green, "Connected to #{remote_host}:#{remote_port}") + @@config end end - def socks_authenticate - # The authentication methods supported are numbered as follows: - # 0x00: No authentication - # 0x01: GSSAPI[10] - # 0x02: Username/Password[11] - # 0x03-0x7F: methods assigned by IANA[12] - # 0x80-0xFE: methods reserved for private use + extend Sockit::V5::Authentication + extend Sockit::V5::Connection + extend Sockit::V5::Support - # The initial greeting from the client is - # field 1: SOCKS version number (must be 0x05 for this version) - # field 2: number of authentication methods supported, 1 byte - # field 3: authentication methods, variable length, 1 byte per method supported - if (socks.username || socks.password) - data = Array.new - data << [socks.version, 0x02, 0x02, 0x00].pack("C*") - data = data.flatten.join + extend Sockit::V4::Connection + extend Sockit::V4::Support - socks.debug and Sockit.debug(:yellow, "Requesting username/password authentication") - socks.debug and Sockit.dump(:write, data) - write(data) - else - data = Array.new - data << [socks.version, 0x01, 0x00].pack("C*") - data = data.flatten.join + extend Sockit::Connection + extend Sockit::Support +end - socks.debug and Sockit.debug(:yellow, "Requesting no authentication") - socks.debug and Sockit.dump(:write, data) - write(data) - end +class TCPSocket - # The server's choice is communicated: - # field 1: SOCKS version, 1 byte (0x05 for this version) - # field 2: chosen authentication method, 1 byte, or 0xFF if no acceptable methods were offered - socks.debug and Sockit.debug(:yellow, "Waiting for SOCKS authentication reply") - auth_reply = recv(2).unpack("C*") - socks.debug and Sockit.dump(:read, auth_reply) - server_socks_version = auth_reply[0] - server_auth_method = auth_reply[1] - - if server_socks_version != socks.version - raise SockitError, "SOCKS server does not support version #{socks.version}!" - end - - if server_auth_method == 0xFF - raise SockitError, Sockit.authentication_method(server_auth_method) + alias :initialize_tcp :initialize + def initialize(remote_host, remote_port, local_host=nil, local_port=nil) + if Sockit.connect_via_socks?(remote_host) + initialize_tcp(Sockit.config.host, Sockit.config.port) + Sockit.perform_v5_authenticate(self) if Sockit.is_socks_v5? + Sockit.connect(self, remote_host, remote_port) else - socks.debug and Sockit.debug(:green, Sockit.authentication_method(server_auth_method)) + Sockit.direct_connect(self, remote_host, remote_port, local_host, local_port) end - - # The subsequent authentication is method-dependent. Username and password authentication (method 0x02) is described in RFC 1929: - case server_auth_method - when 0x00 then - # No authentication - when 0x01 then - # GSSAPI - raise SockitError, "Authentication method GSSAPI not implemented" - when 0x02 then - # For username/password authentication the client's authentication request is - # field 1: version number, 1 byte (must be 0x01) - # field 2: username length, 1 byte - # field 3: username - # field 4: password length, 1 byte - # field 5: password - data = Array.new - data << [0x01].pack("C*") - data << [socks.username.length.to_i].pack("C*") - data << socks.username - data << [socks.password.length.to_i].pack("C*") - data << socks.password - data = data.flatten.join - - socks.debug and Sockit.debug(:yellow, "Sending username and password") - socks.debug and Sockit.dump(:write, data) - write(data) - - # Server response for username/password authentication: - # field 1: version, 1 byte - # field 2: status code, 1 byte. - # 0x00 = success - # any other value = failure, connection must be closed - socks.debug and Sockit.debug(:yellow, "Waiting for SOCKS authentication reply") - auth_reply = recv(2).unpack("C*") - socks.debug and Sockit.dump(:read, auth_reply) - version = auth_reply[0] - status_code = auth_reply[1] - - if status_code == 0x00 - socks.debug and Sockit.debug(:green, Sockit.authentication_status(status_code)) - else - raise SockitError, Sockit.authentication_status(status_code) - end - end - end - - def socks_connect(remote_host, remote_port) - # The client's connection request is - # field 1: SOCKS version number, 1 byte (must be 0x05 for this version) - # field 2: command code, 1 byte: - # 0x01 = establish a TCP/IP stream connection - # 0x02 = establish a TCP/IP port binding - # 0x03 = associate a UDP port - # field 3: reserved, must be 0x00 - # field 4: address type, 1 byte: - # 0x01 = IPv4 address - # 0x03 = Domain name - # 0x04 = IPv6 address - # field 5: destination address of - # 4 bytes for IPv4 address - # 1 byte of name length followed by the name for Domain name - # 16 bytes for IPv6 address - # field 6: port number in a network byte order, 2 bytes - data = Array.new - data << [ socks.version.to_i, 0x01, 0x00 ].pack("C*") - - # when doing proxy mode on SS5; we seemingly need to resolve all names first. - if remote_host !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ - remote_host = Resolv::DNS.new.getaddress(remote_host).to_s - end - - if remote_host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ - data << [0x01].pack("C*") - data << [$1.to_i, $2.to_i, $3.to_i, $4.to_i].pack("C*") - elsif remote_host =~ /^[:0-9a-f]+$/ - data << [0x04].pack("C*") - data << [$1].pack("C*") - else - data << [0x03].pack("C*") - data << [remote_host.length.to_i].pack("C*") - data << remote_host - end - data << [remote_port].pack("n") - data = data.flatten.join - - Sockit.debug(:yellow, "Requesting SOCKS connection to #{remote_host}:#{remote_port}") - socks.debug and Sockit.dump(:write, data) - write(data) - - # Server response: - # field 1: SOCKS protocol version, 1 byte (0x05 for this version) - # field 2: status, 1 byte: - # 0x00 = request granted - # 0x01 = general failure - # 0x02 = connection not allowed by ruleset - # 0x03 = network unreachable - # 0x04 = host unreachable - # 0x05 = connection refused by destination host - # 0x06 = TTL expired - # 0x07 = command not supported / protocol error - # 0x08 = address type not supported - # field 3: reserved, must be 0x00 - # field 4: address type, 1 byte: - # 0x01 = IPv4 address - # 0x03 = Domain name - # 0x04 = IPv6 address - # field 5: destination address of - # 4 bytes for IPv4 address - # 1 byte of name length followed by the name for Domain name - # 16 bytes for IPv6 address - # field 6: network byte order port number, 2 bytes - socks.debug and Sockit.debug(:yellow, "Waiting for SOCKS connection reply") - packet = recv(4).unpack("C*") - socks.debug and Sockit.dump(:read, packet) - socks_version = packet[0] - status_code = packet[1] - reserved = packet[2] - address_type = packet[3] - - if status_code == 0x00 - socks.debug and Sockit.debug(:green, Sockit.status_message(status_code)) - else - raise SockitError, Sockit.status_message(status_code) - end - - address_length = case address_type - when 0x01 then - 4 - when 0x03 then - data = recv(1).unpack("C*") - socks.debug and Sockit.dump(:read, data) - data[0] - when 0x04 then - 16 - end - address = recv(address_length).unpack("C*") - socks.debug and Sockit.dump(:read, address) - - port = recv(2).unpack("n") - socks.debug and Sockit.dump(:read, port) - - socks.debug and Sockit.debug(:green, [address, port].inspect) - end - end