require 'resolv' class DNSXLCheck class Resolver def self.search(query) begin Resolv.getaddress(query) true rescue Resolv::ResolvError false end end end IP_REGEXP = /\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z/ DEFAULT_THRESHOLD = 0.33 def self.create_from_list(list) dnsxl_check = DNSXLCheck.new list.each do |entry| dnsxl_check.add_list(entry.domain, entry.weight) end dnsxl_check end def test(ip) if use_threshold? test_with_threshold(ip) else test_strict(ip) end end def test_with_threshold(ip) return false if lists.empty? search(ip) final_score >= threshold end def test_strict(ip) return false if lists.empty? search(ip) @score > 0 end def use_threshold=(value) @use_threshold = value == true end def use_threshold? @use_threshold &&= true end def threshold=(threshold) raise ArgumentError, "'threshold' value must be grather than 0 and less than or equal to 1" unless threshold > 0 && threshold <= 1 @threshold = threshold end def threshold @threshold ||= DEFAULT_THRESHOLD end def add_list(domain, weight) @lists ||= [] @lists << { domain: domain, weight: weight } end def lists @lists ||= [] end private def search(ip) raise ArgumentError, "'ip' value must be in #{IP_REGEXP} format" unless ip.match(IP_REGEXP) @score = 0 reversed = reverse_ip(ip) search_in_rbls(reversed) end def reverse_ip(ip) ip.split('.').reverse.join('.') end def search_in_rbls(reversed_ip) lists.each do |rbl| query = "#{reversed_ip}.#{rbl[:domain]}" @score += rbl[:weight] if Resolver.search(query) end end def final_score weights = lists.map{ |rbl| rbl[:weight] }.reduce(:+).to_i return 0 if weights == 0 (@score.to_f / weights.to_f).round(2) end end