class Dnsruby::ZoneReader
Public Class Methods
new(origin, soa_minimum = nil, soa_ttl = nil)
click to toggle source
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 def initialize(origin, soa_minimum = nil, soa_ttl = nil) @origin = origin.to_s if (!Name.create(@origin).absolute?) @origin = @origin.to_s + "." end @soa_ttl = soa_ttl if (soa_minimum && !@last_explicit_ttl) @last_explicit_ttl = soa_minimum else @last_explicit_ttl = 0 end @last_explicit_class = Classes.new("IN") @last_name = nil @continued_line = nil @in_quoted_section = false end
Public Instance Methods
get_ttl(ttl_text_in)
click to toggle source
Get the TTL in seconds from the m, h, d, w format
# File lib/dnsruby/zone_reader.rb, line 397 def get_ttl(ttl_text_in) # If no letter afterwards, then in seconds already # Could be e.g. "3d4h12m" - unclear if "4h5w" is legal - best assume it is # So, search out each letter in the string, and get the number before it. ttl_text = ttl_text_in.downcase index = ttl_text.index(/[whdms]/) if (!index) return ttl_text.to_i end last_index = -1 total = 0 while (index) letter = ttl_text[index] number = ttl_text[last_index + 1, index-last_index-1].to_i new_number = 0 case letter when 115 then # "s" new_number = number when 109 then # "m" new_number = number * 60 when 104 then # "h" new_number = number * 3600 when 100 then # "d" new_number = number * 86400 when 119 then # "w" new_number = number * 604800 end total += new_number last_index = index index = ttl_text.index(/[whdms]/, last_index + 1) end return total end
normalise_line(line, do_prefix_hack = false)
click to toggle source
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 211 def normalise_line(line, do_prefix_hack = false) # Note that a freestanding "@" is used to denote the current origin - we can simply replace that straight away # Remove the ( and ) # 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. # If we have text in the record, then ignore that in the parsing, and stick it on again at the end stored_line = ""; if (line.index('"') != nil) stored_line = line[line.index('"'), line.length]; line = line [0, line.index('"')] end if ((line[0,1] == " ") || (line[0,1] == "\t")) line = @last_name + " " + line end line.chomp! line.sub!(/\s+@$/, " #{@origin}") # IN CNAME @ line.sub!(/^@\s+/, "#{@origin} ") # IN CNAME @ line.sub!(/\s+@\s+/, " #{@origin} ") line.strip! # o We need to identify the domain name in the record, and then split = line.split(' ') # split on whitespace name = split[0].strip if (name.index"\\") ls =[] Name.create(name).labels.each {|el| ls.push(Name.decode(el.to_s))} new_name = ls.join('.') if (!(/\.\z/ =~ name)) new_name += "." + @origin else new_name += "." end line = new_name + " " (split.length - 1).times {|i| line += "#{split[i+1]} "} line += "\n" name = new_name split = line.split # o add $ORIGIN to it if it is not absolute elsif !(/\.\z/ =~ name) new_name = name + "." + @origin line.sub!(name, new_name) name = new_name split = line.split end # If the second field is not a number, then we should add the TTL to the line # Remember we can get "m" "w" "y" here! So need to check for appropriate regexp... found_ttl_regexp = (split[1]=~/^[0-9]+[smhdwSMHDW]/) if (found_ttl_regexp == 0) # Replace the formatted ttl with an actual number ttl = get_ttl(split[1]) line = name + " #{ttl} " @last_explicit_ttl = ttl (split.length - 2).times {|i| line += "#{split[i+2]} "} line += "\n" split = line.split elsif (((split[1]).to_i == 0) && (split[1] != "0")) # Add the TTL if (!@last_explicit_ttl) # If this is the SOA record, and no @last_explicit_ttl is defined, # then we need to try the SOA TTL element from the config. Otherwise, # find the SOA Minimum field, and use that. # We should also generate a warning to that effect # How do we know if it is an SOA record at this stage? It must be, or # else @last_explicit_ttl should be defined # We could put a marker in the RR for now - and replace it once we know # the actual type. If the type is not SOA then, then we can raise an error line = name + " %MISSING_TTL% " else line = name + " #{@last_explicit_ttl} " end (split.length - 1).times {|i| line += "#{split[i+1]} "} line += "\n" split = line.split else @last_explicit_ttl = split[1].to_i end # Now see if the clas is included. If not, then we should default to the last class used. begin klass = Classes.new(split[2]) @last_explicit_class = klass rescue ArgumentError # Wasn't a CLASS # So add the last explicit class in line = "" (2).times {|i| line += "#{split[i]} "} line += " #{@last_explicit_class} " (split.length - 2).times {|i| line += "#{split[i+2]} "} line += "\n" split = line.split rescue Error end # Add the type so we can load the zone one RRSet at a time. type = Types.new(split[3].strip) is_soa = (type == Types::SOA) type_was = type if (type == Types.RRSIG) # 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 type = Types.new(split[4].strip) end type_string=prefix_for_rrset_order(type, type_was) @last_name = name if !([Types::NAPTR, Types::TXT].include?type_was) line.sub!("(", "") line.sub!(")", "") end if (is_soa) if (@soa_ttl) # Replace the %MISSING_TTL% text with the SOA TTL from the config line.sub!(" %MISSING_TTL% ", " #{@soa_ttl} ") else # Can we try the @last_explicit_ttl? if (@last_explicit_ttl) line.sub!(" %MISSING_TTL% ", " #{@last_explicit_ttl} ") end end line = replace_soa_ttl_fields(line) if (!@last_explicit_ttl) soa_rr = Dnsruby::RR.create(line) @last_explicit_ttl = soa_rr.minimum end end line = line.strip if (stored_line && stored_line != "") line += " " + stored_line.strip end # We need to fix up any non-absolute names in the RR # Some RRs have a single name, at the end of the string - # to do these, we can just check the last character for "." and add the # "." + origin string if necessary if ([Types::MX, Types::NS, Types::AFSDB, Types::NAPTR, Types::RT, Types::SRV, Types::CNAME, Types::MB, Types::MG, Types::MR, Types::PTR, Types::DNAME].include?type_was) # if (line[line.length-1, 1] != ".") if (!(/\.\z/ =~ line)) line = line + "." + @origin.to_s end end # Other RRs have several names. These should be parsed by Dnsruby, # and the names adjusted there. if ([Types::MINFO, Types::PX, Types::RP].include?type_was) parsed_rr = Dnsruby::RR.create(line) case parsed_rr.type when Types::MINFO if (!parsed_rr.rmailbx.absolute?) parsed_rr.rmailbx = parsed_rr.rmailbx.to_s + "." + @origin.to_s end if (!parsed_rr.emailbx.absolute?) parsed_rr.emailbx = parsed_rr.emailbx.to_s + "." + @origin.to_s end when Types::PX if (!parsed_rr.map822.absolute?) parsed_rr.map822 = parsed_rr.map822.to_s + "." + @origin.to_s end if (!parsed_rr.mapx400.absolute?) parsed_rr.mapx400 = parsed_rr.mapx400.to_s + "." + @origin.to_s end when Types::RP if (!parsed_rr.mailbox.absolute?) parsed_rr.mailbox = parsed_rr.mailbox.to_s + "." + @origin.to_s end if (!parsed_rr.txtdomain.absolute?) parsed_rr.txtdomain = parsed_rr.txtdomain.to_s + "." + @origin.to_s end end line = parsed_rr.to_s end if (do_prefix_hack) return line + "\n", type_string, @last_name end return line+"\n" end
process_file(source)
click to toggle source
Takes a filename string, or any type of IO object, and attempts to load a zone. Returns a list of RRs if successful, nil otherwise.
# File lib/dnsruby/zone_reader.rb, line 47 def process_file(source) if source.is_a?(String) File.open(source) do |file| process_io(file) end else process_io(source) end end
process_io(io)
click to toggle source
Iterate over each line in a IO object, and process it. Returns a list of RRs if successful, nil otherwise.
# File lib/dnsruby/zone_reader.rb, line 59 def process_io(io) zone = nil io.each do |line| begin ret = process_line(line) if (ret) rr = RR.create(ret) if (!zone) zone = [] end zone.push(rr) end rescue Exception raise ParseException.new("Error reading line #{io.lineno} of #{io.inspect} : [#{line}]") end end return zone end
process_line(line, do_prefix_hack = false)
click to toggle source
Process the next line of the file Returns a string representing the normalised line.
# File lib/dnsruby/zone_reader.rb, line 80 def process_line(line, do_prefix_hack = false) return nil if (line[0,1] == ";") line = strip_comments(line) return nil if (line.strip.length == 0) return nil if (!line || (line.length == 0)) @in_quoted_section = false if !@continued_line if (line.index("$ORIGIN") == 0) @origin = line.split()[1].strip # $ORIGIN <domain-name> [<comment>] # print "Setting $ORIGIN to #{@origin}\n" return nil end if (line.index("$TTL") == 0) @last_explicit_ttl = get_ttl(line.split()[1].strip) # $TTL <ttl> # print "Setting $TTL to #{ttl}\n" return nil end if (@continued_line) # Add the next line until we see a ")" # REMEMBER TO STRIP OFF COMMENTS!!! @continued_line = strip_comments(@continued_line) line = @continued_line.rstrip.chomp + " " + line if (line.index(")")) # OK @continued_line = false end end open_bracket = line.index("(") if (open_bracket) # Keep going until we see ")" index = line.index(")") if (index && (index > open_bracket)) # OK @continued_line = false else @continued_line = line end end return nil if @continued_line line = strip_comments(line) + "\n" # If SOA, then replace "3h" etc. with expanded seconds # begin return normalise_line(line, do_prefix_hack) # rescue Exception => e # print "ERROR parsing line #{@line_num} : #{line}\n" # return "\n", Types::ANY # end end
process_quotes(section)
click to toggle source
# File lib/dnsruby/zone_reader.rb, line 199 def process_quotes(section) # Look through the section of text and set the @in_quoted_section # as it should be at the end of the given section last_index = 0 while (next_index = section.index("\"", last_index + 1)) @in_quoted_section = !@in_quoted_section last_index = next_index end end
replace_soa_ttl_fields(line)
click to toggle source
# File lib/dnsruby/zone_reader.rb, line 432 def replace_soa_ttl_fields(line) # Replace any fields which evaluate to 0 split = line.split 4.times {|i| x = i + 7 split[x].strip! split[x] = get_ttl(split[x]).to_s } return split.join(" ") + "\n" end
strip_comments(line)
click to toggle source
# File lib/dnsruby/zone_reader.rb, line 131 def strip_comments(line) last_index = 0 # Are we currently in a quoted section? # Does a quoted section begin or end in this line? # Are there any semi-colons? # Ary any of the semi-colons inside a quoted section? # Handle escape characters if (line.index"\\") return strip_comments_meticulously(line) end while (next_index = line.index(";", last_index + 1)) # Have there been any quotes since we last looked? process_quotes(line[last_index, next_index - last_index]) # Now use @in_quoted_section to work out if the ';' terminates the line if (!@in_quoted_section) return line[0,next_index] end last_index = next_index end # Check out the quote situation to the end of the line process_quotes(line[last_index, line.length-1]) return line end
strip_comments_meticulously(line)
click to toggle source
# File lib/dnsruby/zone_reader.rb, line 158 def strip_comments_meticulously(line) # We have escape characters in the text. Go through it character by # character and work out what's escaped and quoted and what's not escaped = false quoted = false pos = 0 line.each_char {|c| if (c == "\\") if (!escaped) escaped = true else escaped = false end else if (escaped) if (c >= "0" && c <= "9") # rfc 1035 5.1 \DDD pos = pos + 2 end escaped = false next else if (c == "\"") if (quoted) quoted = false else quoted = true end else if (c == ";") if (!quoted) return line[0, pos+1] end end end end end pos +=1 } return line end