Add zonefile generation procedure

This commit is contained in:
Martin Lensment 2014-11-21 15:30:40 +02:00
parent f5ce88b548
commit ac4b63f390
23 changed files with 370 additions and 255 deletions

View file

@ -80,6 +80,9 @@ gem 'delayed_job_active_record', '~> 4.0.2'
gem 'daemons' gem 'daemons'
group :development, :test do group :development, :test do
# for inserting dummy data
gem 'activerecord-import', '~> 0.6.0'
gem 'capybara', '~> 2.4.1' gem 'capybara', '~> 2.4.1'
# For feature testing # For feature testing
# gem 'capybara-webkit', '1.2.0' # Webkit driver didn't work with turbolinks # gem 'capybara-webkit', '1.2.0' # Webkit driver didn't work with turbolinks

View file

@ -21,6 +21,8 @@ GEM
activemodel (= 4.1.4) activemodel (= 4.1.4)
activesupport (= 4.1.4) activesupport (= 4.1.4)
arel (~> 5.0.0) arel (~> 5.0.0)
activerecord-import (0.6.0)
activerecord (>= 3.0)
activesupport (4.1.4) activesupport (4.1.4)
i18n (~> 0.6, >= 0.6.9) i18n (~> 0.6, >= 0.6.9)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
@ -361,6 +363,7 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
activerecord-import (~> 0.6.0)
bootstrap-sass (~> 3.2.0.1) bootstrap-sass (~> 3.2.0.1)
brakeman (~> 2.6.2) brakeman (~> 2.6.2)
bullet (~> 4.14.0) bullet (~> 4.14.0)

View file

@ -27,11 +27,6 @@ class Admin::DomainsController < AdminController
end end
end end
def zonefile
@zonefile = @domain.generate_zonefile
# send_data @zonefile, filename: 'bla.txt'
end
private private
def set_domain def set_domain

View file

@ -0,0 +1,30 @@
class Admin::ZonefileSettingsController < ApplicationController
before_action :set_zonefile_setting, only: [:update, :edit]
def index
@zonefile_settings = ZonefileSetting.all
end
def edit
@zonefile_setting = ZonefileSetting.find(params[:id])
end
def update
if @zonefile_setting.update(zonefile_setting_params)
flash[:notice] = I18n.t('shared.record_updated')
redirect_to admin_zonefile_settings_path
else
flash.now[:alert] = I18n.t('shared.failed_to_update_record')
render 'edit'
end
end
private
def set_zonefile_setting
@zonefile_setting = ZonefileSetting.find(params[:id])
end
def zonefile_setting_params
params.require(:zonefile_setting).permit(:ttl, :refresh, :retry, :expire, :minimum_ttl, :email)
end
end

View file

