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

Source Code for Module dns.zone

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