zonefile 1.00
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/zonefile/zonefile.rb +263 -0
- data/lib/zonefile.rb +1 -0
- data/tests/test-zone.db +32 -0
- data/tests/zonefile.rb +141 -0
- metadata +57 -0
@@ -0,0 +1,263 @@
|
|
1
|
+
#
|
2
|
+
# = Ruby Zonefile - Parse and manipulate DNS Zone Files.
|
3
|
+
#
|
4
|
+
# == Description
|
5
|
+
# This class can read, manipulate and create DNS zone files. It supports A, AAAA, MX, NS, SOA,
|
6
|
+
# TXT, CNAME and SRV records. The data can be accessed by the instance method of the same
|
7
|
+
# name. All except SOA return an array of hashes containing the named data. SOA directly returns the
|
8
|
+
# hash since there can only be one SOA information.
|
9
|
+
#
|
10
|
+
# The following hash keys are returned per record type:
|
11
|
+
#
|
12
|
+
# * SOA
|
13
|
+
# - :ttl, :primary, :email, :serial, :refresh, :retry, :expire, :minimumTTL
|
14
|
+
# * A
|
15
|
+
# - :name, :ttl, :class, :host
|
16
|
+
# * MX
|
17
|
+
# - :name, :ttl, :class, :pri, :host
|
18
|
+
# * NS
|
19
|
+
# - :name, :ttl, :class, :host
|
20
|
+
# * CNAME
|
21
|
+
# - :name, :ttl, :class, :host
|
22
|
+
# * TXT
|
23
|
+
# - :name, :ttl, :class, :text
|
24
|
+
# * A4 (AAAA)
|
25
|
+
# - :name, :ttl, :class, :host
|
26
|
+
# * SRV
|
27
|
+
# - :name, :ttl, :class, :pri, :weight, :port, :host
|
28
|
+
#
|
29
|
+
# == Examples
|
30
|
+
#
|
31
|
+
# === Read a Zonefile
|
32
|
+
#
|
33
|
+
# zf = Zonefile.from_file('/path/to/zonefile.db')
|
34
|
+
#
|
35
|
+
# # Display MX-Records
|
36
|
+
# zf.mx.each do |mx_record|
|
37
|
+
# puts "Mail Exchagne with priority: #{mx_record[:pri]} --> #{mx_record[:host]}"
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Show SOA TTL
|
41
|
+
# puts "Record Time To Live: #{zf.soa[:ttl]}"
|
42
|
+
#
|
43
|
+
# # Show A-Records
|
44
|
+
# zf.a.each do |a_record|
|
45
|
+
# puts "#{a_record[:name]} --> #{a_record[:host]}"
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
#
|
49
|
+
# ==== Manipulate a Zonefile
|
50
|
+
#
|
51
|
+
# zf = Zonefile.from_file('/path/to/zonefile.db')
|
52
|
+
#
|
53
|
+
# # Change TTL and add an A-Record
|
54
|
+
#
|
55
|
+
# zf.soa[:ttl] = '123123' # Change the SOA ttl
|
56
|
+
# zf.a << { :class => 'IN', :name => 'www', :host => '192.168.100.1', :ttl => 3600 } # add A-Record
|
57
|
+
#
|
58
|
+
# # Increase Serial Number
|
59
|
+
# zf.new_serial
|
60
|
+
#
|
61
|
+
# # Print new zonefile
|
62
|
+
# puts "New Zonefile: \n#{zf.output}"
|
63
|
+
#
|
64
|
+
# == Author
|
65
|
+
#
|
66
|
+
# Martin Boese, based on Simon Flack Perl library DNS::ZoneParse
|
67
|
+
#
|
68
|
+
|
69
|
+
class Zonefile
|
70
|
+
|
71
|
+
RECORDS = %w{ mx a a4 ns cname txt ptr srv soa }
|
72
|
+
attr :records
|
73
|
+
attr :soa
|
74
|
+
attr :data
|
75
|
+
|
76
|
+
def method_missing(m)
|
77
|
+
return super unless RECORDS.include?(m.to_s)
|
78
|
+
@records[m]
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Compact a zonefile content - removes empty lines, comments,
|
83
|
+
# converts tabs into spaces etc...
|
84
|
+
def self.simplify(zf)
|
85
|
+
# concatenate everything split over multiple lines in parentheses - remove ;-comments in block
|
86
|
+
zf = zf.gsub(/(\([^\)]*?\))/) { |m| m.split(/\n/).map { |l| l.gsub(/\;.*$/, '') }.join("\n").gsub(/[\r\n]/, '') }
|
87
|
+
|
88
|
+
zf.split(/\n/).map do |line|
|
89
|
+
r = line.gsub(/\t/, ' ')
|
90
|
+
r = r.gsub(/\s+/, ' ')
|
91
|
+
r = r.gsub(/\;.*$/, '')
|
92
|
+
end.delete_if { |line| line.empty? || line[0].chr == ';'}.join("\n")
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# create a new zonefile object by passing the content of the zonefile
|
97
|
+
def initialize(zonefile = '', file_name= nil, origin= nil)
|
98
|
+
@data = zonefile
|
99
|
+
@filename = file_name
|
100
|
+
@origin = origin
|
101
|
+
|
102
|
+
@records = {}
|
103
|
+
@soa = {}
|
104
|
+
RECORDS.each { |r| @records[r.intern] = [] }
|
105
|
+
parse
|
106
|
+
end
|
107
|
+
|
108
|
+
# Create a new object by reading the content of a file
|
109
|
+
def self.from_file(file_name, origin = nil)
|
110
|
+
Zonefile.new(File.read(file_name), file_name.split('/').last, origin)
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_record(type, data= {})
|
114
|
+
@records[type.downcase.intern] << data
|
115
|
+
end
|
116
|
+
|
117
|
+
# Generates a new serial number in the format of YYYYMMDDII if possible
|
118
|
+
def new_serial
|
119
|
+
base = "%04d%02d%02d" % [Time.now.year, Time.now.month, Time.now.day ]
|
120
|
+
|
121
|
+
if ((self.soa[:serial].to_i / 100) > base.to_i) then
|
122
|
+
ns = self.soa[:serial].to_i + 1
|
123
|
+
self.soa[:serial] = ns.to_s
|
124
|
+
return ns.to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
ii = 0
|
128
|
+
while (("#{base}%02d" % ii).to_i <= self.soa[:serial].to_i) do
|
129
|
+
ii += 1
|
130
|
+
end
|
131
|
+
self.soa[:serial] = "#{base}%02d" % ii
|
132
|
+
end
|
133
|
+
|
134
|
+
def parse_line(line)
|
135
|
+
valid_name = /[\@a-z_\-\.0-9\*]+/i
|
136
|
+
valid_ip6 = /[\@a-z_\-\.0-9\*:]+/i
|
137
|
+
rr_class = /\b(?:IN|HS|CH)\b/i
|
138
|
+
rr_type = /\b(?:NS|A|CNAME)\b/i
|
139
|
+
rr_ttl = /(?:\d+[wdhms]?)+/i
|
140
|
+
ttl_cls = Regexp.new("(?:(#{rr_ttl})\s)?(?:(#{rr_class})\s)?")
|
141
|
+
|
142
|
+
data = {}
|
143
|
+
if line =~ /^(#{valid_name})? \s*
|
144
|
+
#{ttl_cls}
|
145
|
+
(#{rr_type}) \s
|
146
|
+
(#{valid_name})
|
147
|
+
/ix then
|
148
|
+
(name, ttl, dclass, type, host) = [$1, $2, $3, $4, $5]
|
149
|
+
add_record($4, :name => $1, :ttl => $2, :class => $3, :host => $5)
|
150
|
+
elsif line=~/^(#{valid_name})? \s*
|
151
|
+
#{ttl_cls}
|
152
|
+
AAAA \s
|
153
|
+
(#{valid_ip6})
|
154
|
+
/x then
|
155
|
+
add_record('a4', :name => $1, :ttl => $2, :class => $3, :host => $4)
|
156
|
+
elsif line=~/^(#{valid_name})? \s*
|
157
|
+
#{ttl_cls}
|
158
|
+
MX \s
|
159
|
+
(\d+) \s
|
160
|
+
(#{valid_name})
|
161
|
+
/ix then
|
162
|
+
add_record('mx', :name => $1, :ttl => $2, :class => $3, :pri => $4.to_i, :host => $5)
|
163
|
+
elsif line=~/^(#{valid_name})? \s*
|
164
|
+
#{ttl_cls}
|
165
|
+
SRV \s
|
166
|
+
(\d+) \s
|
167
|
+
(\d+) \s
|
168
|
+
(\d+) \s
|
169
|
+
(#{valid_name})
|
170
|
+
/ix
|
171
|
+
add_record('srv', :name => $1, :ttl => $2, :class => $3, :pri => $4, :weight => $5,
|
172
|
+
:port => $6, :host => $7)
|
173
|
+
elsif line=~/^(#{valid_name}) \s+
|
174
|
+
#{ttl_cls}
|
175
|
+
SOA \s+
|
176
|
+
(#{valid_name}) \s+
|
177
|
+
(#{valid_name}) \s*
|
178
|
+
\(?\s*
|
179
|
+
(#{rr_ttl}) \s+
|
180
|
+
(#{rr_ttl}) \s+
|
181
|
+
(#{rr_ttl}) \s+
|
182
|
+
(#{rr_ttl}) \s+
|
183
|
+
(#{rr_ttl}) \s*
|
184
|
+
\)?
|
185
|
+
/ix
|
186
|
+
ttl = @soa[:ttl] || $2 || ''
|
187
|
+
@soa[:origin] = $1
|
188
|
+
@soa[:ttl] = ttl
|
189
|
+
@soa[:primary] = $4
|
190
|
+
@soa[:email] = $5
|
191
|
+
@soa[:serial] = $6
|
192
|
+
@soa[:refresh] = $7
|
193
|
+
@soa[:retry] = $8
|
194
|
+
@soa[:expire] = $9
|
195
|
+
@soa[:minimumTTL] = $10
|
196
|
+
|
197
|
+
elsif line=~ /^(#{valid_name})? \s*
|
198
|
+
#{ttl_cls}
|
199
|
+
PTR \s+
|
200
|
+
(#{valid_name})
|
201
|
+
/ix
|
202
|
+
add_record('ptr', :name => $1, :class => $3, :ttl => $2, :host => $4)
|
203
|
+
elsif line =~ /(#{valid_name})? \s #{ttl_cls} TXT \s \"([^\"]*)\"/ix
|
204
|
+
add_record('txt', :name => $1, :ttl => $2, :class => $3, :text => $4)
|
205
|
+
elsif line =~ /\$TTL\s+(#{rr_ttl})/i
|
206
|
+
@soa[:ttl] = $1
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def parse
|
211
|
+
Zonefile.simplify(@data).each_line do |line|
|
212
|
+
parse_line(line)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# Build a new nicely formatted Zonefile
|
218
|
+
#
|
219
|
+
def output
|
220
|
+
out =<<-ENDH
|
221
|
+
;
|
222
|
+
; Database file #{@filename || 'unknown'} for #{@origin || 'unknown'} zone.
|
223
|
+
; Zone version: #{self.soa[:serial]}
|
224
|
+
;
|
225
|
+
#{self.soa[:ttl] ? "$TTL #{self.soa[:ttl]}" : ''}
|
226
|
+
#{self.soa[:origin]} #{self.soa[:ttl]} IN SOA #{self.soa[:primary]} #{self.soa[:email]} (
|
227
|
+
#{self.soa[:serial]} ; serial number
|
228
|
+
#{self.soa[:refresh]} ; refresh
|
229
|
+
#{self.soa[:retry]} ; retry
|
230
|
+
#{self.soa[:expire]} ; expire
|
231
|
+
#{self.soa[:minimumTTL]} ; minimum TTL
|
232
|
+
)
|
233
|
+
; Zone NS Records
|
234
|
+
ENDH
|
235
|
+
self.ns.each do |ns|
|
236
|
+
out << "#{ns[:name]} #{ns[:ttl]} #{ns[:class]} NS #{ns[:host]}\n"
|
237
|
+
end
|
238
|
+
out << "\n; Zone MX Records\n" unless self.mx.empty?
|
239
|
+
self.mx.each do |mx|
|
240
|
+
out << "#{mx[:name]} #{mx[:ttl]} #{mx[:class]} MX #{mx[:pri]} #{mx[:host]}\n"
|
241
|
+
end
|
242
|
+
|
243
|
+
self.a.each do |a|
|
244
|
+
out << "#{a[:name]} #{a[:ttl]} #{a[:class]} A #{a[:host]}\n"
|
245
|
+
end
|
246
|
+
self.cname.each do |cn|
|
247
|
+
out << "#{cn[:name]} #{cn[:ttl]} #{cn[:class]} CNAME #{cn[:host]}\n"
|
248
|
+
end
|
249
|
+
self.a4.each do |a4|
|
250
|
+
out << "#{a4[:name]} #{a4[:ttl]} #{a4[:class]} AAAA #{a4[:host]}\n"
|
251
|
+
end
|
252
|
+
self.txt.each do |tx|
|
253
|
+
out << "#{tx[:name]} #{tx[:ttl]} #{tx[:class]} TXT \"#{tx[:text]}\"\n"
|
254
|
+
end
|
255
|
+
self.srv.each do |srv|
|
256
|
+
out << "#{srv[:name]} #{srv[:ttl]} #{srv[:class]} SRV #{srv[:pri]} #{srv[:weight]} #{srv[:port]} #{srv[:host]}\n"
|
257
|
+
end
|
258
|
+
|
259
|
+
out
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
data/lib/zonefile.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'zonefile/zonefile'
|
data/tests/test-zone.db
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
; Database file dns-zoneparse-test.net.dns for dns-zoneparse-test.net zone.
|
2
|
+
; Zone version: 2000100501
|
3
|
+
$TTL 1H
|
4
|
+
@ 3600 IN SOA ns0.dns-zoneparse-test.net. support.dns-zoneparse-test.net. (
|
5
|
+
2000100501 ; serial number
|
6
|
+
10800 ; refresh
|
7
|
+
3600 ; retry
|
8
|
+
691200 ; expire
|
9
|
+
86400 ) ; minimum TTL
|
10
|
+
|
11
|
+
43200 IN NS ns0.dns-zoneparse-test.net. ; ( A multi line
|
12
|
+
comment )
|
13
|
+
@ IN NS ns1.dns-zoneparse-test.net.
|
14
|
+
|
15
|
+
@ IN A 127.0.0.1
|
16
|
+
@ IN MX 10 mail
|
17
|
+
ftp IN CNAME www
|
18
|
+
localhost IN A 127.0.0.1
|
19
|
+
mail IN A 127.0.0.1
|
20
|
+
www IN A 127.0.0.1
|
21
|
+
in a 10.0.0.2
|
22
|
+
43200 IN A 10.0.0.3
|
23
|
+
IN MX 10 10.0.0.4
|
24
|
+
A 10.0.0.5
|
25
|
+
TXT "web server"
|
26
|
+
foo IN A 10.0.0.6
|
27
|
+
mini A 10.0.0.7
|
28
|
+
icarus IN AAAA fe80::0260:83ff:fe7c:3a2a
|
29
|
+
soup IN TXT "This is a text message"
|
30
|
+
txta TXT "This is another text message"
|
31
|
+
_sip._tcp.example.com. 86400 IN SRV 0 5 5060 sipserver.example.com.
|
32
|
+
|
data/tests/zonefile.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
|
4
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
5
|
+
|
6
|
+
require 'zonefile'
|
7
|
+
|
8
|
+
$zonefile = ARGV[0] || 'test-zone.db'
|
9
|
+
|
10
|
+
class TC_Zonefile < Test::Unit::TestCase
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@zf = Zonefile.from_file(File.dirname(__FILE__) + '/'+$zonefile, 'test-origin')
|
14
|
+
end
|
15
|
+
|
16
|
+
def swap # generate output and re-read @zf from it
|
17
|
+
@zf = Zonefile.new(@zf.output, 'test-origin')
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_soa
|
21
|
+
assert_equal '86400', @zf.soa[:minimumTTL]
|
22
|
+
assert_equal '691200', @zf.soa[:expire]
|
23
|
+
assert_equal '3600', @zf.soa[:retry]
|
24
|
+
assert_equal '10800', @zf.soa[:refresh]
|
25
|
+
assert_equal '2000100501', @zf.soa[:serial]
|
26
|
+
assert_equal 'support.dns-zoneparse-test.net.', @zf.soa[:email]
|
27
|
+
assert_equal 'ns0.dns-zoneparse-test.net.', @zf.soa[:primary]
|
28
|
+
|
29
|
+
begin
|
30
|
+
@swap_soa = true
|
31
|
+
swap
|
32
|
+
test_soa
|
33
|
+
end unless @swap_soa
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_a
|
37
|
+
assert_equal 9, @zf.a.size
|
38
|
+
assert_equal 'mini', @zf.a.last[:name]
|
39
|
+
assert_equal '10.0.0.7', @zf.a.last[:host]
|
40
|
+
assert_equal '127.0.0.1', @zf.a.first[:host]
|
41
|
+
|
42
|
+
a = @zf.a.find { |a| a[:host] == '10.0.0.3'}
|
43
|
+
assert_equal '43200', a[:ttl]
|
44
|
+
assert_equal '', a[:name].to_s
|
45
|
+
|
46
|
+
begin
|
47
|
+
@swap_a = true
|
48
|
+
swap
|
49
|
+
test_a
|
50
|
+
end unless @swap_a
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_mx
|
54
|
+
assert_equal 2, @zf.mx.size
|
55
|
+
assert_equal 10, @zf.mx.first[:pri]
|
56
|
+
begin
|
57
|
+
@swap_mx = true
|
58
|
+
swap
|
59
|
+
test_mx
|
60
|
+
end unless @swap_mx
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_cname
|
64
|
+
assert !!@zf.cname.find { |e| e[:host] == 'www' }
|
65
|
+
begin
|
66
|
+
@swap_cname = true
|
67
|
+
swap
|
68
|
+
test_cname
|
69
|
+
end unless @swap_cname
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_ns
|
73
|
+
assert_equal 'ns0.dns-zoneparse-test.net.', @zf.ns[0][:host]
|
74
|
+
assert_equal 'ns1.dns-zoneparse-test.net.', @zf.ns[1][:host]
|
75
|
+
begin
|
76
|
+
@swap_ns = true
|
77
|
+
swap
|
78
|
+
test_ns
|
79
|
+
end unless @swap_ns
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_txt
|
83
|
+
assert_equal 'web server', @zf.txt[0][:text]
|
84
|
+
assert_equal 'IN', @zf.txt[1][:class]
|
85
|
+
assert_equal 'soup', @zf.txt[1][:name]
|
86
|
+
assert_equal 'txta', @zf.txt[2][:name]
|
87
|
+
assert_equal 3, @zf.txt.size
|
88
|
+
begin
|
89
|
+
@swap_txt = true
|
90
|
+
swap
|
91
|
+
test_txt
|
92
|
+
end unless @swap_txt
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_a4
|
96
|
+
assert_equal 'icarus', @zf.a4[0][:name]
|
97
|
+
assert_equal 'IN', @zf.a4[0][:class]
|
98
|
+
assert_equal 1, @zf.a4.size
|
99
|
+
assert_equal 'fe80::0260:83ff:fe7c:3a2a', @zf.a4[0][:host]
|
100
|
+
begin
|
101
|
+
@swap_a4 = true
|
102
|
+
swap
|
103
|
+
test_a4
|
104
|
+
end unless @swap_a4
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_srv
|
108
|
+
assert_equal '_sip._tcp.example.com.', @zf.srv[0][:name]
|
109
|
+
assert_equal '86400', @zf.srv[0][:ttl]
|
110
|
+
assert_equal '0', @zf.srv[0][:pri]
|
111
|
+
assert_equal '5', @zf.srv[0][:weight]
|
112
|
+
assert_equal '5060', @zf.srv[0][:port]
|
113
|
+
assert_equal 'sipserver.example.com.', @zf.srv[0][:host]
|
114
|
+
begin
|
115
|
+
@swap_srv = true
|
116
|
+
swap
|
117
|
+
test_srv
|
118
|
+
end unless @swap_srv
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_serial_generator
|
122
|
+
old = @zf.soa[:serial]
|
123
|
+
new = @zf.new_serial
|
124
|
+
assert new.to_i > old.to_i
|
125
|
+
newer = @zf.new_serial
|
126
|
+
assert newer.to_i - 1, new
|
127
|
+
|
128
|
+
@zf.soa[:serial] = '9999889901'
|
129
|
+
@zf.new_serial
|
130
|
+
assert_equal '9999889902', @zf.soa[:serial]
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
def test_output
|
136
|
+
# puts @zf.data
|
137
|
+
# puts '==================='
|
138
|
+
# puts @zf.output
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zonefile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "1.00"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Boese
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-04 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: martin@internet.ao
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/zonefile
|
26
|
+
- lib/zonefile/zonefile.rb
|
27
|
+
- lib/zonefile.rb
|
28
|
+
- tests/test-zone.db
|
29
|
+
- tests/zonefile.rb
|
30
|
+
has_rdoc: true
|
31
|
+
homepage: http://zonefile.rubyforge.org/
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: "0"
|
42
|
+
version:
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
rubyforge_project: zonefile
|
52
|
+
rubygems_version: 1.2.0
|
53
|
+
signing_key:
|
54
|
+
specification_version: 2
|
55
|
+
summary: BIND 8/9 Zonefile Reader and Writer
|
56
|
+
test_files: []
|
57
|
+
|