@ -2,44 +2,7 @@ class Admin::ZonefilesController < ApplicationController
# TODO: Refactor this # TODO: Refactor this
# rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/MethodLength
def index def index
zf = Zonefile.new @zonefile = ActiveRecord::Base.connection.execute("select generate_zonefile('ee')")[0]['generate_zonefile']
send_data @zonefile, filename: 'zonefile-1000.txt'
zf.origin = 'ee.'
zf.ttl = '43200'
zf.soa[:primary_ns] = 'ns.tld.ee.'
zf.soa[:email] = 'hostmaster.eestiinternet.ee.'
zf.soa[:origin] = 'ee.'
zf.soa[:refresh] = '3600'
zf.soa[:retry] = '900'
zf.soa[:expire] = '1209600'
zf.soa[:minimumTTL] = '3600'
zf.new_serial
zf.ns << { name: 'ee.', class: 'IN', host: 'b.tld.ee.' }
zf.ns << { name: 'ee.', class: 'IN', host: 'e.tld.ee.' }
zf.ns << { name: 'ee.', class: 'IN', host: 'ee.aso.ee.' }
zf.ns << { name: 'ee.', class: 'IN', host: 'ns.ut.ee.' }
zf.ns << { name: 'ee.', class: 'IN', host: 'ns.tld.ee.' }
zf.ns << { name: 'ee.', class: 'IN', host: 'sunic.sunet.se.' }
zf.a << { name: 'b.tld.ee.', class: 'IN', host: '194.146.106.110' }
zf.a4 << { name: 'b.tld.ee.', class: 'IN', host: '2001:67c:1010:28::53' }
zf.a << { name: 'e.tld.ee.', class: 'IN', host: '204.61.216.36' }
zf.a4 << { name: 'e.tld.ee.', class: 'IN', host: '2001:678:94:53::53' }
zf.a << { name: 'ee.aso.ee.', class: 'IN', host: '213.184.51.122' }
zf.a4 << { name: 'ee.aso.ee.', class: 'IN', host: '2a02:88:0:21::2' }
zf.a << { name: 'ns.ut.ee.', class: 'IN', host: '193.40.5.99' }
zf.a << { name: 'ns.tld.ee.', class: 'IN', host: '195.43.87.10' }
zf.a << { name: 'sunic.sunet.se.', class: 'IN', host: '192.36.125.2' }
zf.a4 << { name: 'sunic.sunet.se.', class: 'IN', host: '2001:6b0:7::2' }
Nameserver.all.includes(:domain).each do |x|
zf.ns << { name: "#{x.domain_name}.", class: 'IN', host: "#{x.hostname}." }
zf.a << { name: "#{x.hostname}.", class: 'IN', host: x.ipv4 } if x.ipv4.present?
zf.a4 << { name: "#{x.hostname}.", class: 'IN', host: x.ipv6 } if x.ipv6.present?
end
@zonefile = zf.generate
end end
end end

View file

@ -2,4 +2,10 @@ class Country < ActiveRecord::Base
def to_s def to_s
name name
end end
class << self
def estonia
find_by(iso: 'EE')
end
end
end end

View file

@ -301,35 +301,6 @@ class Domain < ActiveRecord::Base
end end
end end
def generate_zonefile
zf = Zonefile.new
zf.ttl = '3600'
zf.origin = "#{name}."
ns = nameservers.first
zf.soa[:primary_ns] = "#{ns.hostname}."
zf.soa[:email] = 'hostmaster.internet.ee'
zf.soa[:origin] = "#{name}."
zf.soa[:refresh] = '10800'
zf.soa[:retry] = '3600'
zf.soa[:expire] = '604800'
zf.soa[:minimumTTL] = '3600'
nameservers.each do |x|
zf.ns << { name: "#{name}.", class: 'IN', host: "#{x.hostname}." }
end
dnskeys.each do |x|
zf.ds << { name: "#{name}.", ttl: '86400', class: 'IN', key_tag: x.ds_key_tag, algorithm: x.ds_alg,
digest_type: x.ds_digest_type, digest: x.ds_digest }
zf.dnskey << { name: "#{name}.", ttl: '86400', class: 'IN', flag: x.flags,
protocol: x.protocol, algorithm: x.alg, public_key: x.public_key }
end
zf.new_serial
zf.generate
end
class << self class << self
def convert_period_to_time(period, unit) def convert_period_to_time(period, unit)
return period.to_i.days if unit == 'd' return period.to_i.days if unit == 'd'

3
app/models/zonefile.rb Normal file
View file

@ -0,0 +1,3 @@
class Zonefile < ActiveRecord::Base
end

View file

@ -0,0 +1,7 @@
class ZonefileSetting < ActiveRecord::Base
validates :origin, :ttl, :refresh, :retry, :expire, :minimum_ttl, :email, presence: true
validates :ttl, :refresh, :retry, :expire, :minimum_ttl, numericality: { only_integer: true }
def to_s
origin
end
end

View file

@ -16,6 +16,7 @@ class DomainNameValidator < ActiveModel::EachValidator
class << self class << self
def validate_format(value) def validate_format(value)
return true if value == 'ee'
return true unless value return true unless value
value = value.mb_chars.downcase.strip value = value.mb_chars.downcase.strip

View file

