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

Source Code for Module dns.zone

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