| Class | Dnsruby::ZoneReader |
| In: |
lib/Dnsruby/zone_reader.rb
|
| Parent: | Object |
Create a new ZoneReader. The zone origin is required. If the desired SOA minimum and TTL are passed in, then they are used as default values.
# File lib/Dnsruby/zone_reader.rb, line 27
27: def initialize(origin, soa_minimum = nil, soa_ttl = nil)
28: @origin = origin.to_s
29:
30: if (!Name.create(@origin).absolute?)
31: @origin = @origin.to_s + "."
32: end
33: @soa_ttl = soa_ttl
34: if (soa_minimum && !@last_explicit_ttl)
35: @last_explicit_ttl = soa_minimum
36: else
37: @last_explicit_ttl = 0
38: end
39: @last_explicit_class = Classes.new("IN")
40: @last_name = nil
41: @continued_line = nil
42: @in_quoted_section = false
43: end
Get the TTL in seconds from the m, h, d, w format
# File lib/Dnsruby/zone_reader.rb, line 377
377: def get_ttl(ttl_text_in)
378: # If no letter afterwards, then in seconds already
379: # Could be e.g. "3d4h12m" - unclear if "4h5w" is legal - best assume it is
380: # So, search out each letter in the string, and get the number before it.
381: ttl_text = ttl_text_in.downcase
382: index = ttl_text.index(/[whdms]/)
383: if (!index)
384: return ttl_text.to_i
385: end
386: last_index = -1
387: total = 0
388: while (index)
389: letter = ttl_text[index]
390: number = ttl_text[last_index + 1, index-last_index-1].to_i
391: new_number = 0
392: case letter
393: when 115 then # "s"
394: new_number = number
395: when 109 then # "m"
396: new_number = number * 60
397: when 104 then # "h"
398: new_number = number * 3600
399: when 100 then # "d"
400: new_number = number * 86400
401: when 119 then # "w"
402: new_number = number * 604800
403: end
404: total += new_number
405:
406: last_index = index
407: index = ttl_text.index(/[whdms]/, last_index + 1)
408: end
409: return total
410: end
Take a line from the input zone file, and return the normalised form do_prefix_hack should always be false
# File lib/Dnsruby/zone_reader.rb, line 202
202: def normalise_line(line, do_prefix_hack = false)
203: # Note that a freestanding "@" is used to denote the current origin - we can simply replace that straight away
204: # Remove the ( and )
205: # Note that no domain name may be specified in the RR - in that case, last_name should be used. How do we tell? Tab or space at start of line.
206: if ((line[0,1] == " ") || (line[0,1] == "\t"))
207: line = @last_name + " " + line
208: end
209: line.chomp!
210: line.sub!(/\s+@$/, " #{@origin}") # IN CNAME @
211: line.sub!(/^@\s+/, "#{@origin} ") # IN CNAME @
212: line.sub!(/\s+@\s+/, " #{@origin} ")
213: line.strip!
214:
215:
216: # o We need to identify the domain name in the record, and then
217: split = line.split(' ') # split on whitespace
218: name = split[0].strip
219: if (name.index"\\")
220:
221: ls =[]
222: Name.create(name).labels.each {|el| ls.push(Name.decode(el.to_s))}
223: new_name = ls.join('.')
224:
225:
226: if (!(/\.\z/ =~ name))
227: new_name += "." + @origin
228: else
229: new_name += "."
230: end
231: line = new_name + " "
232: (split.length - 1).times {|i| line += "#{split[i+1]} "}
233: line += "\n"
234: name = new_name
235: split = line.split
236: # o add $ORIGIN to it if it is not absolute
237: elsif !(/\.\z/ =~ name)
238: new_name = name + "." + @origin
239: line.sub!(name, new_name)
240: name = new_name
241: split = line.split
242: end
243:
244: # If the second field is not a number, then we should add the TTL to the line
245: # Remember we can get "m" "w" "y" here! So need to check for appropriate regexp...
246: found_ttl_regexp = (split[1]=~/^[0-9]+[smhdwSMHDW]/)
247: if (found_ttl_regexp == 0)
248: # Replace the formatted ttl with an actual number
249: ttl = get_ttl(split[1])
250: line = name + " #{ttl} "
251: @last_explicit_ttl = ttl
252: (split.length - 2).times {|i| line += "#{split[i+2]} "}
253: line += "\n"
254: split = line.split
255: elsif (((split[1]).to_i == 0) && (split[1] != "0"))
256: # Add the TTL
257: if (!@last_explicit_ttl)
258: # If this is the SOA record, and no @last_explicit_ttl is defined,
259: # then we need to try the SOA TTL element from the config. Otherwise,
260: # find the SOA Minimum field, and use that.
261: # We should also generate a warning to that effect
262: # How do we know if it is an SOA record at this stage? It must be, or
263: # else @last_explicit_ttl should be defined
264: # We could put a marker in the RR for now - and replace it once we know
265: # the actual type. If the type is not SOA then, then we can raise an error
266: line = name + " %MISSING_TTL% "
267: else
268: line = name + " #{@last_explicit_ttl} "
269: end
270: (split.length - 1).times {|i| line += "#{split[i+1]} "}
271: line += "\n"
272: split = line.split
273: else
274: @last_explicit_ttl = split[1].to_i
275: end
276:
277: # Now see if the clas is included. If not, then we should default to the last class used.
278: begin
279: klass = Classes.new(split[2])
280: @last_explicit_class = klass
281: rescue ArgumentError
282: # Wasn't a CLASS
283: # So add the last explicit class in
284: line = ""
285: (2).times {|i| line += "#{split[i]} "}
286: line += " #{@last_explicit_class} "
287: (split.length - 2).times {|i| line += "#{split[i+2]} "}
288: line += "\n"
289: split = line.split
290: rescue Error => e
291: end
292:
293: # Add the type so we can load the zone one RRSet at a time.
294: type = Types.new(split[3].strip)
295: is_soa = (type == Types::SOA)
296: type_was = type
297: if (type == Types.RRSIG)
298: # If this is an RRSIG record, then add the TYPE COVERED rather than the type - this allows us to load a complete RRSet at a time
299: type = Types.new(split[4].strip)
300: end
301:
302: type_string=prefix_for_rrset_order(type, type_was)
303: @last_name = name
304:
305: if !([Types::NAPTR, Types::TXT].include?type_was)
306: line.sub!("(", "")
307: line.sub!(")", "")
308: end
309:
310: if (is_soa)
311: if (@soa_ttl)
312: # Replace the %MISSING_TTL% text with the SOA TTL from the config
313: line.sub!(" %MISSING_TTL% ", " #{@soa_ttl} ")
314: else
315: # Can we try the @last_explicit_ttl?
316: if (@last_explicit_ttl)
317: line.sub!(" %MISSING_TTL% ", " #{@last_explicit_ttl} ")
318: end
319: end
320: line = replace_soa_ttl_fields(line)
321: if (!@last_explicit_ttl)
322: soa_rr = Dnsruby::RR.create(line)
323: @last_explicit_ttl = soa_rr.minimum
324: end
325: end
326:
327: line = line.split.join(' ').strip
328: # We need to fix up any non-absolute names in the RR
329: # Some RRs have a single name, at the end of the string -
330: # to do these, we can just check the last character for "." and add the
331: # "." + origin string if necessary
332: if ([Types::MX, Types::NS, Types::AFSDB, Types::NAPTR, Types::RT,
333: Types::SRV, Types::CNAME, Types::MB, Types::MG, Types::MR,
334: Types::PTR].include?type_was)
335: # if (line[line.length-1, 1] != ".")
336: if (!(/\.\z/ =~ line))
337: line = line + "." + @origin.to_s + "."
338: end
339: end
340: # Other RRs have several names. These should be parsed by Dnsruby,
341: # and the names adjusted there.
342: if ([Types::MINFO, Types::PX, Types::RP].include?type_was)
343: parsed_rr = Dnsruby::RR.create(line)
344: case parsed_rr.type
345: when Types::MINFO
346: if (!parsed_rr.rmailbx.absolute?)
347: parsed_rr.rmailbx = parsed_rr.rmailbx.to_s + "." + @origin.to_s
348: end
349: if (!parsed_rr.emailbx.absolute?)
350: parsed_rr.emailbx = parsed_rr.emailbx.to_s + "." + @origin.to_s
351: end
352: when Types::PX
353: if (!parsed_rr.map822.absolute?)
354: parsed_rr.map822 = parsed_rr.map822.to_s + "." + @origin.to_s
355: end
356: if (!parsed_rr.mapx400.absolute?)
357: parsed_rr.mapx400 = parsed_rr.mapx400.to_s + "." + @origin.to_s
358: end
359: when Types::RP
360: if (!parsed_rr.mailbox.absolute?)
361: parsed_rr.mailbox = parsed_rr.mailbox.to_s + "." + @origin.to_s
362: if (!parsed_rr.txtdomain.absolute?)
363: parsed_rr.txtdomain = parsed_rr.txtdomain.to_s + "." + @origin.to_s
364: end
365: end
366: end
367: line = parsed_rr.to_s
368: end
369:
370: if (do_prefix_hack)
371: return line + "\n", type_string, @last_name
372: end
373: return line+"\n"
374: end
Takes a filename string and attempts to load a zone. Returns a list of RRs if successful, nil otherwise.
# File lib/Dnsruby/zone_reader.rb, line 47
47: def process_file(file)
48: line_num = 0
49: zone = nil
50: IO.foreach(file) { |line|
51: begin
52:
53: ret = process_line(line)
54: if (ret)
55: rr = RR.create(ret)
56: if (!zone)
57: zone = []
58: end
59: zone.push(rr)
60: end
61: rescue Exception => e
62: raise ParseException.new("Error reading line #{line_num} of #{file} : [#{line}]")
63: end
64: }
65: return zone
66: end
Process the next line of the file Returns a string representing the normalised line.
# File lib/Dnsruby/zone_reader.rb, line 70
70: def process_line(line, do_prefix_hack = false)
71: return nil if (line[0,1] == ";")
72: return nil if (line.strip.length == 0)
73: return nil if (!line || (line.length == 0))
74: @in_quoted_section = false if !@continued_line
75:
76: line = strip_comments(line)
77:
78: if (line.index("$ORIGIN") == 0)
79: @origin = line.split()[1].strip # $ORIGIN <domain-name> [<comment>]
80: # print "Setting $ORIGIN to #{@origin}\n"
81: return nil
82: end
83: if (line.index("$TTL") == 0)
84: @last_explicit_ttl = get_ttl(line.split()[1].strip) # $TTL <ttl>
85: # print "Setting $TTL to #{ttl}\n"
86: return nil
87: end
88: if (@continued_line)
89: # Add the next line until we see a ")"
90: # REMEMBER TO STRIP OFF COMMENTS!!!
91: @continued_line = strip_comments(@continued_line)
92: line = @continued_line.rstrip.chomp + " " + line
93: if (line.index(")"))
94: # OK
95: @continued_line = false
96: end
97: end
98: open_bracket = line.index("(")
99: if (open_bracket)
100: # Keep going until we see ")"
101: index = line.index(")")
102: if (index && (index > open_bracket))
103: # OK
104: @continued_line = false
105: else
106: @continued_line = line
107: end
108: end
109: return nil if @continued_line
110:
111: line = strip_comments(line) + "\n"
112:
113: # If SOA, then replace "3h" etc. with expanded seconds
114: # begin
115: return normalise_line(line, do_prefix_hack)
116: # rescue Exception => e
117: # print "ERROR parsing line #{@line_num} : #{line}\n"
118: # return "\n", Types::ANY
119: # end
120: end
# File lib/Dnsruby/zone_reader.rb, line 190
190: def process_quotes(section)
191: # Look through the section of text and set the @in_quoted_section
192: # as it should be at the end of the given section
193: last_index = 0
194: while (next_index = section.index("\"", last_index + 1))
195: @in_quoted_section = !@in_quoted_section
196: last_index = next_index
197: end
198: end
# File lib/Dnsruby/zone_reader.rb, line 412
412: def replace_soa_ttl_fields(line)
413: # Replace any fields which evaluate to 0
414: split = line.split
415: 4.times {|i|
416: x = i + 7
417: split[x].strip!
418: split[x] = get_ttl(split[x]).to_s
419: }
420: return split.join(" ") + "\n"
421: end
# File lib/Dnsruby/zone_reader.rb, line 122
122: def strip_comments(line)
123: last_index = 0
124: # Are we currently in a quoted section?
125: # Does a quoted section begin or end in this line?
126: # Are there any semi-colons?
127: # Ary any of the semi-colons inside a quoted section?
128: # Handle escape characters
129: if (line.index"\\")
130: return strip_comments_meticulously(line)
131: end
132: while (next_index = line.index(";", last_index + 1))
133: # Have there been any quotes since we last looked?
134: process_quotes(line[last_index, next_index - last_index])
135:
136: # Now use @in_quoted_section to work out if the ';' terminates the line
137: if (!@in_quoted_section)
138: return line[0,next_index]
139: end
140:
141: last_index = next_index
142: end
143: # Check out the quote situation to the end of the line
144: process_quotes(line[last_index, line.length-1])
145:
146: return line
147: end
# File lib/Dnsruby/zone_reader.rb, line 149
149: def strip_comments_meticulously(line)
150: # We have escape characters in the text. Go through it character by
151: # character and work out what's escaped and quoted and what's not
152: escaped = false
153: quoted = false
154: pos = 0
155: line.each_char {|c|
156: if (c == "\\")
157: if (!escaped)
158: escaped = true
159: else
160: escaped = false
161: end
162: else
163: if (escaped)
164: if (c >= "0" && c <= "9") # rfc 1035 5.1 \DDD
165: pos = pos + 2
166: end
167: escaped = false
168: next
169: else
170: if (c == "\"")
171: if (quoted)
172: quoted = false
173: else
174: quoted = true
175: end
176: else
177: if (c == ";")
178: if (!quoted)
179: return line[0, pos+1]
180: end
181: end
182: end
183: end
184: end
185: pos +=1
186: }
187: return line
188: end