@ -14,7 +14,7 @@
%tbody %tbody
- @settings.each do |x| - @settings.each do |x|
%tr %tr
%td= t("shared.#{x.var}") %td= t("#{x.var}")
- if [TrueClass, FalseClass].include?(x.value.class) - if [TrueClass, FalseClass].include?(x.value.class)
%td %td
= hidden_field_tag("[settings][#{x.var}]", '') = hidden_field_tag("[settings][#{x.var}]", '')

View file

@ -0,0 +1,50 @@
%h2= t('zonefile_settings')
%hr
= form_for [:admin, @zonefile_setting], html: { class: 'form-horizontal' } do |f|
.row
.col-md-12
#domain-statuses
.errors
- if f.object.errors.any?
- f.object.errors.full_messages.each do |x|
= x
%br
- if f.object.errors.any?
%hr
.form-group
= f.label :origin, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :origin, class: 'form-control', disabled: true
.form-group
= f.label :ttl, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :ttl, class: 'form-control'
.form-group
= f.label :refresh, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :refresh, class: 'form-control'
.form-group
= f.label :retry, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :retry, class: 'form-control'
.form-group
= f.label :expire, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :expire, class: 'form-control'
.form-group
= f.label :minimum_ttl, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :minimum_ttl, class: 'form-control'
.form-group
= f.label :email, class: 'col-md-2 control-label'
.col-md-10
= f.text_field :email, class: 'form-control'
.row
.col-md-12.text-right
%button.btn.btn-primary=t('shared.save')

View file

@ -0,0 +1,20 @@
.row
.col-sm-12
%h2.text-center-xs= t('zonefile_settings')
%hr
.row
.col-md-12
.table-responsive
%table.table.table-hover.table-bordered.table-condensed
%thead
%tr
%th{class: 'col-xs-10'}
= t('origin')
%th{class: 'col-xs-2'}
= t('action')
%tbody
- @zonefile_settings.each do |x|
%tr
%td= link_to(x, edit_admin_zonefile_setting_path(x))
%td
= link_to(t('generate_zonefile'), admin_zonefiles_path, class: 'btn btn-xs btn-primary')

View file

@ -38,7 +38,7 @@
%li %li
= link_to t('shared.settings'), admin_settings_path = link_to t('shared.settings'), admin_settings_path
%li %li
= link_to t('zonefile'), admin_zonefiles_path = link_to t('zonefile'), admin_zonefile_settings_path
%li.divider %li.divider
%li.dropdown-header= t('shared.users') %li.dropdown-header= t('shared.users')
%li %li

View file

