123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- # Error classes
- # Generic error, when processing a request
- class WhoisError < StandardError; end
- # Client may try to harm the server. Abort immediately.
- class SuspiciousBehavior < WhoisError; end
- # The client query in invalid.
- class InvalidQuery < WhoisError; end
- # The requested object was not found in the registry.
- class ObjectNotFound < WhoisError; end
- # Class, which provides the actual Whois server
- class WhoisServer
- require 'async/io/host_endpoint'
- require 'async/io/stream'
- require 'optparse'
- require_relative 'registry'
- def initialize(args)
- # Ensure data types of arguments
- raise ArgumentError, 'Wrong argument format' unless args.is_a? Hash
- raise ArgumentError, 'Logger must be a logger' unless args[:logger].is_a? Logger
- raise ArgumentError, 'Registry must be a registry' unless args[:registry].is_a? Registry
- raise ArgumentError, 'Endpoint must be a array' unless args[:endpoint].is_a? Array
- raise ArgumentError, 'Endpoint has invalid length' unless args[:endpoint].length == 2
- raise ArgumentError, 'Host must be a string' unless args[:endpoint][0].is_a? String
- raise ArgumentError, 'Port must be a integer' unless args[:endpoint][1].is_a? Integer
- @registry = args[:registry]
- @logger = args[:logger]
- @endpoint = Async::IO::Endpoint.tcp(*args[:endpoint])
- end
- def run
- @endpoint.accept do |peer|
- @logger.debug "Incoming connection from #{peer.remote_address.ip_address} port #{peer.remote_address.ip_port}"
- stream = Async::IO::Stream.new(peer, sync: true)
- begin
- query = stream.read_partial 128
- # Possible attempt to damage the Whois server. Abort the request and log it.
- raise SuspiciousBehavior, "Long request from #{peer.remote_address.ip_address}" if query.length > 64
- unless query.end_with? "\r\n"
- raise SuspiciousBehavior, "Non RFC-compliant request from #{peer.remote_address.ip_address}"
- end
- # There is a query. Write greetings.
- stream.write "% This is the dn42 whois query service.\r\n\r\n"
- query.strip!
- # Request cannot be processed because it is empty.
- raise InvalidQuery, 'Empty query' if query.empty?
- # Splitting the query to parse the flags.
- pquery = query.split
- @logger.debug "Query: `#{query}`"
- # Parse flags
- flags = []
- opt_parser = OptionParser.new do |opts|
- opts.on('-f', '--filter') do
- flags << :filter
- end
- end
- opt_parser.parse! pquery
- # pquery still contains the unknown flags.
- raise InvalidQuery, 'Unknown flags' if pquery.length > 1
- # Flags were given, but this could not be recognized.
- raise InvalidQuery, 'Only flags given' if pquery.empty?
- # The actual request
- rquery = pquery[-1]
- # If the request contains flags, the known ones should be checked.
- if flags.empty?
- ipaddr = ipaddr? rquery
- data = ipaddr ? @registry.lookup_ipaddr(ipaddr) : @registry.lookup(rquery)
- elsif flags.include? :filter
- raise InvalidQuery, 'Unknown combination of flags' if flags.length > 1
- raise InvalidQuery, 'Invalid value for used flag' if rquery !~ /^filter\d?(?:\.txt)?$/i
- # Ensure that the extension .txt is present, but without duplication
- rquery.delete_suffix! '.txt'
- rquery = "#{rquery}.txt"
- data = @registry.lookup_direct(rquery)
- end
- # Does not found any data
- raise ObjectNotFound if data.empty?
- data.each do |path, cnt|
- stream.write "% Information related to '#{path}':\r\n"
- stream.write cnt
- stream.write "\r\n\r\n"
- end
- rescue ObjectNotFound
- stream.write "% Object Not Found\r\n\r\n"
- rescue InvalidQuery => e
- stream.write "% #{e.message}\r\n\r\n"
- ensure
- stream.close unless stream.closed?
- end
- rescue SuspiciousBehavior => e
- @logger.info "BAN: #{e.message}."
- ensure
- peer.close unless peer.closed?
- end
- end
- protected
- # Checks if the given object is an IP address. If yes, an IPAddr object with the IP is returned, otherwise false.
- #
- # @return [IPAddr, FalseClass]
- def ipaddr?(string)
- return IPAddr.new(string)
- rescue IPAddr::InvalidAddressError
- return false
- end
- end
|