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