dns-zonefile 0.0.1 → 1.0.0

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.
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dns-zonefile.gemspec
4
+ gemspec
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dns-zonefile (1.0.0)
5
+ polyglot
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.1.3)
11
+ polyglot (0.3.2)
12
+ rspec (2.6.0)
13
+ rspec-core (~> 2.6.0)
14
+ rspec-expectations (~> 2.6.0)
15
+ rspec-mocks (~> 2.6.0)
16
+ rspec-core (2.6.4)
17
+ rspec-expectations (2.6.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.6.0)
20
+ treetop (1.4.10)
21
+ polyglot
22
+ polyglot (>= 0.3.1)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ dns-zonefile!
29
+ rspec (= 2.6)
30
+ treetop
data/LICENCE ADDED
@@ -0,0 +1,13 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2010 Craig R Webster <craig@barkingiguana.com>
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
data/README CHANGED
@@ -25,24 +25,48 @@ that I've hacked together.
25
25
 
26
26
  Okay, you're ready to move onto the examples now.
27
27
 
28
+ The above steps should not be necessary if you install the gem via rubygems.
29
+
28
30
 
29
31
  Examples
30
32
  --------
31
33
 
34
+ Using raw data from the parser. Note that "@" isn't translated in this mode.
35
+ Nor are inherited TTLs interpreted.
36
+
32
37
  zonefile = "/path/to/file.zone"
33
38
  zone_string = File.read(zonefile)
34
39
  zone = DNS::Zonefile.parse(zone_string)
35
40
 
41
+ puts zone.soa.origin.to_s
42
+ puts zone.soa.ns.to_s
43
+ puts zone.rr[0].to_s
44
+
45
+ Using more structure data. @, TTLs, and empty hostname inheritance are all
46
+ handled in this mode.
47
+
48
+ zonefile = "/path/to/file.zone"
49
+ zone_string = File.read(zonefile)
50
+ zone = DNS::Zonefile.load(zone_string)
51
+ # or, if no $origin is in the zone file
52
+ zone = DNS::Zonefile.load(zone_string, 'example.com.')
53
+
36
54
  puts zone.soa.origin
37
- puts zone.soa.ns
38
- puts zone.resource_records[0]
55
+ puts zone.soa.nameserver
56
+ puts zone.records[1]
57
+ # get all MX records
58
+ puts zone.records_of(DNS::MX)
39
59
 
40
60
 
41
61
  Authors
42
62
  -------
43
63
 
64
+ Original code and concept:
44
65
  Craig R Webster <http://barkingiguana.com/>
45
66
 
67
+ Additions:
68
+ t.e.morgan <http://zerigo.com/>
69
+
46
70
 
47
71
  Contributing
48
72
  ------------
data/Rakefile CHANGED
@@ -1,32 +1,30 @@
1
+ require "bundler/gem_tasks"
2
+
1
3
  require 'rake'
2
- require 'spec/rake/spectask'
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
3
6
 
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
7
  task :default => [ :generate_grammar, :spec ]
13
8
 
14
9
  desc "Regenerate the grammar definition"
15
10
  task :generate_grammar do
16
- parser_file = 'lib/dns/zonefile_parser.rb'
11
+ parser_file = 'lib/dns/zonefile/parser.rb'
17
12
  File.unlink(parser_file) if File.exists?(parser_file)
