root/lib/cyborghood-mapmaker/dns/base.rb @ 51546fe5
90197e7b | Marc Dequènes (Duck) | #--
|
|
# CyborgHood, a distributed system management software.
|
|||
364e4a96 | Marc Dequènes (Duck) | # Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
|
|
90197e7b | Marc Dequènes (Duck) | #
|
|
# This program is free software: you can redistribute it and/or modify
|
|||
# it under the terms of the GNU General Public License as published by
|
|||
# the Free Software Foundation, either version 3 of the License, or
|
|||
# (at your option) any later version.
|
|||
#
|
|||
# This program is distributed in the hope that it will be useful,
|
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
# GNU General Public License for more details.
|
|||
#
|
|||
# You should have received a copy of the GNU General Public License
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#++
|
|||
require 'digest/md5'
|
|||
require 'tempfile'
|
|||
require 'fileutils'
|
|||
require 'cyborghood-mapmaker/zone_content'
|
|||
module CyborgHood
|
|||
module MapMakerLand
|
|||
class DNSBase
|
|||
attr_reader :master_zones, :slave_zones
|
|||
def initialize(config)
|
|||
@config = config
|
|||
@master_zone_files_pattern = @config.dns.master_zone_pattern.gsub("#ZONE#", "*")
|
|||
@master_zone_files_regex = Regexp.new("^" + @config.dns.master_zone_pattern.gsub("#ZONE#", "(.*)") + "$")
|
|||
@slave_zone_files_pattern = @config.dns.slave_zone_pattern.gsub("#ZONE#", "*")
|
|||
@slave_zone_files_regex = Regexp.new("^" + @config.dns.slave_zone_pattern.gsub("#ZONE#", "(.*)") + "$")
|
|||
flush_cache
|
|||
end
|
|||
def zones
|
|||
self.master_zones + self.slave_zones
|
|||
end
|
|||
def get_zone(zone_name)
|
|||
self.zones.include?(zone_name) ? DNSZone.new(zone_name) : nil
|
|||
end
|
|||
def get_zone_file(zone_name)
|
|||
self.zones.include?(zone_name) ? DNSZoneFile.new(@config, zone_name) : nil
|
|||
end
|
|||
def software
|
|||
@config.dns.software
|
|||
end
|
|||
def info
|
|||
{
|
|||
:software => self.software
|
|||
}
|
|||
end
|
|||
def flush_cache
|
|||
# TODO: fetch more zone info in bind config
|
|||
# master zones
|
|||
@master_zones = Dir.glob(@master_zone_files_pattern).collect do |file|
|
|||
$1 if file =~ @master_zone_files_regex
|
|||
end
|
|||
# slave zones
|
|||
@slave_zones = Dir.glob(@slave_zone_files_pattern).collect do |file|
|
|||
$1 if file =~ @slave_zone_files_regex
|
|||
end
|
|||
end
|
|||
# methods a backend MUST implement
|
|||
# - check_config
|
|||
# - status
|
|||
end
|
|||
class DNSZone < ZoneContentBase
|
|||
def initialize(name)
|
|||
super
|
|||
@resolver = Dnsruby::Resolver.new
|
|||
end
|
|||
def find_rr(rr_type)
|
|||
logger.debug "Querying '#{rr_type}' for domain '#{@name}'"
|
|||
soa = @resolver.query(@name, rr_type).answer{|rr| rr.type == rr_type}
|
|||
end
|
|||
def info
|
|||
{
|
|||
:serial => self.serial,
|
|||
:is_signed => self.signed?
|
|||
}
|
|||
end
|
|||
end
|
|||
class DNSZoneFileBase
|
|||
attr_reader :filename, :filename_signed
|
|||
def initialize(config, name)
|
|||
@config = config
|
|||
@name = name
|
|||
@content = nil
|
|||
@temp_file = nil
|
|||
master_filename = @config.dns.master_zone_pattern.gsub("#ZONE#", @name)
|
|||
slave_filename = @config.dns.slave_zone_pattern.gsub("#ZONE#", @name)
|
|||
if File.exists? master_filename
|
|||
@master = true
|
|||
@filename = master_filename
|
|||
@filename_signed = @config.dns.signed_master_zone_pattern.gsub("#ZONE#", @name) if @config.dns.signed_master_zone_pattern
|
|||
elsif File.exists? slave_filename
|
|||
@master = false
|
|||
@filename = slave_filename
|
|||
else
|
|||
raise CyberError.new(:unrecoverable, "services/dns", "nonexistent zone '#{@name}'")
|
|||
end
|
|||
end
|
|||
def master?
|
|||
@master
|
|||
end
|
|||
def has_signed_zone_file?
|
|||
master? and File.exists?(@filename_signed)
|
|||
end
|
|||
def content
|
|||
read_zone(@filename) if @content.nil?
|
|||
@content
|
|||
end
|
|||
def signed_content
|
|||
39f1d449 | Marc Dequènes (Duck) | read_zone(@filename_signed) if has_signed_zone_file?
|
|
90197e7b | Marc Dequènes (Duck) | end
|
|
def content=(c)
|
|||
if not master?
|
|||
raise CyberError.new(:unrecoverable, "services/dns", "cannot change content of non-master zone '#{@name}'")
|
|||
end
|
|||
@content = c.to_s
|
|||
end
|
|||
def parsed_content(on_disk = false)
|
|||
f234746f | Marc Dequènes (Duck) | begin
|
|
parsed_content = ZoneContent.new(@name)
|
|||
if on_disk or not changed?
|
|||
parsed_content.import_from_file(@filename)
|
|||
else
|
|||
parsed_content.content = @content
|
|||
end
|
|||
parsed_content
|
|||
rescue Dnsruby::ZoneReader::ParseException
|
|||
# return nil is not parseable
|
|||
90197e7b | Marc Dequènes (Duck) | end
|
|
end
|
|||
def parsed_signed_content
|
|||
39f1d449 | Marc Dequènes (Duck) | return unless has_signed_zone_file?
|
|
90197e7b | Marc Dequènes (Duck) | ||
f234746f | Marc Dequènes (Duck) | begin
|
|
parsed_content = ZoneContent.new(@name)
|
|||
parsed_content.import_from_file(@filename_signed)
|
|||
parsed_content
|
|||
rescue Dnsruby::ZoneReader::ParseException
|
|||
# return nil is not parseable
|
|||
end
|
|||
90197e7b | Marc Dequènes (Duck) | end
|
|
def changed?
|
|||
return false if @content.nil?
|
|||
# if original hash is missing, save zone content, reload
|
|||
# original file, compute hash, and restore previous content
|
|||
if @content_hash.nil?
|
|||
content_backup = @content
|
|||
@content = nil
|
|||
content
|
|||
@content = content_backup
|
|||
end
|
|||
Digest::MD5.hexdigest(@content) != @content_hash
|
|||
end
|
|||
def cancel_changes
|
|||
@content = nil
|
|||
cleanup_temp
|
|||
end
|
|||
def import_from_file(new_zone_filename)
|
|||
read_zone(new_zone_filename)
|
|||
end
|
|||
def import_from_backup
|
|||
read_zone(self.backup_filename)
|
|||
end
|
|||
def create_backup
|
|||
FileUtils.cp(@filename, backup_filename())
|
|||
end
|
|||
def save
|
|||
raise CyberError.new(:unrecoverable, "services/dns", "won't save an empty zone file") if @content.nil?
|
|||
write_zone(@filename)
|
|||
update_hash
|
|||
cleanup_temp
|
|||
end
|
|||
def info
|
|||
39f1d449 | Marc Dequènes (Duck) | hszf = self.has_signed_zone_file?
|
|
f234746f | Marc Dequènes (Duck) | p_c = self.parsed_content
|
|
39f1d449 | Marc Dequènes (Duck) | data = {
|
|
90197e7b | Marc Dequènes (Duck) | :is_master => self.master?,
|
|
39f1d449 | Marc Dequènes (Duck) | :has_signed_zone_file => hszf,
|
|
f234746f | Marc Dequènes (Duck) | :serial_in_zone_file => p_c.nil? ? nil : p_c.serial
|
|
90197e7b | Marc Dequènes (Duck) | }
|
|
39f1d449 | Marc Dequènes (Duck) | ||
if hszf
|
|||
f234746f | Marc Dequènes (Duck) | s_p_c = self.parsed_signed_content
|
|
39f1d449 | Marc Dequènes (Duck) | data.merge!({
|
|
f234746f | Marc Dequènes (Duck) | :serial_in_signed_zone_file => s_p_c.nil? ? nil : s_p_c.serial
|
|
39f1d449 | Marc Dequènes (Duck) | })
|
|
end
|
|||
data
|
|||
90197e7b | Marc Dequènes (Duck) | end
|
|
def __destroy
|
|||
cleanup_temp
|
|||
end
|
|||
# methods a backend MUST implement
|
|||
# - activate
|
|||
# - check
|
|||
protected
|
|||
def backup_filename
|
|||
@filename + ".ch-backup"
|
|||
end
|
|||
def save_to_temp
|
|||
return unless @temp_file.nil?
|
|||
begin
|
|||
@temp_file = Tempfile.new(@name)
|
|||
@temp_file.write(@content)
|
|||
@temp_file.close
|
|||
rescue
|
|||
raise CyberError.new(:unrecoverable, "services/dns", "could not save temporary zone")
|
|||
end
|
|||
end
|
|||
def cleanup_temp
|
|||
return if @temp_file.nil?
|
|||
@temp_file.close!
|
|||
@temp_file = nil
|
|||
end
|
|||
def temp_filename
|
|||
@temp_file.path
|
|||
end
|
|||
def current_filename
|
|||
if changed?
|
|||
save_to_temp
|
|||
return temp_filename
|
|||
end
|
|||
@filename
|
|||
end
|
|||
def update_hash
|
|||
@content_hash = Digest::MD5.hexdigest(@content)
|
|||
end
|
|||
def read_zone(filename)
|
|||
begin
|
|||
@content = File.read(filename)
|
|||
update_hash if filename == @filename
|
|||
rescue
|
|||
raise CyberError.new(:unrecoverable, "services/dns", "zone '#{@name}' cannot be read from '#{filename}' (I/O error, nonexistent or lack of permission)")
|
|||
end
|
|||
end
|
|||
def write_zone(filename)
|
|||
begin
|
|||
File.open(filename, "w") do |fp|
|
|||
fp.print @content
|
|||
end
|
|||
rescue
|
|||
raise CyberError.new(:unrecoverable, "services/dns", "zone '#{@name}' cannot be written to '#{filename}' (I/O error or lack of permission)")
|
|||
end
|
|||
end
|
|||
end
|
|||
end # MapMakerLand
|
|||
end
|