summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2022-02-14 02:56:13 +0100
committerHugo Hörnquist <hugo@lysator.liu.se>2022-02-14 16:50:40 +0100
commiteae45daa97335ea0aebbff32b39622af589a27af (patch)
tree90837a83ed2bcfb9b2edf3c19691dc8c418faff8
downloaddns_record-eae45daa97335ea0aebbff32b39622af589a27af.tar.gz
dns_record-eae45daa97335ea0aebbff32b39622af589a27af.tar.xz
Vendor zonefile module.
The version in the gem repositories is version 1.06, and seems abandoned. This is version 1.07 copied from GitHub [1]. Vendoring is not due to version, but due to properly installing Gem's being next to impossible, and that I need to change some of the code later on. [1]: https://github.com/boesemar/zonefile
-rw-r--r--lib/zonefile.rb23
-rw-r--r--lib/zonefile/zonefile.rb520
2 files changed, 543 insertions, 0 deletions
diff --git a/lib/zonefile.rb b/lib/zonefile.rb
new file mode 100644
index 0000000..713df0d
--- /dev/null
+++ b/lib/zonefile.rb
@@ -0,0 +1,23 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2015 Martin Boese
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+require 'zonefile/zonefile'
diff --git a/lib/zonefile/zonefile.rb b/lib/zonefile/zonefile.rb
new file mode 100644
index 0000000..bd94b0f
--- /dev/null
+++ b/lib/zonefile/zonefile.rb
@@ -0,0 +1,520 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2015 Martin Boese
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+# = Ruby Zonefile - Parse and manipulate DNS Zone Files.
+#
+# == Description
+# This class can read, manipulate and create DNS zone files. The data can be accessed by the instance method of the same
+# name. All except SOA return an array of hashes containing the named data. SOA directly returns the
+# hash since there can only be one SOA information.
+#
+# The following hash keys are returned per record type:
+#
+# * SOA
+# - :ttl, :primary, :email, :serial, :refresh, :retry, :expire, :minimumTTL
+# * A
+# - :name, :ttl, :class, :host
+# * MX
+# - :name, :ttl, :class, :pri, :host
+# * NS
+# - :name, :ttl, :class, :host
+# * CNAME
+# - :name, :ttl, :class, :host
+# * TXT
+# - :name, :ttl, :class, :text
+# * A4 (AAAA)
+# - :name, :ttl, :class, :host
+# * PTR
+# - :name, :ttl, :class, :host
+# * SRV
+# - :name, :ttl, :class, :pri, :weight, :port, :host
+# * DS
+# - :name, :ttl, :class, :key_tag, :algorithm, :digest_type, :digest
+# * DNSKEY
+# - :name, :ttl, :class, :flag, :protocol, :algorithm, :public_key
+# * RRSIG
+# - :name, :ttl, :class, :type_covered, :algorithm, :labels, :original_ttl,
+# :expiration, :inception, :key_tag, :signer, :signature
+# * NSEC
+# - :name, :ttl, :class, :next, :types
+# * NSEC3
+# - :name, :ttl, :class, :algorithm, :flags, :iterations, :salt, :next, :types
+# * NSEC3PARAM
+# - :name, :ttl, :class, :algorithm, :flags, :iterations, :salt
+# * TLSA
+# - :name, :ttl, :class, :certificate_usage, :selector, :matching_type, :data
+# * NAPTR
+# - :name, :ttl, :class, :order, :preference, :flags, :service, :regexp, :replacement
+# * SPF
+# - :name, :ttl, :class, :text
+# * CAA
+# - :name, :ttl, :class, :flag, :tag, :value
+#
+# == Examples
+#
+# === Read a Zonefile
+#
+# zf = Zonefile.from_file('/path/to/zonefile.db')
+#
+# # Display MX-Records
+# zf.mx.each do |mx_record|
+# puts "Mail Exchagne with priority: #{mx_record[:pri]} --> #{mx_record[:host]}"
+# end
+#
+# # Show SOA TTL
+# puts "Record Time To Live: #{zf.soa[:ttl]}"
+#
+# # Show A-Records
+# zf.a.each do |a_record|
+# puts "#{a_record[:name]} --> #{a_record[:host]}"
+# end
+#
+#
+# ==== Manipulate a Zonefile
+#
+# zf = Zonefile.from_file('/path/to/zonefile.db')
+#
+# # Change TTL and add an A-Record
+#
+# zf.soa[:ttl] = '123123' # Change the SOA ttl
+# zf.a << { :class => 'IN', :name => 'www', :host => '192.168.100.1', :ttl => 3600 } # add A-Record
+#
+# # Setting PTR records (deleting existing ones)
+#
+# zf.ptr = [ { :class => 'IN', :name=>'1.100.168.192.in-addr.arpa', :host => 'my.host.com' },
+# { :class => 'IN', :name=>'2.100.168.192.in-addr.arpa', :host => 'me.host.com' } ]
+#
+# # Increase Serial Number
+# zf.new_serial
+#
+# # Print new zonefile
+# puts "New Zonefile: \n#{zf.output}"
+#
+# == Name attribute magic
+#
+# Since 1.04 the :name attribute is preserved and returned as defined in a previous record if a zonefile entry
+# omits it. This should be the expected behavior for most users.
+# You can switch this off globally by calling Zonefile.preserve_name(false)
+#
+# == Authors
+#
+# Martin Boese, based on Simon Flack Perl library DNS::ZoneParse
+#
+# Andy Newton, patch to support various additional records
+#
+
+class Zonefile
+
+ RECORDS = %w{ mx a a4 ns cname txt ptr srv soa ds dnskey rrsig nsec nsec3 nsec3param tlsa naptr spf caa }
+ attr :records
+ attr :soa
+ attr :data
+# global $ORIGIN option
+ attr :origin
+ # global $TTL option
+ attr :ttl
+
+ @@preserve_name = true
+
+ # For compatibility: This can switches off copying of the :name from the
+ # previous record in a zonefile if found omitted.
+ # This was zonefile's behavior in <= 1.03 .
+ def self.preserve_name(do_preserve_name)
+ @@preserve_name = do_preserve_name
+ end
+
+ def method_missing(m, *args)
+ mname = m.to_s.sub("=","")
+ return super unless RECORDS.include?(mname)
+
+ if m.to_s[-1].chr == '=' then
+ @records[mname.intern] = args.first
+ @records[mname.intern]
+ else
+ @records[m]
+ end
+ end
+
+
+ # Compact a zonefile content - removes empty lines, comments,
+ # converts tabs into spaces etc...
+ def self.simplify(zf)
+ # concatenate everything split over multiple lines in parentheses - remove ;-comments in block
+ zf = zf.gsub(/(\([^\)]*?\))/) { |m| m.split(/\n/).map { |l| l.gsub(/\;.*$/, '') }.join("\n").gsub(/[\r\n]/, '').gsub( /[\(\)]/, '') }
+
+ zf.split(/\n/).map do |line|
+ r = line.gsub(/\t/, ' ')
+ r = r.gsub(/\s+/, ' ')
+ # FIXME: this is ugly and not accurate, couldn't find proper regex:
+ # Don't strip ';' if it's quoted. Happens a lot in TXT records.
+ (0..(r.length - 1)).find_all { |i| r[i].chr == ';' }.each do |comment_idx|
+ if !r[(comment_idx+1)..-1].index(/['"]/) then
+ r = r[0..(comment_idx-1)]
+ break
+ end
+ end
+ r
+ end.delete_if { |line| line.empty? || line[0].chr == ';'}.join("\n")
+ end
+
+
+ # create a new zonefile object by passing the content of the zonefile
+ def initialize(zonefile = '', file_name= nil, origin= nil)
+ @data = zonefile
+ @filename = file_name
+ @origin = origin || (file_name ? file_name.split('/').last : '')
+
+ @records = {}
+ @soa = {}
+ RECORDS.each { |r| @records[r.intern] = [] }
+ parse
+ end
+
+ # True if no records (except sao) is defined in this file
+ def empty?
+ RECORDS.each do |r|
+ return false unless @records[r.intern].empty?
+ end
+ true
+ end
+
+ # Create a new object by reading the content of a file
+ def self.from_file(file_name, origin = nil)
+ Zonefile.new(File.read(file_name), file_name.split('/').last, origin)
+ end
+
+ def add_record(type, data= {})
+ if @@preserve_name then
+ @lastname = data[:name] if data[:name].to_s != ''
+ data[:name] = @lastname if data[:name].to_s == ''
+ end
+ @records[type.downcase.intern] << data
+ end
+
+ # Generates a new serial number in the format of YYYYMMDDII if possible
+ def new_serial
+ base = "%04d%02d%02d" % [Time.now.year, Time.now.month, Time.now.day ]
+
+ if ((@soa[:serial].to_i / 100) > base.to_i) then
+ ns = @soa[:serial].to_i + 1
+ @soa[:serial] = ns.to_s
+ return ns.to_s
+ end
+
+ ii = 0
+ while (("#{base}%02d" % ii).to_i <= @soa[:serial].to_i) do
+ ii += 1
+ end
+ @soa[:serial] = "#{base}%02d" % ii
+ end
+
+ def parse_line(line)
+ valid_name = /[\@a-z_\-\.0-9\*]+/i
+ valid_ip6 = /[\@a-z_\-\.0-9\*:]+/i
+ rr_class = /\b(?:IN|HS|CH)\b/i
+ rr_type = /\b(?:NS|A|CNAME)\b/i
+ rr_ttl = /(?:\d+[wdhms]?)+/i
+ ttl_cls = Regexp.new("(?:(#{rr_ttl})\s)?(?:(#{rr_class})\s)?")
+ base64 = /([\s\w\+\/]*=*)/i
+ hexadeimal = /([\sA-F0-9]*)/i
+ quoted = /(\"[^\"]*\")/i
+
+ data = {}
+ if line =~ /^\$ORIGIN\s*(#{valid_name})/ix then
+ @origin = $1
+ elsif line =~ /^(#{valid_name})? \s*
+ #{ttl_cls}
+ (#{rr_type}) \s
+ (#{valid_name})
+ /ix then
+ (name, ttl, dclass, type, host) = [$1, $2, $3, $4, $5]
+ add_record($4, :name => $1, :ttl => $2, :class => $3, :host => $5)
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ AAAA \s
+ (#{valid_ip6})
+ /x then
+ add_record('a4', :name => $1, :ttl => $2, :class => $3, :host => $4)
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ MX \s
+ (\d+) \s
+ (#{valid_name})
+ /ix then
+ add_record('mx', :name => $1, :ttl => $2, :class => $3, :pri => $4.to_i, :host => $5)
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ SRV \s
+ (\d+) \s
+ (\d+) \s
+ (\d+) \s
+ (#{valid_name})
+ /ix
+ add_record('srv', :name => $1, :ttl => $2, :class => $3, :pri => $4, :weight => $5,
+ :port => $6, :host => $7)
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ DS \s
+ (\d+) \s
+ (\w+) \s
+ (\d+) \s
+ #{hexadeimal}
+ /ix
+ add_record( 'ds', :name => $1, :ttl => $2, :class => $3, :key_tag => $4.to_i, :algorithm => $5,
+ :digest_type => $6.to_i, :digest => $7.gsub( /\s/,'') )
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ NSEC \s
+ (#{valid_name}) \s
+ ([\s\w]*)
+ /ix
+ add_record( 'nsec', :name => $1, :ttl => $2, :class => $3, :next => $4, :types => $5.strip )
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ NSEC3 \s
+ (\d+) \s
+ (\d+) \s
+ (\d+) \s
+ (-|[A-F0-9]*) \s
+ ([A-Z2-7=]*) \s
+ ([\s\w]*)
+ /ix
+ add_record( 'nsec3', :name => $1, :ttl => $2, :class => $3, :algorithm => $4, :flags => $5,
+ :iterations => $6, :salt => $7, :next => $8.strip, :types => $9.strip )
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ NSEC3PARAM \s
+ (\d+) \s
+ (\d+) \s
+ (\d+) \s
+ (-|[A-F0-9]*)
+ /ix
+ add_record( 'nsec3param', :name => $1, :ttl => $2, :class => $3, :algorithm => $4, :flags => $5,
+ :iterations => $6, :salt => $7 )
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ DNSKEY \s
+ (\d+) \s
+ (\d+) \s
+ (\w+) \s
+ #{base64}
+ /ix
+ add_record( 'dnskey', :name => $1, :ttl => $2, :class => $3, :flag => $4.to_i, :protocol => $5.to_i,
+ :algorithm => $6, :public_key => $7.gsub( /\s/,'') )
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ RRSIG \s
+ (\w+) \s
+ (\w+) \s
+ (\d+) \s
+ (\d+) \s
+ (\d+) \s
+ (\d+) \s
+ (\d+) \s
+ (#{valid_name}) \s
+ #{base64}
+ /ix
+ add_record( 'rrsig', :name => $1, :ttl => $2, :class => $3, :type_covered => $4, :algorithm => $5,
+ :labels => $6.to_i, :original_ttl => $7.to_i, :expiration => $8.to_i, :inception => $9.to_i,
+ :key_tag => $10.to_i, :signer => $11, :signature => $12.gsub( /\s/,'') )
+ elsif line=~/^(#{valid_name}) \s*
+ #{ttl_cls}
+ TLSA \s
+ (\d+) \s
+ (\d+) \s
+ (\d+) \s
+ #{base64}
+ /ix
+ add_record( 'tlsa', :name => $1, :ttl => $2, :class => $3, :certificate_usage => $4.to_i,
+ :selector => $5.to_i, :matching_type => $6.to_i, :data => $7 )
+ elsif line=~/^(#{valid_name})? \s*
+ #{ttl_cls}
+ NAPTR \s
+ (\d+) \s
+ (\d+) \s
+ #{quoted} \s
+ #{quoted} \s
+ #{quoted} \s
+ (#{valid_name})
+ /ix
+ add_record( 'naptr', :name => $1, :ttl => $2, :class => $3, :order => $4.to_i, :preference => $5.to_i,
+ :flags => $6, :service => $7, :regexp => $8, :replacement => $9 )
+ elsif line=~/^(#{valid_name}) \s+
+ #{ttl_cls}
+ SOA \s+
+ (#{valid_name}) \s+
+ (#{valid_name}) \s*
+ \s*
+ (#{rr_ttl}) \s+
+ (#{rr_ttl}) \s+
+ (#{rr_ttl}) \s+
+ (#{rr_ttl}) \s+
+ (#{rr_ttl}) \s*
+ /ix
+ ttl = @soa[:ttl] || $2 || ''
+ @soa[:origin] = $1
+ @soa[:ttl] = ttl
+ @soa[:primary] = $4
+ @soa[:email] = $5
+ @soa[:serial] = $6
+ @soa[:refresh] = $7
+ @soa[:retry] = $8
+ @soa[:expire] = $9
+ @soa[:minimumTTL] = $10
+
+ elsif line=~ /^(#{valid_name})? \s*
+ #{ttl_cls}
+ PTR \s+
+ (#{valid_name})
+ /ix
+ add_record('ptr', :name => $1, :class => $3, :ttl => $2, :host => $4)
+ elsif line =~ /^(#{valid_name})? \s* #{ttl_cls} CAA\s+ (\d+) \s+ (#{valid_name}) \s+ (.*)$/ix
+ add_record('caa', :name => $1, :ttl => $2, :class => $3, :flag=> $4.to_i, :tag => $5, :value => $6)
+ elsif line =~ /^(#{valid_name})? \s* #{ttl_cls} TXT \s+ (.*)$/ix
+ add_record('txt', :name => $1, :ttl => $2, :class => $3, :text => $4.strip)
+ elsif line =~ /^(#{valid_name})? \s* #{ttl_cls} SPF \s+ (.*)$/ix
+ add_record('spf', :name => $1, :ttl => $2, :class => $3, :text => $4.strip)
+ elsif line =~ /\$TTL\s+(#{rr_ttl})/i
+ @ttl = $1
+ end
+ end
+
+ def parse
+ Zonefile.simplify(@data).each_line do |line|
+ parse_line(line)
+ end
+ end
+
+
+ # Build a new nicely formatted Zonefile
+ #
+ def output
+ out =<<-ENDH
+;
+; Database file #{@filename || 'unknown'} for #{@origin || 'unknown'} zone.
+; Zone version: #{self.soa[:serial]}
+;
+#{self.soa[:origin]} #{self.soa[:ttl]} IN SOA #{self.soa[:primary]} #{self.soa[:email]} (
+ #{self.soa[:serial]} ; serial number
+ #{self.soa[:refresh]} ; refresh
+ #{self.soa[:retry]} ; retry
+ #{self.soa[:expire]} ; expire
+ #{self.soa[:minimumTTL]} ; minimum TTL
+ )
+
+#{@origin ? "$ORIGIN #{@origin}" : ''}
+#{@ttl ? "$TTL #{@ttl}" : ''}
+ENDH
+ out << "\n; Zone NS Records\n" unless self.ns.empty?
+ self.ns.each do |ns|
+ out << "#{ns[:name]} #{ns[:ttl]} #{ns[:class]} NS #{ns[:host]}\n"
+ end
+ out << "\n; Zone MX Records\n" unless self.mx.empty?
+ self.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 self.a.empty?
+ self.a.each do |a|
+ out << "#{a[:name]} #{a[:ttl]} #{a[:class]} A #{a[:host]}\n"
+ end
+
+ out << "\n; Zone CNAME Records\n" unless self.cname.empty?
+ self.cname.each do |cn|
+ out << "#{cn[:name]} #{cn[:ttl]} #{cn[:class]} CNAME #{cn[:host]}\n"
+ end
+
+ out << "\n; Zone AAAA Records\n" unless self.a4.empty?
+ self.a4.each do |a4|
+ out << "#{a4[:name]} #{a4[:ttl]} #{a4[:class]} AAAA #{a4[:host]}\n"
+ end
+
+ out << "\n; Zone TXT Records\n" unless self.txt.empty?
+ self.txt.each do |tx|
+ out << "#{tx[:name]} #{tx[:ttl]} #{tx[:class]} TXT #{tx[:text]}\n"
+ end
+
+ out << "\n; Zone SPF Records\n" unless self.spf.empty?
+ self.spf.each do |spf|
+ out << "#{spf[:name]} #{spf[:ttl]} #{spf[:class]} SPF #{spf[:text]}\n"
+ end
+
+ out << "\n; Zone SRV Records\n" unless self.srv.empty?
+ self.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 self.ptr.empty?
+ self.ptr.each do |ptr|
+ out << "#{ptr[:name]} #{ptr[:ttl]} #{ptr[:class]} PTR #{ptr[:host]}\n"
+ end
+
+ out << "\n; Zone DS Records\n" unless self.ds.empty?
+ self.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?
+ self.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?
+ self.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?
+ self.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?
+ self.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?
+ self.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 self.tlsa.empty?
+ self.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?
+ self.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 << "\n; Zone CAA Records\n" unless self.caa.empty?
+ self.caa.each do |caa|
+ out << "#{caa[:name]} #{caa[:ttl]} #{caa[:class]} CAA #{caa[:flag]} #{caa[:tag]} #{caa[:value]}\n"
+ end
+
+ out
+ end
+
+end
+