lib/dns_one/zone_search.rb
require "dns_one/cache"
require 'dns_one/backend/base'
require 'dns_one/backend/http_bell'
require 'dns_one/backend/file'
require 'dns_one/backend/db'
module DnsOne; class ZoneSearch
include Singleton
Name = Resolv::DNS::Name
IN = Resolv::DNS::Resource::IN
def setup
@conf = Global.conf
check_record_sets
@backend = set_backend
@cache = Cache.new Global.conf.cache_max
@ignore_subdomains_re = build_ignore_subdomains_re
if @backend.preload_dummy?
query 'dummy.com.br', Resolv::DNS::Resource::IN, '1.2.3.4'
end
self
end
def query dom_name, res_class, ip_address
return unless dom_name =~ Util::DOM_REGEX
dom_name = dom_name.dup
res_class_short = Util.last_mod res_class # :A, :NS, found in conf.yml:record_sets items
Global.logger.debug "request #{ dom_name }/#{res_class_short} from #{ip_address}..."
records = []
rec_set_name, from_cache = find_record_set dom_name
Global.logger.debug "domain #{ rec_set_name ? "found, rec_set_name = '#{rec_set_name}'" : 'not found' }"
return unless rec_set_name
# use first record set if rec_set_name == ''
rec_set_name = @conf.record_sets.to_h.keys.first if rec_set_name == ''
rec_set = @conf.record_sets[rec_set_name]
Global.logger.debug "record set #{ rec_set ? 'found' : 'not found' }"
return records unless rec_set
# TODO: move parsing logic to own class
recs = rec_set[res_class_short.to_sym]
Global.logger.debug "record(s) #{ recs ? 'found' : 'not found' }"
# Loop over 1 or more
recs = [recs]
recs.flatten! unless res_class == IN::SOA
recs.compact.each do |val_raw|
val = if res_class == IN::NS
Name.create val_raw
elsif res_class == IN::SOA
[0, 1].each{|i| val_raw[i] = Name.create val_raw[i] }
val_raw
else
val_raw
end
records << OpenStruct.new(val: val, res_class: res_class, section: 'answer')
end
[records, from_cache]
end
private
def build_ignore_subdomains_re
if i = @conf.ignore_subdomains.presence
s = i.strip.split(/\s+/).map(&:downcase).join '|'
/^(#{ s })\./i
end
end
def set_backend
if file = @conf.backend.file
unless ::File.exists? file
Util.die "Domain list file #{file} not found (pwd = #{Dir.pwd})."
end
Backend::File.new file
elsif @conf.backend.http_bell_url
unless @conf.backend.http_bell_record_set
Util.die "backend.http_bell_record_set not set."
end
Backend::HTTPBell.new @conf.backend
else
Backend::DB.new @conf.backend
end
end
def find_record_set dom_name
dom_name, use_cache = check_debug_tags dom_name
dom_name = normalize_domain dom_name
enabled_cache = use_cache && @backend.allow_cache
if enabled_cache and rec_set = @cache.find(dom_name)
Global.logger.debug "found in cache (#{@cache.stat})"
[rec_set, true]
else
if rec_set = @backend.find(dom_name)
@cache.add dom_name, rec_set if enabled_cache
[rec_set, false]
end
end
end
def normalize_domain dom_name
dom_name = dom_name.dup
dom_name.sub! @ignore_subdomains_re, '' if @ignore_subdomains_re
dom_name.downcase!
dom_name.sub! /\.home$/i, ''
dom_name
end
def check_debug_tags dom_name
use_cache = true
if dom_name.sub!(/^NC/, '')
use_cache = false
end
[dom_name, use_cache]
end
def check_record_sets
if @conf.record_sets.blank?
Util.die "Record sets cannot be empty. Check file."
end
@conf.record_sets.each_pair do |rec_set_name, records|
unless records[:NS] and records[:NS].length >= 1
Util.die "Record set #{rec_set_name} is invalid. It must have at least 1 NS record."
end
unless records[:SOA] and records[:SOA].length == 7
Util.die "Record set #{rec_set_name} is invalid. It must have a valid SOA record."
end
end
end
end; end