zonesync 0.6.1 → 0.8.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,323 @@
1
+ require "treetop"
2
+ Treetop.load File.join(__dir__, "zonefile")
3
+
4
+ module Zonesync
5
+ module Zonefile
6
+ class << self
7
+ def parse(zone_string)
8
+ parser = ZonefileParser.new
9
+ result = parser.parse(zone_string)
10
+ return result if result
11
+ puts zone_string
12
+ raise ParsingError, parser.failure_reason
13
+ end
14
+
15
+ def load(zone_string)
16
+ parsed = parse(zone_string)
17
+ Zone.new(parsed.entries,
18
+ origin: parsed.variables["ORIGIN"],
19
+ )
20
+ end
21
+ end
22
+
23
+ class ParsingError < RuntimeError; end
24
+ class UnknownRecordType < RuntimeError; end
25
+ class Zone
26
+ attr_reader :origin
27
+ attr_reader :records
28
+
29
+ def initialize(entries, origin: nil, alternate_origin: ".")
30
+ @origin = origin
31
+ @records = []
32
+ @vars = {"origin" => alternate_origin, :last_host => "."}
33
+ entries.each do |e|
34
+ case e.parse_type
35
+ when :variable
36
+ key = e.name.text_value.downcase
37
+ @vars[key] = case key
38
+ when "ttl"
39
+ e.value.text_value.to_i
40
+ else
41
+ e.value.text_value
42
+ end
43
+ when :soa
44
+ @records << SOA.new(@vars, e)
45
+ when :record
46
+ case e.record_type
47
+ when "A" then @records << A.new(@vars, e)
48
+ when "AAAA" then @records << AAAA.new(@vars, e)
49
+ when "CAA" then @records << CAA.new(@vars, e)
50
+ when "CNAME" then @records << CNAME.new(@vars, e)
51
+ when "MX" then @records << MX.new(@vars, e)
52
+ when "NAPTR" then @records << NAPTR.new(@vars, e)
53
+ when "NS" then @records << NS.new(@vars, e)
54
+ when "PTR" then @records << PTR.new(@vars, e)
55
+ when "SRV" then @records << SRV.new(@vars, e)
56
+ when "SPF" then @records << SPF.new(@vars, e)
57
+ when "SSHFP" then @records << SSHFP.new(@vars, e)
58
+ when "TXT" then @records << TXT.new(@vars, e)
59
+ when "SOA" then
60
+ # No-op
61
+ else
62
+ raise UnknownRecordType, "Unknown record type: #{e.record_type}"
63
+ end
64
+ end
65
+ end
66
+ @origin ||= soa.origin
67
+ end
68
+
69
+ def soa
70
+ records_of(SOA).first
71
+ end
72
+
73
+ def records_of(kl)
74
+ @records.select { |r| r.instance_of? kl }
75
+ end
76
+ end
77
+
78
+ class Record
79
+ # assign, with handling for global TTL
80
+ def self.writer_for_ttl(*attribs)
81
+ attribs.each do |attrib|
82
+ define_method "#{attrib}=" do |val|
83
+ instance_variable_set("@#{attrib}", val || @vars["ttl"])
84
+ end
85
+ end
86
+ end
87
+
88
+ attr_reader :ttl
89
+ attr_writer :klass
90
+ writer_for_ttl :ttl
91
+
92
+ def klass
93
+ @klass = nil if @klass == ""
94
+ @klass ||= "IN"
95
+ end
96
+
97
+ attr_accessor :comment
98
+
99
+ private
100
+
101
+ def qualify_host(host)
102
+ origin = vars["origin"]
103
+ host = vars[:last_host] if /^\s*$/.match?(host)
104
+ host = host.gsub(/@/, origin)
105
+ if /\.$/.match?(host)
106
+ host
107
+ elsif /^\./.match?(origin)
108
+ host + origin
109
+ else
110
+ host + "." + origin
111
+ end
112
+ end
113
+ attr_accessor :vars
114
+ end
115
+
116
+ class SOA < Record
117
+ attr_accessor :origin, :nameserver, :responsible_party, :serial, :refresh_time, :retry_time, :expiry_time, :nxttl
118
+
119
+ def initialize(vars, zonefile_soa = nil)
120
+ @vars = vars
121
+ if zonefile_soa
122
+ self.origin = qualify_host(zonefile_soa.origin.to_s)
123
+ @vars[:last_host] = origin
124
+ self.ttl = zonefile_soa.ttl.to_i
125
+ self.klass = zonefile_soa.klass.to_s
126
+ self.nameserver = qualify_host(zonefile_soa.ns.to_s)
127
+ self.responsible_party = qualify_host(zonefile_soa.rp.to_s)
128
+ self.serial = zonefile_soa.serial.to_i
129
+ self.refresh_time = zonefile_soa.refresh.to_i
130
+ self.retry_time = zonefile_soa.reretry.to_i
131
+ self.expiry_time = zonefile_soa.expiry.to_i
132
+ self.nxttl = zonefile_soa.nxttl.to_i
133
+ end
134
+ end
135
+ end
136
+
137
+ class A < Record
138
+ attr_accessor :host, :address
139
+
140
+ def initialize(vars, zonefile_record)
141
+ @vars = vars
142
+ if zonefile_record
143
+ self.host = qualify_host(zonefile_record.host.to_s)
144
+ @vars[:last_host] = host
145
+ self.ttl = zonefile_record.ttl.to_i
146
+ self.klass = zonefile_record.klass.to_s
147
+ self.address = zonefile_record.ip_address.to_s
148
+ self.comment = zonefile_record.comment&.to_s
149
+ end
150
+ end
151
+ end
152
+
153
+ class AAAA < A
154
+ end
155
+
156
+ class CAA < Record
157
+ attr_accessor :host, :flags, :tag, :value
158
+
159
+ def initialize(vars, zonefile_record)
160
+ @vars = vars
161
+ if zonefile_record
162
+ self.host = qualify_host(zonefile_record.host.to_s)
163
+ @vars[:last_host] = host
164
+ self.ttl = zonefile_record.ttl.to_i
165
+ self.klass = zonefile_record.klass.to_s
166
+ self.flags = zonefile_record.flags.to_i
167
+ self.tag = zonefile_record.tag.to_s
168
+ self.value = zonefile_record.value.to_s
169
+ self.comment = zonefile_record.comment&.to_s
170
+ end
171
+ end
172
+ end
173
+
174
+ class CNAME < Record
175
+ attr_accessor :host, :domainname
176
+
177
+ def initialize(vars, zonefile_record)
178
+ @vars = vars
179
+ if zonefile_record
180
+ self.host = qualify_host(zonefile_record.host.to_s)
181
+ @vars[:last_host] = host
182
+ self.ttl = zonefile_record.ttl.to_i
183
+ self.klass = zonefile_record.klass.to_s
184
+ self.domainname = qualify_host(zonefile_record.target.to_s)
185
+ self.comment = zonefile_record.comment&.to_s
186
+ end
187
+ end
188
+
189
+ alias target domainname
190
+ alias alias host
191
+ end
192
+
193
+ class MX < Record
194
+ attr_accessor :host, :priority, :domainname
195
+
196
+ def initialize(vars, zonefile_record)
197
+ @vars = vars
198
+ if zonefile_record
199
+ self.host = qualify_host(zonefile_record.host.to_s)
200
+ @vars[:last_host] = host
201
+ self.ttl = zonefile_record.ttl.to_i
202
+ self.klass = zonefile_record.klass.to_s
203
+ self.priority = zonefile_record.priority.to_i
204
+ self.domainname = qualify_host(zonefile_record.exchanger.to_s)
205
+ self.comment = zonefile_record.comment&.to_s
206
+ end
207
+ end
208
+
209
+ alias exchange domainname
210
+ alias exchanger domainname
211
+ end
212
+
213
+ class NAPTR < Record
214
+ attr_accessor :host, :data
215
+
216
+ def initialize(vars, zonefile_record)
217
+ @vars = vars
218
+ if zonefile_record
219
+ self.host = qualify_host(zonefile_record.host.to_s)
220
+ @vars[:last_host] = host
221
+ self.ttl = zonefile_record.ttl.to_i
222
+ self.klass = zonefile_record.klass.to_s
223
+ self.data = zonefile_record.data.to_s
224
+ self.comment = zonefile_record.comment&.to_s
225
+ end
226
+ end
227
+ end
228
+
229
+ class NS < Record
230
+ attr_accessor :host, :domainname
231
+
232
+ def initialize(vars, zonefile_record)
233
+ @vars = vars
234
+ if zonefile_record
235
+ self.host = qualify_host(zonefile_record.host.to_s)
236
+ @vars[:last_host] = host
237
+ self.ttl = zonefile_record.ttl.to_i
238
+ self.klass = zonefile_record.klass.to_s
239
+ self.domainname = qualify_host(zonefile_record.nameserver.to_s)
240
+ self.comment = zonefile_record.comment&.to_s
241
+ end
242
+ end
243
+
244
+ alias nameserver domainname
245
+ end
246
+
247
+ class PTR < Record
248
+ attr_accessor :host, :domainname
249
+
250
+ def initialize(vars, zonefile_record)
251
+ @vars = vars
252
+ if zonefile_record
253
+ self.host = qualify_host(zonefile_record.host.to_s)
254
+ @vars[:last_host] = host
255
+ self.ttl = zonefile_record.ttl.to_i
256
+ self.klass = zonefile_record.klass.to_s
257
+ self.domainname = qualify_host(zonefile_record.target.to_s)
258
+ self.comment = zonefile_record.comment&.to_s
259
+ end
260
+ end
261
+
262
+ alias target domainname
263
+ end
264
+
265
+ class SRV < Record
266
+ attr_accessor :host, :priority, :weight, :port, :domainname
267
+
268
+ def initialize(vars, zonefile_record)
269
+ @vars = vars
270
+ if zonefile_record
271
+ self.host = qualify_host(zonefile_record.host.to_s)
272
+ @vars[:last_host] = host
273
+ self.ttl = zonefile_record.ttl.to_i
274
+ self.klass = zonefile_record.klass.to_s
275
+ self.priority = zonefile_record.priority.to_i
276
+ self.weight = zonefile_record.weight.to_i
277
+ self.port = zonefile_record.port.to_i
278
+ self.domainname = qualify_host(zonefile_record.target.to_s)
279
+ self.comment = zonefile_record.comment&.to_s
280
+ end
281
+ end
282
+
283
+ alias target domainname
284
+ end
285
+
286
+ class SSHFP < Record
287
+ attr_accessor :host, :alg, :fptype, :fp
288
+
289
+ def initialize(vars, zonefile_record)
290
+ @vars = vars
291
+ if zonefile_record
292
+ self.host = qualify_host(zonefile_record.host.to_s)
293
+ @vars[:last_host] = host
294
+ self.ttl = zonefile_record.ttl.to_i
295
+ self.klass = zonefile_record.klass.to_s
296
+ self.alg = zonefile_record.alg.to_i
297
+ self.fptype = zonefile_record.fptype.to_i
298
+ self.fp = zonefile_record.fp.to_s
299
+ self.comment = zonefile_record.comment&.to_s
300
+ end
301
+ end
302
+ end
303
+
304
+ class TXT < Record
305
+ attr_accessor :host, :data
306
+
307
+ def initialize(vars, zonefile_record)
308
+ @vars = vars
309
+ if zonefile_record
310
+ self.host = qualify_host(zonefile_record.host.to_s)
311
+ @vars[:last_host] = host
312
+ self.ttl = zonefile_record.ttl.to_i
313
+ self.klass = zonefile_record.klass.to_s
314
+ self.data = zonefile_record.data.to_s
315
+ self.comment = zonefile_record.comment&.to_s
316
+ end
317
+ end
318
+ end
319
+
320
+ class SPF < TXT
321
+ end
322
+ end
323
+ end