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 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.
@@ -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
@@ -0,0 +1,16 @@
1
+ require 'dns/zonefile_parser'
2
+
3
+ module DNS
4
+ class Zonefile
5
+ VERSION = "0.0.1"
6
+
7
+ attr_reader :origin, :soa
8
+
9
+ class << self
10
+ def parse(zone_string)
11
+ parser = DNS::ZonefileParser.new
12
+ parser.parse(zone_string)
13
+ end
14
+ end
15
+ end
16
+ end