dns-zonefile 0.0.1
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/README +51 -0
- data/Rakefile +32 -0
- data/TODO +8 -0
- data/doc/example2.com.zone +16 -0
- data/doc/example3.com.zone +24 -0
- data/doc/zonefile.example +45 -0
- data/doc/zonefile.treetop +257 -0
- data/lib/dns/zonefile.rb +16 -0
- data/lib/dns/zonefile_parser.rb +1895 -0
- data/spec/dns/zonefile_spec.rb +141 -0
- data/spec/spec_helper.rb +2 -0
- metadata +72 -0
data/README
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
DNS::Zonefile
|
2
|
+
=============
|
3
|
+
|
4
|
+
The format of a DNS Zonefile is defined in RFC 1035 section 5 and RFC
|
5
|
+
1034 section 3.6.1. To anyone who's using BIND they'll look very
|
6
|
+
familiar.
|
7
|
+
|
8
|
+
This is an attempt to use Ruby parse them into an object graph which can
|
9
|
+
be investigated programatically, manipulated, validated or printed into
|
10
|
+
some canonical form.
|
11
|
+
|
12
|
+
|
13
|
+
Getting setup
|
14
|
+
-------------
|
15
|
+
|
16
|
+
Well, you'll need the treetop and polyglot gems installed. They do all
|
17
|
+
the hard work. Thanks!
|
18
|
+
|
19
|
+
sudo gem install treetop polyglot
|
20
|
+
|
21
|
+
Now you'll need to generate the Treetop parser from the Zonefile grammar
|
22
|
+
that I've hacked together.
|
23
|
+
|
24
|
+
rake generate_grammar
|
25
|
+
|
26
|
+
Okay, you're ready to move onto the examples now.
|
27
|
+
|
28
|
+
|
29
|
+
Examples
|
30
|
+
--------
|
31
|
+
|
32
|
+
zonefile = "/path/to/file.zone"
|
33
|
+
zone_string = File.read(zonefile)
|
34
|
+
zone = DNS::Zonefile.parse(zone_string)
|
35
|
+
|
36
|
+
puts zone.soa.origin
|
37
|
+
puts zone.soa.ns
|
38
|
+
puts zone.resource_records[0]
|
39
|
+
|
40
|
+
|
41
|
+
Authors
|
42
|
+
-------
|
43
|
+
|
44
|
+
Craig R Webster <http://barkingiguana.com/>
|
45
|
+
|
46
|
+
|
47
|
+
Contributing
|
48
|
+
------------
|
49
|
+
|
50
|
+
See the TODO. Send me patches or pull request either by email or on
|
51
|
+
GitHub.
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
desc "Run the tests"
|
5
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
6
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
7
|
+
t.ruby_opts = [ '-rubygems' ]
|
8
|
+
t.spec_opts = [ '--format specdoc' ]
|
9
|
+
t.libs << 'lib'
|
10
|
+
t.libs << 'spec'
|
11
|
+
end
|
12
|
+
task :default => [ :generate_grammar, :spec ]
|
13
|
+
|
14
|
+
desc "Regenerate the grammar definition"
|
15
|
+
task :generate_grammar do
|
16
|
+
parser_file = 'lib/dns/zonefile_parser.rb'
|
17
|
+
File.unlink(parser_file) if File.exists?(parser_file)
|
18
|
+
puts %x[tt doc/zonefile -o #{parser_file}]
|
19
|
+
source = "require 'treetop'\n\n"
|
20
|
+
source += "module DNS\n"
|
21
|
+
parser_source = File.open(parser_file, 'r').read
|
22
|
+
parser_source.gsub!(/(\s+)Zonefile$/, '\1ZonefileGrammar # :nodoc:')
|
23
|
+
source += parser_source.split(/\n/).map { |l| "\t#{l}" }.join("\n")
|
24
|
+
source += "\nend"
|
25
|
+
File.open(parser_file, 'w') do |f|
|
26
|
+
f.write(source)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
task :gem => :generate_grammar do
|
31
|
+
puts %x[gem build dns-zonefile.gemspec]
|
32
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
* Allow empty host strings which expand to the zone origin eg:
|
2
|
+
[spaces] MX 100 mail4
|
3
|
+
-> example.com. MX 100 mail4.example.com.
|
4
|
+
* Support more record types: http://www.dns.net/dnsrd/rr.html
|
5
|
+
* Output the zone object-graph as a zone string.
|
6
|
+
* Should this support sub-types of TXT records - should we recognise
|
7
|
+
SPF records as special TXT records or are these just TXT records?
|
8
|
+
* Allow comparison of resource records and zones for checking equality.
|
@@ -0,0 +1,16 @@
|
|
1
|
+
$ORIGIN example2.com.
|
2
|
+
$TTL 86400
|
3
|
+
@ IN SOA ns1.nameservers.com. dns.example2.com. (
|
4
|
+
2009102121
|
5
|
+
7200
|
6
|
+
7200
|
7
|
+
1209600
|
8
|
+
86400
|
9
|
+
)
|
10
|
+
@ NS ns1.nameservers.com.
|
11
|
+
@ NS ns2.nameservers.com.
|
12
|
+
@ NS ns3.nameservers.com.
|
13
|
+
@ NS ns4.nameservers.com.
|
14
|
+
@ A 197.127.138.160
|
15
|
+
www A 197.127.138.160
|
16
|
+
right A 178.109.109.208
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$ORIGIN example1.com.
|
2
|
+
$TTL 86400
|
3
|
+
@ IN SOA ns1.name-server.com. support.example1.com. (
|
4
|
+
2009102121
|
5
|
+
7200
|
6
|
+
7200
|
7
|
+
1209600
|
8
|
+
86400
|
9
|
+
)
|
10
|
+
@ NS ns1.name-server.com.
|
11
|
+
@ NS ns2.name-server.com.
|
12
|
+
@ NS ns3.name-server.com.
|
13
|
+
@ NS ns4.name-server.com.
|
14
|
+
@ MX 10 mx-ext-0.mx-service.com.
|
15
|
+
@ MX 20 mx-ext-1-0.mx-service.com.
|
16
|
+
@ MX 20 mx-ext-1-1.mx-service.com.
|
17
|
+
@ MX 30 mx-ext-2-0.mx-service.com.
|
18
|
+
@ MX 30 mx-ext-2-1.mx-service.co.uk.
|
19
|
+
@ MX 30 mx-ext-2-2.mx-service.org.
|
20
|
+
@ MX 30 mx-ext-2-3.mx-service.net.uk.
|
21
|
+
@ A 197.207.108.251
|
22
|
+
* A 197.207.108.251
|
23
|
+
googleblahblah3135 CNAME google.com.
|
24
|
+
mail CNAME ghs.google.com.
|
@@ -0,0 +1,45 @@
|
|
1
|
+
$ORIGIN example.com. ; Start zone example.com
|
2
|
+
$TTL 1h ; Default TTL
|
3
|
+
|
4
|
+
; TODO: Find out if the above are optional.
|
5
|
+
; See RFC 1035 (section 5) and RFC 1034 (Section 3.6.1)
|
6
|
+
|
7
|
+
example.com. IN SOA ns.example.com. hostmaster.example.com. (
|
8
|
+
2009080101 ; zone serial
|
9
|
+
1d ; slave refresh
|
10
|
+
1d ; slave retry
|
11
|
+
4w ; slave expiration
|
12
|
+
1h ; minimum TTL (is this of SOA or zone?)
|
13
|
+
)
|
14
|
+
|
15
|
+
; TODO: Find out how record-specific TTLs are listed.
|
16
|
+
;
|
17
|
+
; Anything after a semi-colon is a comment
|
18
|
+
;
|
19
|
+
; @ means use the same name as the origin
|
20
|
+
;
|
21
|
+
; If the name doesn't end with a period then it's prepended to the
|
22
|
+
; origin.
|
23
|
+
|
24
|
+
@ NS ns1 ; specify a nameserver
|
25
|
+
example.com. NS ns2.example.com. ; specify a nameserver
|
26
|
+
|
27
|
+
ns1 A 123.123.123.123
|
28
|
+
ns2 A 123.123.123.124
|
29
|
+
|
30
|
+
@ A 123.123.123.122
|
31
|
+
www CNAME example.com
|
32
|
+
|
33
|
+
mx1 A 123.123.123.123 ; Records used as MX must not be
|
34
|
+
; CNAME records. See RFC 2181.
|
35
|
+
|
36
|
+
@ AAAA ; TODO: Find out how IPv6 addresses are specified
|
37
|
+
|
38
|
+
@ MX 10 mx1
|
39
|
+
@ MX 20 mx2.example.com.
|
40
|
+
@ MX 20 mx-backup.elsewhere.com.
|
41
|
+
|
42
|
+
@ TXT "Random text"
|
43
|
+
@ TXT "spf=v1 ..." ; TODO: Get an example of an SPF record
|
44
|
+
|
45
|
+
@ SRV ; TODO: Get an example of a service record eg XMPP
|
@@ -0,0 +1,257 @@
|
|
1
|
+
grammar Zonefile
|
2
|
+
rule zone
|
3
|
+
(variable / space / comment)* soa (resource_record / comment / space)* {
|
4
|
+
def variables
|
5
|
+
@variables ||= begin
|
6
|
+
raw = elements[0].elements.select { |e| e.to_s =~ /^\$/ }
|
7
|
+
variables = {}
|
8
|
+
raw.each do |e|
|
9
|
+
variables[e.name.text_value.to_s] = e.value.text_value.to_s
|
10
|
+
end
|
11
|
+
variables
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def origin
|
16
|
+
soa.origin.host.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
text_value
|
21
|
+
end
|
22
|
+
|
23
|
+
def rr
|
24
|
+
elements[-1].elements.select { |e| e.to_s !~ /\A\s*(;.*)?\z|\A\z/; }
|
25
|
+
end
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
rule variable
|
30
|
+
"$" name:([a-zA-Z0-9]+) space value:([a-zA-Z0-9\.\-]+) space? comment? {
|
31
|
+
def to_s
|
32
|
+
"$#{name.text_value.to_s} #{value.text_value.to_s}"
|
33
|
+
end
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
rule soa
|
38
|
+
origin space zone_type space "SOA" space ns space rp space "(" space* serial space refresh space retry space expiry space ttl space* ")" {
|
39
|
+
def to_s
|
40
|
+
text_value
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
rule resource_record
|
46
|
+
space? record:(a_record / cname_record / mx_record / ns_record) space? comment? {
|
47
|
+
def to_s
|
48
|
+
text_value
|
49
|
+
end
|
50
|
+
|
51
|
+
def record_type
|
52
|
+
record.elements[2].text_value
|
53
|
+
end
|
54
|
+
|
55
|
+
def method_missing(method_name, *args)
|
56
|
+
if record.respond_to?(method_name)
|
57
|
+
record.send(method_name, *args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def respond_to?(method_name)
|
62
|
+
super || record.respond_to?(method_name)
|
63
|
+
end
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
rule a_record
|
68
|
+
host space "A" space ip_address {
|
69
|
+
def to_s
|
70
|
+
text_value
|
71
|
+
end
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
rule ip_address
|
76
|
+
[\d]+ "." [\d]+ "." [\d]+ "." [\d]+ {
|
77
|
+
def to_s
|
78
|
+
text_value
|
79
|
+
end
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
rule cname_record
|
84
|
+
alias:host space "CNAME" space host {
|
85
|
+
def to_s
|
86
|
+
text_value
|
87
|
+
end
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
rule mx_record
|
92
|
+
host space "MX" space priority:integer space exchanger:host {
|
93
|
+
def to_s
|
94
|
+
text_value
|
95
|
+
end
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
rule ns_record
|
100
|
+
host space "NS" space nameserver:host {
|
101
|
+
def to_s
|
102
|
+
text_value
|
103
|
+
end
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
rule origin
|
108
|
+
host comment* {
|
109
|
+
def to_s
|
110
|
+
text_value
|
111
|
+
end
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
rule space
|
116
|
+
[\s]+ {
|
117
|
+
def to_s
|
118
|
+
text_value
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
rule zone_type
|
124
|
+
"IN" {
|
125
|
+
def to_s
|
126
|
+
text_value
|
127
|
+
end
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
rule comment
|
132
|
+
space* ";" [^\n]+ {
|
133
|
+
def to_s
|
134
|
+
text_value
|
135
|
+
end
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
rule ns
|
140
|
+
host comment* {
|
141
|
+
def to_s
|
142
|
+
text_value
|
143
|
+
end
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
rule rp
|
148
|
+
([a-zA-Z0-9\-]+ ".")+ {
|
149
|
+
def to_s
|
150
|
+
text_value
|
151
|
+
end
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
rule serial
|
156
|
+
integer comment* {
|
157
|
+
def to_i
|
158
|
+
integer.to_i
|
159
|
+
end
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
rule time_interval
|
164
|
+
integer time_multiplier {
|
165
|
+
def to_s
|
166
|
+
text_value
|
167
|
+
end
|
168
|
+
|
169
|
+
def to_i
|
170
|
+
multipliers = {
|
171
|
+
"" => 1,
|
172
|
+
"h" => 60 * 60,
|
173
|
+
"d" => 60 * 60 * 24,
|
174
|
+
"w" => 60 * 60 * 24 * 7,
|
175
|
+
"m" => 60 * 60 * 24 * 7 * 4,
|
176
|
+
"y" => 60 * 60 * 24 * 7 * 4 * 52
|
177
|
+
}
|
178
|
+
multipliers[time_multiplier.to_s] * integer.to_i
|
179
|
+
end
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
rule refresh
|
184
|
+
time_interval comment* {
|
185
|
+
def to_i
|
186
|
+
time_interval.to_i
|
187
|
+
end
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
rule integer
|
192
|
+
[0-9]+ {
|
193
|
+
def to_i
|
194
|
+
text_value.to_i
|
195
|
+
end
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
rule time_multiplier
|
200
|
+
("h" / "d" / "w" / "m" / "y" / "") {
|
201
|
+
def to_s
|
202
|
+
text_value
|
203
|
+
end
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
rule retry
|
208
|
+
time_interval comment* {
|
209
|
+
def to_i
|
210
|
+
time_interval.to_i
|
211
|
+
end
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
rule expiry
|
216
|
+
time_interval comment* {
|
217
|
+
def to_i
|
218
|
+
time_interval.to_i
|
219
|
+
end
|
220
|
+
}
|
221
|
+
end
|
222
|
+
|
223
|
+
rule ttl
|
224
|
+
time_interval comment* {
|
225
|
+
def to_i
|
226
|
+
time_interval.to_i
|
227
|
+
end
|
228
|
+
}
|
229
|
+
end
|
230
|
+
|
231
|
+
rule host
|
232
|
+
(([a-zA-Z0-9\-\.])+ / "@"?) {
|
233
|
+
def zone
|
234
|
+
p = parent
|
235
|
+
while p.respond_to?(:parent) && p.parent
|
236
|
+
p = p.parent
|
237
|
+
end
|
238
|
+
p
|
239
|
+
end
|
240
|
+
|
241
|
+
def to_s
|
242
|
+
case
|
243
|
+
when text_value =~ /\.$/
|
244
|
+
text_value
|
245
|
+
when text_value == "@" || text_value == ""
|
246
|
+
if parent == zone.soa.origin
|
247
|
+
zone.variables['ORIGIN']
|
248
|
+
else
|
249
|
+
zone.origin.to_s
|
250
|
+
end
|
251
|
+
else
|
252
|
+
text_value + '.' + zone.origin.to_s
|
253
|
+
end
|
254
|
+
end
|
255
|
+
}
|
256
|
+
end
|
257
|
+
end
|
data/lib/dns/zonefile.rb
ADDED