# -*- ruby -*-

require "resolv"
unless Thread.const_defined?(:Queue)
  require "thread"
  unless Thread.const_defined?(:Queue)
    class Thread
      Queue = ::Queue
    end
  end
end

dnsbl = Object.new
dnsbl.instance_eval do
  @services = []
  @dns_configration = nil
end

class << dnsbl
  TIMEOUT = 5

  def add_service(domain, expected_answer=nil)
    if expected_answer
      @services.push([domain, IPAddr.new(expected_answer)])
    else
      @services.push([domain, nil])
    end
  end

  def name_server=(name_server)
    @dns_configuration ||= {}
    @dns_configuration[:nameserver] = name_server
  end

  def name_servers=(name_servers)
    @dns_configuration ||= {}
    @dns_configuration[:nameserver] ||= []
    @dns_configuration[:nameserver] += name_servers
  end

  def listed?(address)
    return false unless address.ipv4?
    listed_address?(address)
  end

  private
  def listed_address?(address)
    rev_address = address.address.split(".").reverse.join(".")

    answers = Thread::Queue.new

    threads = []
    @services.each do |domain, expected_answer|
      query_domain = rev_address + "." + domain

      threads << Thread.new do
        resolver = Resolv::DNS.new(@dns_configuration)
        resolver.timeouts = TIMEOUT

        begin
          answer = resolver.getaddress(query_domain)
          if expected_answer
            answers.push(expected_answer.include?(answer.to_s))
          else
            answers.push(true)
          end
        rescue Exception => _ # or Resolv::ResolvError
          answers.push(false)
        end
      end
    end

    listed = false
    @services.size.times do
      answer = answers.pop
      if answer
        listed = true
        break
      end
    end
    threads.each(&:kill)
    threads.each(&:join)
    listed
  end
end

singleton_class = class << self; self; end
singleton_class.send(:define_method, :dnsbl) do
  dnsbl
end

dnsbl.add_service("zen.spamhaus.org", "127.0.0.10/31")
dnsbl.add_service("dnsbl.sorbs.net", "127.0.0.10")
dnsbl.add_service("bl.spamcop.net", "127.0.0.2")
dnsbl.add_service("b.barracudacentral.org", "127.0.0.2")

# dnsbl.name_servers = ["8.8.8.8", "8.8.4.4"]

define_applicable_condition("DNSBL Listed") do |condition|
  condition.description =
    "Apply a milter only when connected host is listed in " +
    "DNS-based Blackhole List"

  condition.define_connect_stopper do |context, host, address|
    not dnsbl.listed?(address)
  end
end

define_applicable_condition("Not DNSBL Listed") do |condition|
  condition.description =
    "Apply a milter only when connected host is not listed in " +
    "DNS-based Blackhole List"

  condition.define_connect_stopper do |context, host, address|
    dnsbl.listed?(address)
  end
end