18
13
  puts %x[tt doc/zonefile -o #{parser_file}]
19
14
  source = "require 'treetop'\n\n"
20
15
  source += "module DNS\n"
16
+ source += " module Zonefile\n"
21
17
  parser_source = File.open(parser_file, 'r').read
22
18
  parser_source.gsub!(/(\s+)Zonefile$/, '\1ZonefileGrammar # :nodoc:')
23
- source += parser_source.split(/\n/).map { |l| "\t#{l}" }.join("\n")
19
+ parser_source.gsub!(/(\s+)ZonefileParser/, '\1Parser')
20
+ source += parser_source.split(/\n/).map { |l| " #{l}".rstrip }.join("\n")
21
+ source += "\n end"
24
22
  source += "\nend"
25
23
  File.open(parser_file, 'w') do |f|
26
24
  f.write(source)
27
25
  end
28
26
  end
29
27
 
30
- task :gem => :generate_grammar do
28
+ task :build => [ :generate_grammar, :spec ] do
31
29
  puts %x[gem build dns-zonefile.gemspec]
32
- end
30
+ end
data/TODO CHANGED
@@ -1,8 +1,4 @@
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
1
  * Support more record types: http://www.dns.net/dnsrd/rr.html
5
2
  * 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.
3
+ * Allow comparison of resource records and zones for checking equality.
4
+ * Allow unqualified ns and rp in SOA
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dns/zonefile/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dns-zonefile"
7
+ s.version = DNS::Zonefile::VERSION
8
+ s.authors = ["Craig R Webster"]
9
+ s.email = ["craig@barkingiguana.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Work with zonefiles (RFC 1035 section 5 and RFC 1034 section 3.6.1)}
12
+ s.description = %q{The format of a DNS Zonefile is defined in RFC 1035 section 5 and RFC
13
+ 1034 section 3.6.1. To anyone who's using BIND they'll look very
14
+ familiar.
15
+
16
+ This is an attempt to use Ruby parse them into an object graph which can
17
+ be investigated programatically, manipulated, validated or printed into
18
+ some canonical form.}
19
+
20
+ s.rubyforge_project = "dns-zonefile"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+
27
+ s.add_development_dependency "rspec", "= 2.6"
28
+ s.add_development_dependency "treetop"
29
+ s.add_runtime_dependency "polyglot"
30
+ end
@@ -0,0 +1,60 @@
1
+ ; Hi! I'm an example zonefile.
2
+ $ORIGIN example.com.
3
+ $TTL 86400; expire in 1 day.
4
+ @ IN SOA ns.example.com. hostmaster.example.com. (
5
+ 2007120710 ; serial number of this zone file
6
+ 1d ; slave refresh (1 day)
7
+ 1d ; slave retry time in case of a problem (1 day)
8
+ 4W ; slave expiration time (4 weeks)
9
+ 3600 ; minimum caching time in case of failed lookups (1 hour)
10
+ )
11
+ ; That's the SOA part done.
12
+
13
+ ; Next comment line has nothing after the semi-colon.
14
+ ;
15
+
16
+ ; Let's start the resource records.
17
+ example.com. NS ns ; ns.example.com is the nameserver for example.com
18
+ example.com. NS ns.somewhere.com. ; ns.somewhere.com is a backup nameserver for example.com
19
+ example.com. A 10.0.0.1 ; ip address for "example.com"
20
+ @ A 10.0.0.11 ; secondary ip for "example.com"
21
+ A 10.0.0.12 ; tertiary ip for "example.com"
22
+ ns A 10.0.0.2 ; ip address for "ns.example.com"
23
+ A 10.0.0.21 ; secondary ip for "ns.example.com"
24
+ * A 10.0.0.100 ; wildcard
25
+ *.sub A 10.0.0.101 ; subdomain wildcard
26
+ with-class IN A 10.0.0.3 ; record that includes the class type of IN
27
+ with-ttl 60 A 10.0.0.5 ; with a specified TTL
28
+ ttl-class 60 IN A 10.0.0.6 ; with TTL and class type
29
+ www CNAME ns ; "www.example.com" is an alias for "ns.example.com"
30
+ wwwtest CNAME www ; "wwwtest.example.com" is another alias for "www.example.com"
31
+ www2 CNAME ns.example.com. ; yet another alias, with FQDN target
32
+
33
+ ; Email... that'll be fun then
34
+ example.com. MX 10 mail.example.com. ; mail.example.com is the mailserver for example.com
35
+ @ MX 20 mail2.example.com. ; Similar to above line, but using "@" to say "use $ORIGIN"
36
+ @ MX 50 mail3 ; Similar to above line, but using a host within this domain
37
+
38
+ @ AAAA 2001:db8:a::1 ; IPv6, lowercase
39
+ ns AAAA 2001:DB8:B::1 ; IPv6, uppercase
40
+ mail AAAA 2001:db8:c::10.0.0.4 ; IPv6, with trailing IPv4-type address
41
+
42
+ sip NAPTR 100 10 "U" "E2U+sip" "!^.*$!sip:cs@example.com!i" . ; NAPTR record
43
+ sip2 NAPTR 100 10 "" "" "/urn:cid:.+@([^\.]+\.)(.*)$/\2/i" . ; another one
44
+
45
+ _xmpp-server._tcp SRV 5 0 5269 xmpp-server.l.google.com. ; SRV record
46
+
47
+ ; TXT record, with embedded semicolons
48
+ _domainkey TXT "v=DKIM1\;g=*\;k=rsa\; p=4tkw1bbkfa0ahfjgnbewr2ttkvahvfmfizowl9s4g0h28io76ndow25snl9iumpcv0jwxr2k"
49
+
50
+ @ TXT "some other \"message\" goes here" ; embedded quotes
51
+ long TXT "a multi-segment TXT record" "usually used for really long TXT records" "since each segment can only span 255 chars"
52
+ @ SPF "v=spf1 a a:other.domain.com ~all"
53
+
54
+ 45 IN PTR @
55
+
56
+ $ORIGIN test.example.com.
57
+ $TTL 3600; expire in 1 day.
58
+ @ A 10.1.0.1 ; Test with alternate origin
59
+ MX 10 mail.example.com.
60
+ www A 10.1.0.2 ; www.test.example.com.
@@ -1,6 +1,6 @@
1
1
  grammar Zonefile
2
2
  rule zone
3
- (variable / space / comment)* soa (resource_record / comment / space)* {
3
+ (variable / space_or_break / comment)* soa (resource_record / variable / comment / space / linebreak)* {
4
4
  def variables
5
5
  @variables ||= begin
6
6
  raw = elements[0].elements.select { |e| e.to_s =~ /^\$/ }
@@ -21,7 +21,13 @@ grammar Zonefile
21
21
  end
22
22
 
23
23
  def rr
24
- elements[-1].elements.select { |e| e.to_s !~ /\A\s*(;.*)?\z|\A\z/; }
24
+ elements[-1].elements.select { |e| e.to_s !~ /\A\s*([;$].*)?\z|\A\z/; }
25
+ end
26
+
27
+ def entries
28
+ elements[0].elements.select { |e| e.to_s !~ /\A\s*(;.*)?\z|\A\z/; } +
29
+ [soa] +
30
+ elements[-1].elements.select { |e| e.to_s !~ /\A\s*(;.*)?\z|\A\z/; }
25
31
  end
26
32
  }
27
33
  end
@@ -31,25 +37,41 @@ grammar Zonefile
31
37
  def to_s
32
38
  "$#{name.text_value.to_s} #{value.text_value.to_s}"
33
39
  end
40
+
41
+ def parse_type ; :variable ; end
34
42
  }
35
43
  end
36
44
 
37
45
  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* ")" {
46
+ origin space ttl klass "SOA" space ns space rp space "("? space_or_break* serial space_or_break refresh space_or_break reretry space_or_break expiry space_or_break nxttl space_or_break* ")"? {
39
47
  def to_s
40
- text_value
48
+ "#{origin} #{ttl} #{klass} SOA #{ns} #{rp} (#{serial} #{refresh} #{reretry} #{expiry} #{nxttl})"
41
49
  end
50
+
51
+ def parse_type ; :soa ; end
42
52
  }
43
53
  end
44
54
 
45
55
  rule resource_record
46
- space? record:(a_record / cname_record / mx_record / ns_record) space? comment? {
56
+ record:(a_record / aaaa_record / cname_record / mx_record / naptr_record / ns_record / ptr_record / srv_record / spf_record / txt_record / soa_record) space* comment? linebreak {
57
+ def zone
58
+ p = parent
59
+ while p.respond_to?(:parent) && p.parent
60
+ p = p.parent
61
+ end
62
+ p
63
+ end
64
+
47
65
  def to_s
48
66
  text_value
49
67
  end
50
68
 
51
69
  def record_type
52
- record.elements[2].text_value
70
+ record.elements[4].text_value
71
+ end
72
+
73
+ def ttl
74
+ record.ttl || zone.variables['TTL'].to_i
53
75
  end
54
76
 
55
77
  def method_missing(method_name, *args)
@@ -61,13 +83,15 @@ grammar Zonefile
61
83
  def respond_to?(method_name)
62
84
  super || record.respond_to?(method_name)
63
85
  end
86
+
87
+ def parse_type ; :record ; end
64
88
  }
65
89
  end
66
90
 
67
91
  rule a_record
68
- host space "A" space ip_address {
92
+ host space ttl klass "A" space ip_address {
69
93
  def to_s
70
- text_value
94
+ "#{host} #{ttl} #{klass} A #{ip_address}"
71
95
  end
72
96
  }
73
97
  end
@@ -79,27 +103,91 @@ grammar Zonefile
79
103
  end
80
104
  }
81
105
  end
106
+
107
+ rule aaaa_record
108
+ host space ttl klass "AAAA" space ip_address:ip6_address {
109
+ def to_s
110
+ "#{host} #{ttl} #{klass} AAAA #{ip_address}"
111
+ end
112
+ }
113
+ end
114
+
115
+ rule ip6_address
116
+ [\da-fA-F:.] 2..39 {
117
+ def to_s
118
+ text_value.downcase
119
+ end
120
+ }
121
+ end
82
122
 
83
123
  rule cname_record
84
- alias:host space "CNAME" space host {
124
+ host space ttl klass "CNAME" space target:host {
85
125
  def to_s
86
- text_value
126
+ "#{host} #{ttl} #{klass} CNAME #{target}"
87
127
  end
88
128
  }
89
129
  end
90
130
 
91
131
  rule mx_record
92
- host space "MX" space priority:integer space exchanger:host {
132
+ host space ttl klass "MX" space priority:integer space exchanger:host {
93
133
  def to_s
94
- text_value
134
+ "#{host} #{ttl} #{klass} MX #{priority} #{exchanger}"
135
+ end
136
+ }
137
+ end
138
+
139
+ rule naptr_record
140
+ host space ttl klass "NAPTR" space data {
141
+ def to_s
142
+ "#{host} #{ttl} #{klass} NAPTR #{data}"
95
143
  end
96
144
  }
97
145
  end
98
146
 
99
147
  rule ns_record
100
- host space "NS" space nameserver:host {
148
+ host space ttl klass "NS" space nameserver:host {
101
149
  def to_s
102
- text_value
150
+ "#{host} #{ttl} #{klass} NS #{nameserver}"
151
+ end
152
+ }
153
+ end
154
+
155
+ rule ptr_record
156
+ host space ttl klass "PTR" space target:host {
157
+ def to_s
158
+ "#{host} #{ttl} #{klass} PTR #{target}"
159
+ end
160
+ }
161
+ end
162
+
163
+ rule soa_record
164
+ origin space ttl klass "SOA" space ns space rp space data {
165
+ def to_s
166
+ "#{origin} #{ttl} #{klass} SOA #{ns} #{rp} (#{space})"
167
+ end
168
+ }
169
+ end
170
+
171
+ rule srv_record
172
+ host space ttl klass "SRV" space priority:integer space weight:integer space port:integer space target:host {
173
+ def to_s
174
+ "#{host} #{ttl} #{klass} SRV #{priority} #{weight} #{port} #{target}"
175
+ end
176
+ }
177
+ end
178
+
179
+ rule spf_record
180
+ host space ttl klass "SPF" space data:txt_data {
181
+ def to_s
182
+ "#{host} #{ttl} #{klass} SPF #{data}"
183
+ end
184
+ }
185
+ end
186
+
187
+ rule txt_record
188
+ host space ttl klass "TXT" space data:txt_data {
189
+ def to_s
190
+ "#{host} #{ttl} #{klass} TXT #{data}"
103
191
  end
104
192
  }
105
193
  end
@@ -107,31 +195,47 @@ grammar Zonefile
107
195
  rule origin
108
196
  host comment* {
109
197
  def to_s
110
- text_value
198
+ "#{host}"
111
199
  end
112
200
  }
113
201
  end
114
202
 
115
203
  rule space
116
- [\s]+ {
204
+ [ \t]+ {
117
205
  def to_s
118
206
  text_value
119
207
  end
120
208
  }
121
209
  end
122
-
123
- rule zone_type
124
- "IN" {
210
+
211
+ rule linebreak
212
+ [\n\r]+ {
213
+ def to_s
214
+ ''
215
+ end
216
+ }
217
+ end
218
+
219
+ rule space_or_break
220
+ [\s]+ {
125
221
  def to_s
126
222
  text_value
127
223
  end
128
224
  }
129
225
  end
130
226
 
227
+ rule klass
228
+ (("IN" space) / '') {
229
+ def to_s
230
+ text_value.strip
231
+ end
232
+ }
233
+ end
234
+
131
235
  rule comment
132
- space* ";" [^\n]+ {
236
+ space* ";" [^\n\r]* {
133
237
  def to_s
134
- text_value
238
+ text_value.strip
135
239
  end
136
240
  }
137
241
  end
@@ -139,11 +243,12 @@ grammar Zonefile
139
243
  rule ns
140
244
  host comment* {
141
245
  def to_s
142
- text_value
246
+ "#{host}"
143
247
  end
144
248
  }
145
249
  end
146
250
 
251
+ # fixme: same as host, except also allow \. -- eg: firstname\.lastname.domain.com
147
252
  rule rp
148
253
  ([a-zA-Z0-9\-]+ ".")+ {
149
254
  def to_s
@@ -157,6 +262,9 @@ grammar Zonefile
157
262
  def to_i
158
263
  integer.to_i
159
264
  end
265
+ def to_s
266
+ "#{to_i}"
267
+ end
160
268
  }
161
269
  end
162
270
 
@@ -167,15 +275,7 @@ grammar Zonefile
167
275
  end
168
276
 
169
277
  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
278
+ time_multiplier.to_i * integer.to_i
179
279
  end
180
280
  }
181
281
  end
@@ -185,6 +285,9 @@ grammar Zonefile
185
285
  def to_i
186
286
  time_interval.to_i
187
287
  end
288
+ def to_s
289
+ time_interval.to_s
290
+ end
188
291
  }
189
292
  end
190
293
 
@@ -193,22 +296,38 @@ grammar Zonefile
193
296
  def to_i
194
297
  text_value.to_i
195
298
  end
299
+ def to_s
300
+ "#{to_i}"
301
+ end
196
302
  }
197
303
  end
198
304
 
199
305
  rule time_multiplier
200
- ("h" / "d" / "w" / "m" / "y" / "") {
306
+ ( 's' / 'S' / 'm' / 'M' / 'h' / 'H' / 'd' / 'D' / 'w' / 'W' / '' ) {
201
307
  def to_s
202
308
  text_value
203
309
  end
310
+ def to_i
311
+ case text_value.downcase
312
+ when 'm' then 60
313
+ when 'h' then 60 * 60
314
+ when 'd' then 60 * 60 * 24
315
+ when 'w' then 60 * 60 * 24 * 7
316
+ else
317
+ 1
318
+ end
319
+ end
204
320
  }
205
321
  end
206
322
 
207
- rule retry
323
+ rule reretry
208
324
  time_interval comment* {
209
325
  def to_i
210
326
  time_interval.to_i
211
327
  end
328
+ def to_s
329
+ time_interval.to_s
330
+ end
212
331
  }
213
332
  end
214
333
 
@@ -217,41 +336,62 @@ grammar Zonefile
217
336
  def to_i
218
337
  time_interval.to_i
219
338
  end
339
+ def to_s
340
+ time_interval.to_s
341
+ end
220
342
  }
221
343
  end
222
344
 
223
- rule ttl
345
+ rule nxttl
224
346
  time_interval comment* {
225
347
  def to_i
226
348
  time_interval.to_i
227
349
  end
350
+ def to_s
351
+ time_interval.to_s
352
+ end
228
353
  }
229
354
  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
355
+
356
+ rule ttl
357
+ ((time_interval space) / '') {
358
+ def to_i
359
+ respond_to?(:time_interval) ? time_interval.to_i : nil
360
+ end
361
+ def to_s
362
+ respond_to?(:time_interval) ? time_interval.to_s : ''
239
363
  end
364
+ }
365
+ end
240
366
 
367
+ rule host
368
+ ( ([*a-zA-Z0-9\-\._]+) / "@" / ' ' / "\t" ) {
241
369
  def to_s
242
- case
243
- when text_value =~ /\.$/
370
+ case text_value
371
+ when /\.$/
372
+ text_value
373
+ when "@", /\s/
244
374
  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
375
  else
252
- text_value + '.' + zone.origin.to_s
376
+ text_value + '.@'
253
377
  end
254
378
  end
255
379
  }
256
380
  end
257
- end
381
+
382
+ rule data
383
+ [^;\n\r]+ {
384
+ def to_s
385
+ text_value.strip
386
+ end
387
+ }
388
+ end
389
+
390
+ rule txt_data
391
+ '"'? ('\\"' / !'"' [^\n\r])* '"'? (space '"' ('\\"' / !'"' [^\n\r])* '"')* {
392
+ def to_s
393
+ text_value
394
+ end
395
+ }
396
+ end
397
+ end