dns-zonefile 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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