Project

General

Profile

Download (7.58 KB) Statistics
| Branch: | Tag: | Revision:
#--
# CyborgHood, a distributed system management software.
# Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
#
# 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
read_zone(@filename_signed) if has_signed_zone_file?
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)
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
end

def parsed_signed_content
return unless has_signed_zone_file?

parsed_content = ZoneContent.new(@name)
parsed_content.import_from_file(@filename_signed)
parsed_content
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
hszf = self.has_signed_zone_file?
data = {
:is_master => self.master?,
:has_signed_zone_file => hszf,
:serial_in_zone_file => self.parsed_content.serial
}

if hszf
data.merge!({
:serial_in_signed_zone_file => self.parsed_signed_content.serial
})
end

data
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
(1-1/2)