Project

General

Profile

Download (4.4 KB) Statistics
| Branch: | Tag: | Revision:
require 'open3'

module Proxy::Dns::Dnscmd
class Record < ::Proxy::Dns::Record
include Proxy::Log
include Proxy::Util

def initialize(a_server, a_ttl)
super(a_server, a_ttl)
end

def do_create(name, value, type)
zone = match_zone(name, enum_zones)
msg = "Added #{type} entry #{name} => #{value}"
value = "#{value}." if type == "PTR"
execute("/RecordAdd", zone, "#{name}.", type, value, msg: msg, error_only: false)
nil
end

def remove_specific_record_from_zone(zone_name, node_name, record, type)
msg = "Removed #{record} #{type} record #{node_name} from #{zone_name}"
execute("/RecordDelete", zone_name, "#{node_name}.", type, record, "/f", msg: msg, error_only: false)
nil
end

def do_remove(name, type)
zone = match_zone(name, enum_zones)
enum_records(zone, name, type).each do |specific_record|
remove_specific_record_from_zone(zone, name, specific_record, type)
end
nil
end

def execute(*args, **keyword_args)
tsecs = 5
response = nil
std_in = std_out = std_err = nil
real_cmd = ['c:\Windows\System32\dnscmd.exe', @server] + args
begin
timeout(tsecs) do
logger.debug { "executing: #{real_cmd}" }
std_in, std_out, std_err = popen3(*real_cmd)
response = [std_out&.readlines, std_err&.readlines].flatten.compact
response = nil if response == []
end
rescue Timeout::Error
raise Proxy::Dns::Error.new("dnscmd did not respond within #{tsecs} seconds")
ensure
std_in&.close
std_out&.close
std_err&.close
end
report keyword_args[:msg], response, keyword_args[:error_only] || false
response
end

def report(msg, response, error_only)
if response.nil?
logger.warn "Response from popen3 was nil."
return
end

if response.grep(/completed successfully/).empty?
logger.error "Command dnscmd failed:\n" + response.join("\n")
msg.sub!(/Removed/, "remove")
msg.sub!(/Added/, "add")
msg = "Failed to #{msg}"
raise Proxy::Dns::Error.new(msg) unless response.grep(/DNS_ERROR_NAME_DOES_NOT_EXIST/).any? && msg == "Failed to EnumRecords"
else
logger.debug msg unless error_only
end
rescue Proxy::Dns::Error
raise
rescue
logger.error "Command dnscmd failed:\n" + (response.is_a?(Array) ? response.join("\n") : "Response was not an array! #{response}")
raise Proxy::Dns::Error.new("Unknown error while processing '#{msg}'")
end

def match_zone(record, zone_list)
weight = 0 # sub zones might be independent from similar named parent zones; use weight for longest suffix match
matched_zone = nil
zone_list.each do |zone|
zone_labels = zone.downcase.split(".").reverse
zone_weight = zone_labels.length
fqdn_labels = record.downcase.split(".")
fqdn_labels.shift
is_match = zone_labels.all? { |zone_label| zone_label == fqdn_labels.pop }
# match only the longest zone suffix
if is_match && zone_weight >= weight
matched_zone = zone
weight = zone_weight
end
end
raise Proxy::Dns::NotFound.new("The DNS server has no authoritative zone for #{record}") unless matched_zone
matched_zone
end

def enum_zones
zones = []
response = execute '/EnumZones', error_only: true
response.each do |line|
next unless line =~ / Primary /
zones << line.sub(/^ +/, '').sub(/ +.*$/, '').chomp("\n")
end
logger.debug "Enumerated authoritative dns zones: #{zones}"
zones
end

def enum_records(zone_name, node_name, type)
records = []
response = execute "/EnumRecords", zone_name, "#{node_name}.", "/Type", type, error_only: true
response.each do |line|
line.chomp!
logger.debug "Extracting record from dnscmd output '#{line}'"
/^@?\s+\d+\s+(A|AAAA|PTR|CNAME)+\s+(?<record>\S+)/ =~ line
if record.nil?
logger.debug "No DNS record found in this line"
else
logger.debug "Found record '#{record}'"
records << record
end
end
logger.debug "Enumerated #{records.size} #{type} records for zone=#{zone_name} node=#{node_name} records=#{records}"
records
end

private

def popen3(*args)
Open3.popen3(*args)
end
end
end
(2-2/4)