dns-zonefile 0.0.1

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