Class Dnsruby::ZoneReader
In: lib/Dnsruby/zone_reader.rb
Parent: Object
Message Update ResolvError EncodeError OtherResolvError ServFail FormErr DecodeError NXRRSet YXDomain NotImp NXDomain VerifyError NotAuth YXRRSet NotZone Refused TsigError CodeMapper Types MetaTypes QTypes Nsec3HashAlgorithms Algorithms OpCode Classes ExtendedRCode Modes RCode Comparable Name RRSet TsigNotSignedResponseError Resolver SingleResolver StandardError TimeoutError ResolvTimeout DNS Dnssec Hosts RR\n[lib/Dnsruby/resource/A.rb\nlib/Dnsruby/resource/AAAA.rb\nlib/Dnsruby/resource/AFSDB.rb\nlib/Dnsruby/resource/CERT.rb\nlib/Dnsruby/resource/DHCID.rb\nlib/Dnsruby/resource/DLV.rb\nlib/Dnsruby/resource/DNSKEY.rb\nlib/Dnsruby/resource/DS.rb\nlib/Dnsruby/resource/HINFO.rb\nlib/Dnsruby/resource/HIP.rb\nlib/Dnsruby/resource/IN.rb\nlib/Dnsruby/resource/IPSECKEY.rb\nlib/Dnsruby/resource/ISDN.rb\nlib/Dnsruby/resource/KX.rb\nlib/Dnsruby/resource/LOC.rb\nlib/Dnsruby/resource/MINFO.rb\nlib/Dnsruby/resource/MX.rb\nlib/Dnsruby/resource/NAPTR.rb\nlib/Dnsruby/resource/NSAP.rb\nlib/Dnsruby/resource/NSEC.rb\nlib/Dnsruby/resource/NSEC3.rb\nlib/Dnsruby/resource/NSEC3PARAM.rb\nlib/Dnsruby/resource/OPT.rb\nlib/Dnsruby/resource/PX.rb\nlib/Dnsruby/resource/RP.rb\nlib/Dnsruby/resource/RRSIG.rb\nlib/Dnsruby/resource/RT.rb\nlib/Dnsruby/resource/SOA.rb\nlib/Dnsruby/resource/SPF.rb\nlib/Dnsruby/resource/SRV.rb\nlib/Dnsruby/resource/SSHFP.rb\nlib/Dnsruby/resource/TKEY.rb\nlib/Dnsruby/resource/TSIG.rb\nlib/Dnsruby/resource/TXT.rb\nlib/Dnsruby/resource/X25.rb\nlib/Dnsruby/resource/domain_name.rb\nlib/Dnsruby/resource/generic.rb\nlib/Dnsruby/resource/resource.rb] Recursor IPv6 IPv4 ZoneTransfer MessageDecoder MessageEncoder Question Header TheLog ValidatorThread PacketSender ResolverRuby Config KeyCache Cache SingleVerifier SelectThread Resolv ZoneReader lib/Dnsruby/DNS.rb lib/Dnsruby/dnssec.rb lib/Dnsruby/Hosts.rb lib/Dnsruby/resource/PX.rb lib/Dnsruby/Recursor.rb lib/Dnsruby/update.rb lib/Dnsruby/ipv6.rb lib/Dnsruby/ipv4.rb lib/Dnsruby/code_mapper.rb lib/Dnsruby/zone_transfer.rb lib/Dnsruby/message.rb lib/Dnsruby/TheLog.rb lib/Dnsruby/resource/resource.rb lib/Dnsruby/validator_thread.rb lib/Dnsruby/PacketSender.rb lib/Dnsruby/Resolver.rb lib/Dnsruby/Config.rb lib/Dnsruby/key_cache.rb lib/Dnsruby/Cache.rb lib/Dnsruby/single_verifier.rb lib/Dnsruby/SingleResolver.rb lib/Dnsruby/select_thread.rb lib/Dnsruby/name.rb lib/dnsruby.rb lib/Dnsruby/resource/TKEY.rb lib/Dnsruby/zone_reader.rb Dnsruby dot/m_61_0.png

Methods

Classes and Modules

Class Dnsruby::ZoneReader::ParseException

Public Class methods

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.

[Source]

    # 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

Public Instance methods

Get the TTL in seconds from the m, h, d, w format

[Source]

     # 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

[Source]

     # 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.

[Source]

    # 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.

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Validate]