whoisd.rb 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. # Error classes
  2. # Generic error, when processing a request
  3. class WhoisError < StandardError; end
  4. # Client may try to harm the server. Abort immediately.
  5. class SuspiciousBehavior < WhoisError; end
  6. # The client query in invalid.
  7. class InvalidQuery < WhoisError; end
  8. # The requested object was not found in the registry.
  9. class ObjectNotFound < WhoisError; end
  10. # Class, which provides the actual Whois server
  11. class WhoisServer
  12. require 'async/io/host_endpoint'
  13. require 'async/io/stream'
  14. require 'optparse'
  15. require_relative 'registry'
  16. def initialize(args)
  17. # Ensure data types of arguments
  18. raise ArgumentError, 'Wrong argument format' unless args.is_a? Hash
  19. raise ArgumentError, 'Logger must be a logger' unless args[:logger].is_a? Logger
  20. raise ArgumentError, 'Registry must be a registry' unless args[:registry].is_a? Registry
  21. raise ArgumentError, 'Endpoint must be a array' unless args[:endpoint].is_a? Array
  22. raise ArgumentError, 'Endpoint has invalid length' unless args[:endpoint].length == 2
  23. raise ArgumentError, 'Host must be a string' unless args[:endpoint][0].is_a? String
  24. raise ArgumentError, 'Port must be a integer' unless args[:endpoint][1].is_a? Integer
  25. @registry = args[:registry]
  26. @logger = args[:logger]
  27. @endpoint = Async::IO::Endpoint.tcp(*args[:endpoint])
  28. end
  29. def run
  30. @endpoint.accept do |peer|
  31. @logger.debug "Incoming connection from #{peer.remote_address.ip_address} port #{peer.remote_address.ip_port}"
  32. stream = Async::IO::Stream.new(peer, sync: true)
  33. begin
  34. query = stream.read_partial 128
  35. # Possible attempt to damage the Whois server. Abort the request and log it.
  36. raise SuspiciousBehavior, "Long request from #{peer.remote_address.ip_address}" if query.length > 64
  37. unless query.end_with? "\r\n"
  38. raise SuspiciousBehavior, "Non RFC-compliant request from #{peer.remote_address.ip_address}"
  39. end
  40. # There is a query. Write greetings.
  41. stream.write "% This is the dn42 whois query service.\r\n\r\n"
  42. query.strip!
  43. # Request cannot be processed because it is empty.
  44. raise InvalidQuery, 'Empty query' if query.empty?
  45. # Splitting the query to parse the flags.
  46. pquery = query.split
  47. @logger.debug "Query: `#{query}`"
  48. # Parse flags
  49. flags = []
  50. opt_parser = OptionParser.new do |opts|
  51. opts.on('-f', '--filter') do
  52. flags << :filter
  53. end
  54. end
  55. opt_parser.parse! pquery
  56. # pquery still contains the unknown flags.
  57. raise InvalidQuery, 'Unknown flags' if pquery.length > 1
  58. # Flags were given, but this could not be recognized.
  59. raise InvalidQuery, 'Only flags given' if pquery.empty?
  60. # The actual request
  61. rquery = pquery[-1]
  62. # If the request contains flags, the known ones should be checked.
  63. if flags.empty?
  64. ipaddr = ipaddr? rquery
  65. data = ipaddr ? @registry.lookup_ipaddr(ipaddr) : @registry.lookup(rquery)
  66. elsif flags.include? :filter
  67. raise InvalidQuery, 'Unknown combination of flags' if flags.length > 1
  68. raise InvalidQuery, 'Invalid value for used flag' if rquery !~ /^filter\d?(?:\.txt)?$/i
  69. # Ensure that the extension .txt is present, but without duplication
  70. rquery.delete_suffix! '.txt'
  71. rquery = "#{rquery}.txt"
  72. data = @registry.lookup_direct(rquery)
  73. end
  74. # Does not found any data
  75. raise ObjectNotFound if data.empty?
  76. data.each do |path, cnt|
  77. stream.write "% Information related to '#{path}':\r\n"
  78. stream.write cnt
  79. stream.write "\r\n\r\n"
  80. end
  81. rescue ObjectNotFound
  82. stream.write "% Object Not Found\r\n\r\n"
  83. rescue InvalidQuery => e
  84. stream.write "% #{e.message}\r\n\r\n"
  85. ensure
  86. stream.close unless stream.closed?
  87. end
  88. rescue SuspiciousBehavior => e
  89. @logger.info "BAN: #{e.message}."
  90. ensure
  91. peer.close unless peer.closed?
  92. end
  93. end
  94. protected
  95. # Checks if the given object is an IP address. If yes, an IPAddr object with the IP is returned, otherwise false.
  96. #
  97. # @return [IPAddr, FalseClass]
  98. def ipaddr?(string)
  99. return IPAddr.new(string)
  100. rescue IPAddr::InvalidAddressError
  101. return false
  102. end
  103. end