Package dns :: Module zone
[hide private]
[frames] | no frames]

Source Code for Module dns.zone

  1  # Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc. 
  2  # 
  3  # Permission to use, copy, modify, and distribute this software and its 
  4  # documentation for any purpose with or without fee is hereby granted, 
  5  # provided that the above copyright notice and this permission notice 
  6  # appear in all copies. 
  7  # 
  8  # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 
  9  # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 
 10  # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 
 11  # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 
 12  # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
 13  # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 
 14  # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 15   
 16  """DNS Zones.""" 
 17   
 18  from __future__ import generators 
 19   
 20  import sys 
 21   
 22  import dns.exception 
 23  import dns.name 
 24  import dns.node 
 25  import dns.rdataclass 
 26  import dns.rdatatype 
 27  import dns.rdata 
 28  import dns.rrset 
 29  import dns.tokenizer 
 30  import dns.ttl 
 31   
32 -class BadZone(dns.exception.DNSException):
33 """The zone is malformed.""" 34 pass
35
36 -class NoSOA(BadZone):
37 """The zone has no SOA RR at its origin.""" 38 pass
39
40 -class NoNS(BadZone):
41 """The zone has no NS RRset at its origin.""" 42 pass
43
44 -class UnknownOrigin(BadZone):
45 """The zone's origin is unknown.""" 46 pass
47
48 -class Zone(object):
49 """A DNS zone. 50 51 A Zone is a mapping from names to nodes. The zone object may be 52 treated like a Python dictionary, e.g. zone[name] will retrieve 53 the node associated with that name. The I{name} may be a 54 dns.name.Name object, or it may be a string. In the either case, 55 if the name is relative it is treated as relative to the origin of 56 the zone. 57 58 @ivar rdclass: The zone's rdata class; the default is class IN. 59 @type rdclass: int 60 @ivar origin: The origin of the zone. 61 @type origin: dns.name.Name object 62 @ivar nodes: A dictionary mapping the names of nodes in the zone to the 63 nodes themselves. 64 @type nodes: dict 65 @ivar relativize: should names in the zone be relativized? 66 @type relativize: bool 67 @cvar node_factory: the factory used to create a new node 68 @type node_factory: class or callable 69 """ 70 71 node_factory = dns.node.Node 72 73 __slots__ = ['rdclass', 'origin', 'nodes', 'relativize'] 74
75 - def __init__(self, origin, rdclass=dns.rdataclass.IN, relativize=True):
76 """Initialize a zone object. 77 78 @param origin: The origin of the zone. 79 @type origin: dns.name.Name object 80 @param rdclass: The zone's rdata class; the default is class IN. 81 @type rdclass: int""" 82 83 self.rdclass = rdclass 84 self.origin = origin 85 self.nodes = {} 86 self.relativize = relativize
87
88 - def __eq__(self, other):
89 """Two zones are equal if they have the same origin, class, and 90 nodes. 91 @rtype: bool 92 """ 93 94 if not isinstance(other, Zone): 95 return False 96 if self.rdclass != other.rdclass or \ 97 self.origin != other.origin or \ 98 self.nodes != other.nodes: 99 return False 100 return True
101
102 - def __ne__(self, other):
103 """Are two zones not equal? 104 @rtype: bool 105 """ 106 107 return not self.__eq__(other)
108
109 - def _validate_name(self, name):
110 if isinstance(name, str): 111 name = dns.name.from_text(name, None) 112 elif not isinstance(name, dns.name.Name): 113 raise KeyError("name parameter must be convertable to a DNS name") 114 if name.is_absolute(): 115 if not name.is_subdomain(self.origin): 116 raise KeyError("name parameter must be a subdomain of the zone origin") 117 if self.relativize: 118 name = name.relativize(self.origin) 119 return name
120
121 - def __getitem__(self, key):
122 key = self._validate_name(key) 123 return self.nodes[key]
124
125 - def __setitem__(self, key, value):
126 key = self._validate_name(key) 127 self.nodes[key] = value
128
129 - def __delitem__(self, key):
130 key = self._validate_name(key) 131 del self.nodes[key]
132
133 - def __iter__(self):
134 return self.nodes.iterkeys()
135
136 - def iterkeys(self):
137 return self.nodes.iterkeys()
138
139 - def keys(self):
140 return self.nodes.keys()
141
142 - def itervalues(self):
143 return self.nodes.itervalues()
144
145 - def values(self):
146 return self.nodes.values()
147
148 - def iteritems(self):
149 return self.nodes.iteritems()
150
151 - def items(self):
152 return self.nodes.items()
153
154 - def get(self, key):
155 key = self._validate_name(key) 156 return self.nodes.get(key)
157
158 - def __contains__(self, other):
159 return other in self.nodes
160
161 - def find_node(self, name, create=False):
162 """Find a node in the zone, possibly creating it. 163 164 @param name: the name of the node to find 165 @type name: dns.name.Name object or string 166 @param create: should the node be created if it doesn't exist? 167 @type create: bool 168 @raises KeyError: the name is not known and create was not specified. 169 @rtype: dns.node.Node object 170 """ 171 172 name = self._validate_name(name) 173 node = self.nodes.get(name) 174 if node is None: 175 if not create: 176 raise KeyError 177 node = self.node_factory() 178 self.nodes[name] = node 179 return node
180
181 - def get_node(self, name, create=False):
182 """Get a node in the zone, possibly creating it. 183 184 This method is like L{find_node}, except it returns None instead 185 of raising an exception if the node does not exist and creation 186 has not been requested. 187 188 @param name: the name of the node to find 189 @type name: dns.name.Name object or string 190 @param create: should the node be created if it doesn't exist? 191 @type create: bool 192 @rtype: dns.node.Node object or None 193 """ 194 195 try: 196 node = self.find_node(name, create) 197 except KeyError: 198 node = None 199 return node
200
201 - def delete_node(self, name):
202 """Delete the specified node if it exists. 203 204 It is not an error if the node does not exist. 205 """ 206 207 name = self._validate_name(name) 208 if name in self.nodes: 209 del self.nodes[name]
210
211 - def find_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE, 212 create=False):
213 """Look for rdata with the specified name and type in the zone, 214 and return an rdataset encapsulating it. 215 216 The I{name}, I{rdtype}, and I{covers} parameters may be 217 strings, in which case they will be converted to their proper 218 type. 219 220 The rdataset returned is not a copy; changes to it will change 221 the zone. 222 223 KeyError is raised if the name or type are not found. 224 Use L{get_rdataset} if you want to have None returned instead. 225 226 @param name: the owner name to look for 227 @type name: DNS.name.Name object or string 228 @param rdtype: the rdata type desired 229 @type rdtype: int or string 230 @param covers: the covered type (defaults to None) 231 @type covers: int or string 232 @param create: should the node and rdataset be created if they do not 233 exist? 234 @type create: bool 235 @raises KeyError: the node or rdata could not be found 236 @rtype: dns.rrset.RRset object 237 """ 238 239 name = self._validate_name(name) 240 if isinstance(rdtype, str): 241 rdtype = dns.rdatatype.from_text(rdtype) 242 if isinstance(covers, str): 243 covers = dns.rdatatype.from_text(covers) 244 node = self.find_node(name, create) 245 return node.find_rdataset(self.rdclass, rdtype, covers, create)
246
247 - def get_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE, 248 create=False):
249 """Look for rdata with the specified name and type in the zone, 250 and return an rdataset encapsulating it. 251 252 The I{name}, I{rdtype}, and I{covers} parameters may be 253 strings, in which case they will be converted to their proper 254 type. 255 256 The rdataset returned is not a copy; changes to it will change 257 the zone. 258 259 None is returned if the name or type are not found. 260 Use L{find_rdataset} if you want to have KeyError raised instead. 261 262 @param name: the owner name to look for 263 @type name: DNS.name.Name object or string 264 @param rdtype: the rdata type desired 265 @type rdtype: int or string 266 @param covers: the covered type (defaults to None) 267 @type covers: int or string 268 @param create: should the node and rdataset be created if they do not 269 exist? 270 @type create: bool 271 @rtype: dns.rrset.RRset object 272 """ 273 274 try: 275 rdataset = self.find_rdataset(name, rdtype, covers, create) 276 except KeyError: 277 rdataset = None 278 return rdataset
279
280 - def delete_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE):
281 """Delete the rdataset matching I{rdtype} and I{covers}, if it 282 exists at the node specified by I{name}. 283 284 The I{name}, I{rdtype}, and I{covers} parameters may be 285 strings, in which case they will be converted to their proper 286 type. 287 288 It is not an error if the node does not exist, or if there is no 289 matching rdataset at the node. 290 291 If the node has no rdatasets after the deletion, it will itself 292 be deleted. 293 294 @param name: the owner name to look for 295 @type name: DNS.name.Name object or string 296 @param rdtype: the rdata type desired 297 @type rdtype: int or string 298 @param covers: the covered type (defaults to None) 299 @type covers: int or string 300 """ 301 302 name = self._validate_name(name) 303 if isinstance(rdtype, str): 304 rdtype = dns.rdatatype.from_text(rdtype) 305 if isinstance(covers, str): 306 covers = dns.rdatatype.from_text(covers) 307 node = self.get_node(name) 308 if not node is None: 309 node.delete_rdataset(self.rdclass, rdtype, covers) 310 if len(node) == 0: 311 self.delete_node(name)
312
313 - def replace_rdataset(self, name, replacement):
314 """Replace an rdataset at name. 315 316 It is not an error if there is no rdataset matching I{replacement}. 317 318 Ownership of the I{replacement} object is transferred to the zone; 319 in other words, this method does not store a copy of I{replacement} 320 at the node, it stores I{replacement} itself. 321 322 If the I{name} node does not exist, it is created. 323 324 @param name: the owner name 325 @type name: DNS.name.Name object or string 326 @param replacement: the replacement rdataset 327 @type replacement: dns.rdataset.Rdataset 328 """ 329 330 if replacement.rdclass != self.rdclass: 331 raise ValueError('replacement.rdclass != zone.rdclass') 332 node = self.find_node(name, True) 333 node.replace_rdataset(replacement)
334
335 - def find_rrset(self, name, rdtype, covers=dns.rdatatype.NONE):
336 """Look for rdata with the specified name and type in the zone, 337 and return an RRset encapsulating it. 338 339 The I{name}, I{rdtype}, and I{covers} parameters may be 340 strings, in which case they will be converted to their proper 341 type. 342 343 This method is less efficient than the similar 344 L{find_rdataset} because it creates an RRset instead of 345 returning the matching rdataset. It may be more convenient 346 for some uses since it returns an object which binds the owner 347 name to the rdata. 348 349 This method may not be used to create new nodes or rdatasets; 350 use L{find_rdataset} instead. 351 352 KeyError is raised if the name or type are not found. 353 Use L{get_rrset} if you want to have None returned instead. 354 355 @param name: the owner name to look for 356 @type name: DNS.name.Name object or string 357 @param rdtype: the rdata type desired 358 @type rdtype: int or string 359 @param covers: the covered type (defaults to None) 360 @type covers: int or string 361 @raises KeyError: the node or rdata could not be found 362 @rtype: dns.rrset.RRset object 363 """ 364 365 name = self._validate_name(name) 366 if isinstance(rdtype, str): 367 rdtype = dns.rdatatype.from_text(rdtype) 368 if isinstance(covers, str): 369 covers = dns.rdatatype.from_text(covers) 370 rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers) 371 rrset = dns.rrset.RRset(name, self.rdclass, rdtype, covers) 372 rrset.update(rdataset) 373 return rrset
374
375 - def get_rrset(self, name, rdtype, covers=dns.rdatatype.NONE):
376 """Look for rdata with the specified name and type in the zone, 377 and return an RRset encapsulating it. 378 379 The I{name}, I{rdtype}, and I{covers} parameters may be 380 strings, in which case they will be converted to their proper 381 type. 382 383 This method is less efficient than the similar L{get_rdataset} 384 because it creates an RRset instead of returning the matching 385 rdataset. It may be more convenient for some uses since it 386 returns an object which binds the owner name to the rdata. 387 388 This method may not be used to create new nodes or rdatasets; 389 use L{find_rdataset} instead. 390 391 None is returned if the name or type are not found. 392 Use L{find_rrset} if you want to have KeyError raised instead. 393 394 @param name: the owner name to look for 395 @type name: DNS.name.Name object or string 396 @param rdtype: the rdata type desired 397 @type rdtype: int or string 398 @param covers: the covered type (defaults to None) 399 @type covers: int or string 400 @rtype: dns.rrset.RRset object 401 """ 402 403 try: 404 rrset = self.find_rrset(name, rdtype, covers) 405 except KeyError: 406 rrset = None 407 return rrset
408
409 - def iterate_rdatasets(self, rdtype=dns.rdatatype.ANY, 410 covers=dns.rdatatype.NONE):
411 """Return a generator which yields (name, rdataset) tuples for 412 all rdatasets in the zone which have the specified I{rdtype} 413 and I{covers}. If I{rdtype} is dns.rdatatype.ANY, the default, 414 then all rdatasets will be matched. 415 416 @param rdtype: int or string 417 @type rdtype: int or string 418 @param covers: the covered type (defaults to None) 419 @type covers: int or string 420 """ 421 422 if isinstance(rdtype, str): 423 rdtype = dns.rdatatype.from_text(rdtype) 424 if isinstance(covers, str): 425 covers = dns.rdatatype.from_text(covers) 426 for (name, node) in self.items(): 427 for rds in node: 428 if rdtype == dns.rdatatype.ANY or \ 429 (rds.rdtype == rdtype and rds.covers == covers): 430 yield (name, rds)
431
432 - def iterate_rdatas(self, rdtype=dns.rdatatype.ANY, 433 covers=dns.rdatatype.NONE):
434 """Return a generator which yields (name, ttl, rdata) tuples for 435 all rdatas in the zone which have the specified I{rdtype} 436 and I{covers}. If I{rdtype} is dns.rdatatype.ANY, the default, 437 then all rdatas will be matched. 438 439 @param rdtype: int or string 440 @type rdtype: int or string 441 @param covers: the covered type (defaults to None) 442 @type covers: int or string 443 """ 444 445 if isinstance(rdtype, str): 446 rdtype = dns.rdatatype.from_text(rdtype) 447 if isinstance(covers, str): 448 covers = dns.rdatatype.from_text(covers) 449 for (name, node) in self.items(): 450 for rds in node: 451 if rdtype == dns.rdatatype.ANY or \ 452 (rds.rdtype == rdtype and rds.covers == covers): 453 for rdata in rds: 454 yield (name, rds.ttl, rdata)
455
456 - def to_file(self, f, sorted=True, relativize=True, nl=None):
457 """Write a zone to a file. 458 459 @param f: file or string. If I{f} is a string, it is treated 460 as the name of a file to open. 461 @param sorted: if True, the file will be written with the 462 names sorted in DNSSEC order from least to greatest. Otherwise 463 the names will be written in whatever order they happen to have 464 in the zone's dictionary. 465 @param relativize: if True, domain names in the output will be 466 relativized to the zone's origin (if possible). 467 @type relativize: bool 468 @param nl: The end of line string. If not specified, the 469 output will use the platform's native end-of-line marker (i.e. 470 LF on POSIX, CRLF on Windows, CR on Macintosh). 471 @type nl: string or None 472 """ 473 474 if nl is None: 475 opts = 'w' 476 else: 477 opts = 'wb' 478 if isinstance(f, str): 479 f = open(f, opts) 480 want_close = True 481 else: 482 want_close = False 483 try: 484 if sorted: 485 names = list(self.keys()) 486 names.sort() 487 else: 488 names = self.iterkeys() 489 for n in names: 490 l = self[n].to_text(n, origin=self.origin, 491 relativize=relativize) 492 if nl is None: 493 print(l, file=f) 494 else: 495 f.write(l.encode('ascii')) 496 f.write(nl.encode('ascii')) 497 finally: 498 if want_close: 499 f.close()
500
501 - def check_origin(self):
502 """Do some simple checking of the zone's origin. 503 504 @raises dns.zone.NoSOA: there is no SOA RR 505 @raises dns.zone.NoNS: there is no NS RRset 506 @raises KeyError: there is no origin node 507 """ 508 if self.relativize: 509 name = dns.name.empty 510 else: 511 name = self.origin 512 if self.get_rdataset(name, dns.rdatatype.SOA) is None: 513 raise NoSOA 514 if self.get_rdataset(name, dns.rdatatype.NS) is None: 515 raise NoNS
516 517
518 -class _MasterReader(object):
519 """Read a DNS master file 520 521 @ivar tok: The tokenizer 522 @type tok: dns.tokenizer.Tokenizer object 523 @ivar ttl: The default TTL 524 @type ttl: int 525 @ivar last_name: The last name read 526 @type last_name: dns.name.Name object 527 @ivar current_origin: The current origin 528 @type current_origin: dns.name.Name object 529 @ivar relativize: should names in the zone be relativized? 530 @type relativize: bool 531 @ivar zone: the zone 532 @type zone: dns.zone.Zone object 533 @ivar saved_state: saved reader state (used when processing $INCLUDE) 534 @type saved_state: list of (tokenizer, current_origin, last_name, file) 535 tuples. 536 @ivar current_file: the file object of the $INCLUDed file being parsed 537 (None if no $INCLUDE is active). 538 @ivar allow_include: is $INCLUDE allowed? 539 @type allow_include: bool 540 @ivar check_origin: should sanity checks of the origin node be done? 541 The default is True. 542 @type check_origin: bool 543 """ 544
545 - def __init__(self, tok, origin, rdclass, relativize, zone_factory=Zone, 546 allow_include=False, check_origin=True):
547 if isinstance(origin, str): 548 origin = dns.name.from_text(origin) 549 self.tok = tok 550 self.current_origin = origin 551 self.relativize = relativize 552 self.ttl = 0 553 self.last_name = None 554 self.zone = zone_factory(origin, rdclass, relativize=relativize) 555 self.saved_state = [] 556 self.current_file = None 557 self.allow_include = allow_include 558 self.check_origin = check_origin
559
560 - def _eat_line(self):
561 while 1: 562 token = self.tok.get() 563 if token.is_eol_or_eof(): 564 break
565
566 - def _rr_line(self):
567 """Process one line from a DNS master file.""" 568 # Name 569 if self.current_origin is None: 570 raise UnknownOrigin 571 token = self.tok.get(want_leading = True) 572 if not token.is_whitespace(): 573 self.last_name = dns.name.from_text(token.value, self.current_origin) 574 else: 575 token = self.tok.get() 576 if token.is_eol_or_eof(): 577 # treat leading WS followed by EOL/EOF as if they were EOL/EOF. 578 return 579 self.tok.unget(token) 580 name = self.last_name 581 if not name.is_subdomain(self.zone.origin): 582 self._eat_line() 583 return 584 if self.relativize: 585 name = name.relativize(self.zone.origin) 586 token = self.tok.get() 587 if not token.is_identifier(): 588 raise dns.exception.SyntaxError 589 # TTL 590 try: 591 ttl = dns.ttl.from_text(token.value) 592 token = self.tok.get() 593 if not token.is_identifier(): 594 raise dns.exception.SyntaxError 595 except dns.ttl.BadTTL: 596 ttl = self.ttl 597 # Class 598 try: 599 rdclass = dns.rdataclass.from_text(token.value) 600 token = self.tok.get() 601 if not token.is_identifier(): 602 raise dns.exception.SyntaxError 603 except dns.exception.SyntaxError: 604 raise dns.exception.SyntaxError 605 except: 606 rdclass = self.zone.rdclass 607 if rdclass != self.zone.rdclass: 608 raise dns.exception.SyntaxError("RR class is not zone's class") 609 # Type 610 try: 611 rdtype = dns.rdatatype.from_text(token.value) 612 except: 613 raise dns.exception.SyntaxError("unknown rdatatype '%s'" % token.value) 614 n = self.zone.nodes.get(name) 615 if n is None: 616 n = self.zone.node_factory() 617 self.zone.nodes[name] = n 618 try: 619 rd = dns.rdata.from_text(rdclass, rdtype, self.tok, 620 self.current_origin, False) 621 except dns.exception.SyntaxError: 622 # Catch and reraise. 623 (ty, va) = sys.exc_info()[:2] 624 raise va 625 except: 626 # All exceptions that occur in the processing of rdata 627 # are treated as syntax errors. This is not strictly 628 # correct, but it is correct almost all of the time. 629 # We convert them to syntax errors so that we can emit 630 # helpful filename:line info. 631 (ty, va) = sys.exc_info()[:2] 632 raise dns.exception.SyntaxError("caught exception %s: %s" % (str(ty), str(va))) 633 634 rd.choose_relativity(self.zone.origin, self.relativize) 635 covers = rd.covers() 636 rds = n.find_rdataset(rdclass, rdtype, covers, True) 637 rds.add(rd, ttl)
638
639 - def read(self):
640 """Read a DNS master file and build a zone object. 641 642 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin 643 @raises dns.zone.NoNS: No NS RRset was found at the zone origin 644 """ 645 646 try: 647 while 1: 648 token = self.tok.get(True, True).unescape() 649 if token.is_eof(): 650 if not self.current_file is None: 651 self.current_file.close() 652 if len(self.saved_state) > 0: 653 (self.tok, 654 self.current_origin, 655 self.last_name, 656 self.current_file, 657 self.ttl) = self.saved_state.pop(-1) 658 continue 659 break 660 elif token.is_eol(): 661 continue 662 elif token.is_comment(): 663 self.tok.get_eol() 664 continue 665 elif token.value[0] == '$': 666 u = token.value.upper() 667 if u == '$TTL': 668 token = self.tok.get() 669 if not token.is_identifier(): 670 raise dns.exception.SyntaxError("bad $TTL") 671 self.ttl = dns.ttl.from_text(token.value) 672 self.tok.get_eol() 673 elif u == '$ORIGIN': 674 self.current_origin = self.tok.get_name() 675 self.tok.get_eol() 676 if self.zone.origin is None: 677 self.zone.origin = self.current_origin 678 elif u == '$INCLUDE' and self.allow_include: 679 token = self.tok.get() 680 if not token.is_quoted_string(): 681 raise dns.exception.SyntaxError("bad filename in $INCLUDE") 682 filename = token.value 683 token = self.tok.get() 684 if token.is_identifier(): 685 new_origin = dns.name.from_text(token.value, \ 686 self.current_origin) 687 self.tok.get_eol() 688 elif not token.is_eol_or_eof(): 689 raise dns.exception.SyntaxError("bad origin in $INCLUDE") 690 else: 691 new_origin = self.current_origin 692 self.saved_state.append((self.tok, 693 self.current_origin, 694 self.last_name, 695 self.current_file, 696 self.ttl)) 697 self.current_file = open(filename, 'r') 698 self.tok = dns.tokenizer.Tokenizer(self.current_file, 699 filename) 700 self.current_origin = new_origin 701 else: 702 raise dns.exception.SyntaxError("Unknown master file directive '" + u + "'") 703 continue 704 self.tok.unget(token) 705 self._rr_line() 706 except dns.exception.SyntaxError as detail: 707 (filename, line_number) = self.tok.where() 708 if detail is None: 709 detail = "syntax error" 710 raise dns.exception.SyntaxError("%s:%d: %s" % (filename, line_number, detail)) 711 712 # Now that we're done reading, do some basic checking of the zone. 713 if self.check_origin: 714 self.zone.check_origin()
715
716 -def from_text(text, origin = None, rdclass = dns.rdataclass.IN, 717 relativize = True, zone_factory=Zone, filename=None, 718 allow_include=False, check_origin=True):
719 """Build a zone object from a master file format string. 720 721 @param text: the master file format input 722 @type text: string. 723 @param origin: The origin of the zone; if not specified, the first 724 $ORIGIN statement in the master file will determine the origin of the 725 zone. 726 @type origin: dns.name.Name object or string 727 @param rdclass: The zone's rdata class; the default is class IN. 728 @type rdclass: int 729 @param relativize: should names be relativized? The default is True 730 @type relativize: bool 731 @param zone_factory: The zone factory to use 732 @type zone_factory: function returning a Zone 733 @param filename: The filename to emit when describing where an error 734 occurred; the default is '<string>'. 735 @type filename: string 736 @param allow_include: is $INCLUDE allowed? 737 @type allow_include: bool 738 @param check_origin: should sanity checks of the origin node be done? 739 The default is True. 740 @type check_origin: bool 741 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin 742 @raises dns.zone.NoNS: No NS RRset was found at the zone origin 743 @rtype: dns.zone.Zone object 744 """ 745 746 # 'text' can also be a file, but we don't publish that fact 747 # since it's an implementation detail. The official file 748 # interface is from_file(). 749 750 if filename is None: 751 filename = '<string>' 752 tok = dns.tokenizer.Tokenizer(text, filename) 753 reader = _MasterReader(tok, origin, rdclass, relativize, zone_factory, 754 allow_include=allow_include, 755 check_origin=check_origin) 756 reader.read() 757 return reader.zone
758
759 -def from_file(f, origin = None, rdclass = dns.rdataclass.IN, 760 relativize = True, zone_factory=Zone, filename=None, 761 allow_include=True, check_origin=True):
762 """Read a master file and build a zone object. 763 764 @param f: file or string. If I{f} is a string, it is treated 765 as the name of a file to open. 766 @param origin: The origin of the zone; if not specified, the first 767 $ORIGIN statement in the master file will determine the origin of the 768 zone. 769 @type origin: dns.name.Name object or string 770 @param rdclass: The zone's rdata class; the default is class IN. 771 @type rdclass: int 772 @param relativize: should names be relativized? The default is True 773 @type relativize: bool 774 @param zone_factory: The zone factory to use 775 @type zone_factory: function returning a Zone 776 @param filename: The filename to emit when describing where an error 777 occurred; the default is '<file>', or the value of I{f} if I{f} is a 778 string. 779 @type filename: string 780 @param allow_include: is $INCLUDE allowed? 781 @type allow_include: bool 782 @param check_origin: should sanity checks of the origin node be done? 783 The default is True. 784 @type check_origin: bool 785 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin 786 @raises dns.zone.NoNS: No NS RRset was found at the zone origin 787 @rtype: dns.zone.Zone object 788 """ 789 790 if isinstance(f, str): 791 if filename is None: 792 filename = f 793 f = open(f, 'rU') 794 want_close = True 795 else: 796 if filename is None: 797 filename = '<file>' 798 want_close = False 799 800 try: 801 z = from_text(f, origin, rdclass, relativize, zone_factory, 802 filename, allow_include, check_origin) 803 finally: 804 if want_close: 805 f.close() 806 return z
807
808 -def from_xfr(xfr, zone_factory=Zone, relativize=True):
809 """Convert the output of a zone transfer generator into a zone object. 810 811 @param xfr: The xfr generator 812 @type xfr: generator of dns.message.Message objects 813 @param relativize: should names be relativized? The default is True. 814 It is essential that the relativize setting matches the one specified 815 to dns.query.xfr(). 816 @type relativize: bool 817 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin 818 @raises dns.zone.NoNS: No NS RRset was found at the zone origin 819 @rtype: dns.zone.Zone object 820 """ 821 822 z = None 823 for r in xfr: 824 if z is None: 825 if relativize: 826 origin = r.origin 827 else: 828 origin = r.answer[0].name 829 rdclass = r.answer[0].rdclass 830 z = zone_factory(origin, rdclass, relativize=relativize) 831 for rrset in r.answer: 832 znode = z.nodes.get(rrset.name) 833 if not znode: 834 znode = z.node_factory() 835 z.nodes[rrset.name] = znode 836 zrds = znode.find_rdataset(rrset.rdclass, rrset.rdtype, 837 rrset.covers, True) 838 zrds.update_ttl(rrset.ttl) 839 for rd in rrset: 840 rd.choose_relativity(z.origin, relativize) 841 zrds.add(rd) 842 z.check_origin() 843 return z
844