@ -27,8 +27,6 @@ module Registry
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de # config.i18n.default_locale = :de
config.autoload_paths += %W(#{config.root}/lib)
config.generators do |g| config.generators do |g|
g.stylesheets false g.stylesheets false
g.javascripts false g.javascripts false

View file

@ -201,13 +201,6 @@ en:
taken: 'Public key already exists' taken: 'Public key already exists'
blank: 'Public key is missing' blank: 'Public key is missing'
delegation_signer:
attributes:
dnskeys:
out_of_range: 'DNS keys count must be between %{min}-%{max}'
attributes: attributes:
epp_domain: &epp_domain_attributes epp_domain: &epp_domain_attributes
name: 'Domain name' name: 'Domain name'
@ -223,6 +216,13 @@ en:
alg: 'Algorithm' alg: 'Algorithm'
public_key: 'Public key' public_key: 'Public key'
zonefile_setting:
ttl: 'TTL'
refresh: 'Refresh'
retry: 'Retry'
expire: 'Expire'
minimum_ttl: 'Minimum ttl'
email: 'E-Mail'
errors: errors:
messages: messages:
@ -410,14 +410,6 @@ en:
authentication_error: 'Authentication error' authentication_error: 'Authentication error'
ds_data_and_key_data_must_not_exists_together: 'dsData and keyData objects must not exists together' ds_data_and_key_data_must_not_exists_together: 'dsData and keyData objects must not exists together'
ns_min_count: 'Nameserver minimum count'
ns_max_count: 'Nameserver maximum count'
dnskeys_min_count: 'DNS keys minimum count'
dnskeys_max_count: 'DNS keys maximum count'
ds_data_allowed: 'DS data allowed'
ds_data_with_key_allowed: 'Allow DS data with key'
key_data_allowed: 'Allow key data'
ds_algorithm: 'DS algorithm'
setting: 'Setting' setting: 'Setting'
registrar: 'Registrar' registrar: 'Registrar'
@ -428,3 +420,19 @@ en:
zonefile: 'Zonefile' zonefile: 'Zonefile'
only_one_parameter_allowed: 'Only one parameter allowed: %{param_1} or %{param_2}' only_one_parameter_allowed: 'Only one parameter allowed: %{param_1} or %{param_2}'
required_parameter_missing_choice: 'Required parameter missing: %{param_1} or %{param_2}' required_parameter_missing_choice: 'Required parameter missing: %{param_1} or %{param_2}'
zonefile_ttl: 'Zonefile TTL'
zonefile_refresh: 'Zonefile refresh'
zonefile_retry: 'Zonefile retry'
zonefile_expire: 'Zonefile expire'
zonefile_minimum_ttl: 'Zonefile minimum TTL'
zonefile_email: 'Zonefile e-mail'
transfer_wait_time: 'Transfer wait time'
ns_min_count: 'Nameserver minimum count'
ns_max_count: 'Nameserver maximum count'
dnskeys_min_count: 'DNS keys minimum count'
dnskeys_max_count: 'DNS keys maximum count'
ds_data_allowed: 'DS data allowed'
ds_data_with_key_allowed: 'Allow DS data with key'
key_data_allowed: 'Allow key data'
ds_algorithm: 'DS algorithm'
zonefile_settings: 'Zonefile settings'

View file

@ -9,11 +9,9 @@ Rails.application.routes.draw do
namespace(:admin) do namespace(:admin) do
resources :zonefiles resources :zonefiles
resources :domains do resources :zonefile_settings
member do
get 'zonefile' resources :domains
end
end
resources :settings resources :settings
resources :registrars do resources :registrars do
collection do collection do

View file

@ -0,0 +1,28 @@
class CreateZonefileSetting < ActiveRecord::Migration
def change
create_table :zonefile_settings do |t|
t.string :origin
t.integer :ttl
t.integer :refresh
t.integer :retry
t.integer :expire
t.integer :minimum_ttl
t.string :email
t.timestamps
end
# rubocop: disable Style/NumericLiterals
ZonefileSetting.create({
origin: 'ee',
ttl: 43200,
refresh: 3600,
retry: 900,
expire: 1209600,
minimum_ttl: 3600,
email: 'hostmaster.eestiinternet.ee'
})
end
end

View file

@ -0,0 +1,58 @@
class AddEeDomainObjects < ActiveRecord::Migration
# rubocop:disable Metrics/MethodLength
def up
r = Registrar.create(
name: 'EIS',
reg_no: '123321',
address: 'Tallinn',
country: Country.estonia
)
c = Contact.create(
name: 'EIS',
phone: '+372.123321',
email: 'info@testing.ee',
ident: '123321',
ident_type: 'ico',
address: Address.create(
city: 'Tallinn',
country: Country.estonia
),
registrar: r
)
EppUser.create(
registrar: r,
username: 'testeis',
password: 'testeis',
active: true
)
Domain.create(
name: 'ee',
valid_to: Date.new(9999, 1, 1),
period: 1,
period_unit: 'y',
owner_contact: c,
nameservers: [
Nameserver.create(hostname: 'ns.tld.ee', ipv4: '195.43.87.10'),
Nameserver.create(hostname: 'b.tld.ee', ipv4: '194.146.106.110', ipv6: '2001:67c:1010:28::53'),
Nameserver.create(hostname: 'e.tld.ee', ipv4: '204.61.216.36', ipv6: '2001:678:94:53::53'),
Nameserver.create(hostname: 'ee.aso.ee', ipv4: '213.184.51.122', ipv6: '2a02:88:0:21::2'),
Nameserver.create(hostname: 'ns.ut.ee', ipv4: '193.40.5.99', ipv6: ''),
Nameserver.create(hostname: 'sunic.sunet.se', ipv4: '195.80.103.202')
],
admin_contacts: [c],
registrar: r
)
end
# rubocop:enable Metrics/MethodLength
def down
Domain.find_by(name: 'ee').destroy
EppUser.find_by(username: 'testeis').destroy
Contact.find_by(name: 'EIS').destroy
Registrar.find_by(name: 'EIS').destroy
end
end

View file

@ -0,0 +1,109 @@
class AddZonefileProcedure < ActiveRecord::Migration
# rubocop:disable Metrics/MethodLength
def up
execute <<-SQL
CREATE OR REPLACE FUNCTION generate_zonefile(i_origin varchar)
RETURNS text AS $$
DECLARE
zone_header text := concat('$ORIGIN ', i_origin, '.');
tmp text := '';
ns_records text := '';
a_records text := '';
a4_records text := '';
ds_records text := '';
BEGIN
-- zonefile header
SELECT concat(
format('%-10s', '$ORIGIN'), i_origin, '.', chr(10),
format('%-10s', '$TTL'), zf.ttl, chr(10), chr(10),
format('%-10s', i_origin || '.'), 'IN SOA ', 'ns.tld.ee', '. ', zf.email, '. (', chr(10),
format('%-17s', ''), format('%-12s', '2014111210'), '; serial number', chr(10),
format('%-17s', ''), format('%-12s', zf.refresh), '; refresh, seconds', chr(10),
format('%-17s', ''), format('%-12s', zf.retry), '; retry, seconds', chr(10),
format('%-17s', ''), format('%-12s', zf.expire), '; expire, seconds', chr(10),
format('%-17s', ''), format('%-12s', zf.minimum_ttl), '; minimum TTL, seconds', chr(10),
format('%-17s', ''), ')'
) FROM zonefile_settings zf WHERE i_origin = zf.origin INTO zone_header;
-- ns records
SELECT array_to_string(
array(
SELECT concat(d.name, '. IN NS ', ns.hostname, '.')
FROM domains d
JOIN nameservers ns ON ns.domain_id = d.id
WHERE d.name LIKE '%' || i_origin
ORDER BY
CASE d.name
WHEN i_origin THEN 1
END
),
chr(10)
) INTO ns_records;
-- a records
SELECT array_to_string(
array(
SELECT concat(ns.hostname, '. IN A ', ns.ipv4, '.')
FROM domains d
JOIN nameservers ns ON ns.domain_id = d.id
WHERE d.name LIKE '%' || i_origin AND ns.ipv4 IS NOT NULL AND ns.ipv4 <> ''
ORDER BY
CASE d.name
WHEN i_origin THEN 1
END
),
chr(10)
) INTO a_records;
-- aaaa records
SELECT array_to_string(
array(
SELECT concat(ns.hostname, '. IN AAAA ', ns.ipv6, '.')
FROM domains d
JOIN nameservers ns ON ns.domain_id = d.id
WHERE d.name LIKE '%' || i_origin AND ns.ipv6 IS NOT NULL AND ns.ipv6 <> ''
ORDER BY
CASE d.name
WHEN i_origin THEN 1
END
),
chr(10)
) INTO a4_records;
-- ds records
SELECT array_to_string(
array(
SELECT concat(
d.name, '. 86400 IN ', dk.ds_key_tag, ' ',
dk.ds_alg, ' ', dk.ds_digest_type, ' ', dk.ds_digest
)
FROM domains d
JOIN dnskeys dk ON dk.domain_id = d.id
WHERE d.name LIKE '%' || i_origin
ORDER BY
CASE d.name
WHEN i_origin THEN 1
END
),
chr(10)
) INTO ds_records;
RETURN concat(
zone_header, chr(10), chr(10),
'; Zone NS Records', chr(10), ns_records, chr(10), chr(10),
'; Zone A Records', chr(10), a_records, chr(10), chr(10),
'; Zone AAAA Records', chr(10), a4_records, chr(10), chr(10),
'; Zone DS Records', chr(10), ds_records
);
END;
$$
LANGUAGE plpgsql;
SQL
end
def down
execute <<-SQL
DROP FUNCTION generate_zonefile(i_origin varchar);
SQL
end
end

View file

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20141114130737) do ActiveRecord::Schema.define(version: 20141121093125) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -335,4 +335,16 @@ ActiveRecord::Schema.define(version: 20141114130737) do
add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id", using: :btree add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id", using: :btree
create_table "zonefile_settings", force: true do |t|
t.string "origin"
t.integer "ttl"
t.integer "refresh"
t.integer "retry"
t.integer "expire"
t.integer "minimum_ttl"
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
end
end end

View file

@ -78,4 +78,13 @@ Setting.ns_max_count = 11
Setting.transfer_wait_time = 0 Setting.transfer_wait_time = 0
# rubocop: disable Style/NumericLiterals
Setting.zonefile_ttl = 43200
Setting.zonefile_refresh = 3600
Setting.zonefile_retry = 900
Setting.zonefile_expire = 1209600
Setting.zonefile_minimum_ttl = 3600
Setting.zonefile_email = 'hostmaster.eestiinternet.ee'
# rubocop: enable Style/NumericLiterals
# Setting.whois_enabled = true only uncomment this if you wish whois # Setting.whois_enabled = true only uncomment this if you wish whois

View file

@ -1,157 +0,0 @@
class Zonefile
RECORDS = [:mx, :a, :a4, :ns, :cname, :txt, :ptr, :srv, :soa, :ds,
:dnskey, :rrsig, :nsec, :nsec3, :nsec3param, :tlsa, :naptr]
attr_accessor(*RECORDS, :ttl, :origin)
def initialize(obj = {})
RECORDS.each do |x|
if x == :soa
send("#{x}=", {})
else
send("#{x}=", [])
end
end
obj.each do |k, v|
send("#{k}=", v)
end
end
def new_serial
base = sprintf('%04d%02d%02d', Time.now.year, Time.now.month, Time.now.day)
if soa[:serial]
if base == soa[:serial].first(8)
sequence = soa[:serial].last(2).to_i + 1
soa[:serial] = "#{base}#{sprintf('%02d', sequence)}"
return soa[:serial]
end
end
soa[:serial] = soa[:serial] = "#{base}00"
end
# rubocop:disable Metrics/MethodLength
# rubocop: disable Metrics/PerceivedComplexity
# rubocop: disable Metrics/CyclomaticComplexity
def generate
out = <<-eos
$ORIGIN #{origin} ; designates the start of this zone file in the namespace
$TTL #{ttl} ; default expiration time of all resource records without their own TTL value
#{soa[:origin]} #{soa[:ttl]} IN SOA #{soa[:primary_ns]} #{soa[:email]} (
#{sprintf('%-13s', soa[:serial])}; serial number
#{sprintf('%-13s', soa[:refresh])}; refresh, seconds
#{sprintf('%-13s', soa[:retry])}; retry, seconds
#{sprintf('%-13s', soa[:expire])}; expire, seconds
#{sprintf('%-13s', soa[:minimumTTL])}; minimum TTL, seconds
)
eos
ns.each do |ns|
out << "#{ns[:name]} #{ns[:ttl]} #{ns[:class]} NS #{ns[:host]}\n"
end
out << "\n; Zone MX Records\n" unless mx.empty?
mx.each do |mx|
out << "#{mx[:name]} #{mx[:ttl]} #{mx[:class]} MX #{mx[:pri]} #{mx[:host]}\n"
end
out << "\n; Zone A Records\n" unless a.empty?
a.each do |a|
out << "#{a[:name]} #{a[:ttl]} #{a[:class]} A #{a[:host]}\n"
end
out << "\n; Zone CNAME Records\n" unless cname.empty?
cname.each do |cn|
out << "#{cn[:name]} #{cn[:ttl]} #{cn[:class]} CNAME #{cn[:host]}\n"
end
out << "\n; Zone AAAA Records\n" unless a4.empty?
a4.each do |a4|
out << "#{a4[:name]} #{a4[:ttl]} #{a4[:class]} AAAA #{a4[:host]}\n"
end
out << "\n; Zone TXT Records\n" unless txt.empty?
txt.each do |tx|
out << "#{tx[:name]} #{tx[:ttl]} #{tx[:class]} TXT #{tx[:text]}\n"
end
out << "\n; Zone SRV Records\n" unless srv.empty?
srv.each do |srv|
out << "#{srv[:name]} #{srv[:ttl]} #{srv[:class]} SRV #{srv[:pri]} "\
"#{srv[:weight]} #{srv[:port]} #{srv[:host]}\n"
end
out << "\n; Zone PTR Records\n" unless ptr.empty?
ptr.each do |ptr|
out << "#{ptr[:name]} #{ptr[:ttl]} #{ptr[:class]} PTR #{ptr[:host]}\n"
end
out << "\n; Zone DS Records\n" unless ds.empty?
ds.each do |ds|
out << "#{ds[:name]} #{ds[:ttl]} #{ds[:class]} DS #{ds[:key_tag]} #{ds[:algorithm]} "\
"#{ds[:digest_type]} #{ds[:digest]}\n"
end
out << "\n; Zone NSEC Records\n" unless self.ds.empty?
nsec.each do |nsec|
out << "#{nsec[:name]} #{nsec[:ttl]} #{nsec[:class]} NSEC #{nsec[:next]} #{nsec[:types]}\n"
end
out << "\n; Zone NSEC3 Records\n" unless self.ds.empty?
nsec3.each do |nsec3|
out << "#{nsec3[:name]} #{nsec3[:ttl]} #{nsec3[:class]} NSEC3 #{nsec3[:algorithm]} "\
"#{nsec3[:flags]} #{nsec3[:iterations]} #{nsec3[:salt]} #{nsec3[:next]} #{nsec3[:types]}\n"
end
out << "\n; Zone NSEC3PARAM Records\n" unless self.ds.empty?
nsec3param.each do |nsec3param|
out << "#{nsec3param[:name]} #{nsec3param[:ttl]} #{nsec3param[:class]} NSEC3PARAM "\
"#{nsec3param[:algorithm]} #{nsec3param[:flags]} #{nsec3param[:iterations]} #{nsec3param[:salt]}\n"
end
out << "\n; Zone DNSKEY Records\n" unless self.ds.empty?
dnskey.each do |dnskey|
out << "#{dnskey[:name]} #{dnskey[:ttl]} #{dnskey[:class]} DNSKEY #{dnskey[:flag]} "\
"#{dnskey[:protocol]} #{dnskey[:algorithm]} #{dnskey[:public_key]}\n"
end
out << "\n; Zone RRSIG Records\n" unless self.ds.empty?
rrsig.each do |rrsig|
out << "#{rrsig[:name]} #{rrsig[:ttl]} #{rrsig[:class]} RRSIG #{rrsig[:type_covered]} "\
"#{rrsig[:algorithm]} #{rrsig[:labels]} #{rrsig[:original_ttl]} #{rrsig[:expiration]} "\
"#{rrsig[:inception]} #{rrsig[:key_tag]} #{rrsig[:signer]} #{rrsig[:signature]}\n"
end
out << "\n; Zone TLSA Records\n" unless tlsa.empty?
tlsa.each do |tlsa|
out << "#{tlsa[:name]} #{tlsa[:ttl]} #{tlsa[:class]} TLSA #{tlsa[:certificate_usage]} "\
"#{tlsa[:selector]} #{tlsa[:matching_type]} #{tlsa[:data]}\n"
end
out << "\n; Zone NAPTR Records\n" unless self.ds.empty?
naptr.each do |naptr|
out << "#{naptr[:name]} #{naptr[:ttl]} #{naptr[:class]} NAPTR #{naptr[:order]} "\
"#{naptr[:preference]} #{naptr[:flags]} #{naptr[:service]} #{naptr[:regexp]} #{naptr[:replacement]}\n"
end
out
end
end