1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license 2 3# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. 4# 5# Permission to use, copy, modify, and distribute this software and its 6# documentation for any purpose with or without fee is hereby granted, 7# provided that the above copyright notice and this permission notice 8# appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18"""DNS Zones.""" 19 20import contextlib 21import io 22import os 23 24import dns.exception 25import dns.name 26import dns.node 27import dns.rdataclass 28import dns.rdatatype 29import dns.rdata 30import dns.rdtypes.ANY.SOA 31import dns.rrset 32import dns.tokenizer 33import dns.transaction 34import dns.ttl 35import dns.grange 36import dns.zonefile 37 38 39class BadZone(dns.exception.DNSException): 40 41 """The DNS zone is malformed.""" 42 43 44class NoSOA(BadZone): 45 46 """The DNS zone has no SOA RR at its origin.""" 47 48 49class NoNS(BadZone): 50 51 """The DNS zone has no NS RRset at its origin.""" 52 53 54class UnknownOrigin(BadZone): 55 56 """The DNS zone's origin is unknown.""" 57 58 59class Zone(dns.transaction.TransactionManager): 60 61 """A DNS zone. 62 63 A ``Zone`` is a mapping from names to nodes. The zone object may be 64 treated like a Python dictionary, e.g. ``zone[name]`` will retrieve 65 the node associated with that name. The *name* may be a 66 ``dns.name.Name object``, or it may be a string. In either case, 67 if the name is relative it is treated as relative to the origin of 68 the zone. 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 *origin* is the origin of the zone. It may be a ``dns.name.Name``, 79 a ``str``, or ``None``. If ``None``, then the zone's origin will 80 be set by the first ``$ORIGIN`` line in a zone file. 81 82 *rdclass*, an ``int``, the zone's rdata class; the default is class IN. 83 84 *relativize*, a ``bool``, determine's whether domain names are 85 relativized to the zone's origin. The default is ``True``. 86 """ 87 88 if origin is not None: 89 if isinstance(origin, str): 90 origin = dns.name.from_text(origin) 91 elif not isinstance(origin, dns.name.Name): 92 raise ValueError("origin parameter must be convertible to a " 93 "DNS name") 94 if not origin.is_absolute(): 95 raise ValueError("origin parameter must be an absolute name") 96 self.origin = origin 97 self.rdclass = rdclass 98 self.nodes = {} 99 self.relativize = relativize 100 101 def __eq__(self, other): 102 """Two zones are equal if they have the same origin, class, and 103 nodes. 104 105 Returns a ``bool``. 106 """ 107 108 if not isinstance(other, Zone): 109 return False 110 if self.rdclass != other.rdclass or \ 111 self.origin != other.origin or \ 112 self.nodes != other.nodes: 113 return False 114 return True 115 116 def __ne__(self, other): 117 """Are two zones not equal? 118 119 Returns a ``bool``. 120 """ 121 122 return not self.__eq__(other) 123 124 def _validate_name(self, name): 125 if isinstance(name, str): 126 name = dns.name.from_text(name, None) 127 elif not isinstance(name, dns.name.Name): 128 raise KeyError("name parameter must be convertible to a DNS name") 129 if name.is_absolute(): 130 if not name.is_subdomain(self.origin): 131 raise KeyError( 132 "name parameter must be a subdomain of the zone origin") 133 if self.relativize: 134 name = name.relativize(self.origin) 135 return name 136 137 def __getitem__(self, key): 138 key = self._validate_name(key) 139 return self.nodes[key] 140 141 def __setitem__(self, key, value): 142 key = self._validate_name(key) 143 self.nodes[key] = value 144 145 def __delitem__(self, key): 146 key = self._validate_name(key) 147 del self.nodes[key] 148 149 def __iter__(self): 150 return self.nodes.__iter__() 151 152 def keys(self): 153 return self.nodes.keys() # pylint: disable=dict-keys-not-iterating 154 155 def values(self): 156 return self.nodes.values() # pylint: disable=dict-values-not-iterating 157 158 def items(self): 159 return self.nodes.items() # pylint: disable=dict-items-not-iterating 160 161 def get(self, key): 162 key = self._validate_name(key) 163 return self.nodes.get(key) 164 165 def __contains__(self, other): 166 return other in self.nodes 167 168 def find_node(self, name, create=False): 169 """Find a node in the zone, possibly creating it. 170 171 *name*: the name of the node to find. 172 The value may be a ``dns.name.Name`` or a ``str``. If absolute, the 173 name must be a subdomain of the zone's origin. If ``zone.relativize`` 174 is ``True``, then the name will be relativized. 175 176 *create*, a ``bool``. If true, the node will be created if it does 177 not exist. 178 179 Raises ``KeyError`` if the name is not known and create was 180 not specified, or if the name was not a subdomain of the origin. 181 182 Returns a ``dns.node.Node``. 183 """ 184 185 name = self._validate_name(name) 186 node = self.nodes.get(name) 187 if node is None: 188 if not create: 189 raise KeyError 190 node = self.node_factory() 191 self.nodes[name] = node 192 return node 193 194 def get_node(self, name, create=False): 195 """Get a node in the zone, possibly creating it. 196 197 This method is like ``find_node()``, except it returns None instead 198 of raising an exception if the node does not exist and creation 199 has not been requested. 200 201 *name*: the name of the node to find. 202 The value may be a ``dns.name.Name`` or a ``str``. If absolute, the 203 name must be a subdomain of the zone's origin. If ``zone.relativize`` 204 is ``True``, then the name will be relativized. 205 206 *create*, a ``bool``. If true, the node will be created if it does 207 not exist. 208 209 Raises ``KeyError`` if the name is not known and create was 210 not specified, or if the name was not a subdomain of the origin. 211 212 Returns a ``dns.node.Node`` or ``None``. 213 """ 214 215 try: 216 node = self.find_node(name, create) 217 except KeyError: 218 node = None 219 return node 220 221 def delete_node(self, name): 222 """Delete the specified node if it exists. 223 224 *name*: the name of the node to find. 225 The value may be a ``dns.name.Name`` or a ``str``. If absolute, the 226 name must be a subdomain of the zone's origin. If ``zone.relativize`` 227 is ``True``, then the name will be relativized. 228 229 It is not an error if the node does not exist. 230 """ 231 232 name = self._validate_name(name) 233 if name in self.nodes: 234 del self.nodes[name] 235 236 def find_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE, 237 create=False): 238 """Look for an rdataset with the specified name and type in the zone, 239 and return an rdataset encapsulating it. 240 241 The rdataset returned is not a copy; changes to it will change 242 the zone. 243 244 KeyError is raised if the name or type are not found. 245 246 *name*: the name of the node to find. 247 The value may be a ``dns.name.Name`` or a ``str``. If absolute, the 248 name must be a subdomain of the zone's origin. If ``zone.relativize`` 249 is ``True``, then the name will be relativized. 250 251 *rdtype*, an ``int`` or ``str``, the rdata type desired. 252 253 *covers*, an ``int`` or ``str`` or ``None``, the covered type. 254 Usually this value is ``dns.rdatatype.NONE``, but if the 255 rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, 256 then the covers value will be the rdata type the SIG/RRSIG 257 covers. The library treats the SIG and RRSIG types as if they 258 were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). 259 This makes RRSIGs much easier to work with than if RRSIGs 260 covering different rdata types were aggregated into a single 261 RRSIG rdataset. 262 263 *create*, a ``bool``. If true, the node will be created if it does 264 not exist. 265 266 Raises ``KeyError`` if the name is not known and create was 267 not specified, or if the name was not a subdomain of the origin. 268 269 Returns a ``dns.rdataset.Rdataset``. 270 """ 271 272 name = self._validate_name(name) 273 rdtype = dns.rdatatype.RdataType.make(rdtype) 274 if covers is not None: 275 covers = dns.rdatatype.RdataType.make(covers) 276 node = self.find_node(name, create) 277 return node.find_rdataset(self.rdclass, rdtype, covers, create) 278 279 def get_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE, 280 create=False): 281 """Look for an rdataset with the specified name and type in the zone. 282 283 This method is like ``find_rdataset()``, except it returns None instead 284 of raising an exception if the rdataset does not exist and creation 285 has not been requested. 286 287 The rdataset returned is not a copy; changes to it will change 288 the zone. 289 290 *name*: the name of the node to find. 291 The value may be a ``dns.name.Name`` or a ``str``. If absolute, the 292 name must be a subdomain of the zone's origin. If ``zone.relativize`` 293 is ``True``, then the name will be relativized. 294 295 *rdtype*, an ``int`` or ``str``, the rdata type desired. 296 297 *covers*, an ``int`` or ``str`` or ``None``, the covered type. 298 Usually this value is ``dns.rdatatype.NONE``, but if the 299 rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, 300 then the covers value will be the rdata type the SIG/RRSIG 301 covers. The library treats the SIG and RRSIG types as if they 302 were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). 303 This makes RRSIGs much easier to work with than if RRSIGs 304 covering different rdata types were aggregated into a single 305 RRSIG rdataset. 306 307 *create*, a ``bool``. If true, the node will be created if it does 308 not exist. 309 310 Raises ``KeyError`` if the name is not known and create was 311 not specified, or if the name was not a subdomain of the origin. 312 313 Returns a ``dns.rdataset.Rdataset`` or ``None``. 314 """ 315 316 try: 317 rdataset = self.find_rdataset(name, rdtype, covers, create) 318 except KeyError: 319 rdataset = None 320 return rdataset 321 322 def delete_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE): 323 """Delete the rdataset matching *rdtype* and *covers*, if it 324 exists at the node specified by *name*. 325 326 It is not an error if the node does not exist, or if there is no 327 matching rdataset at the node. 328 329 If the node has no rdatasets after the deletion, it will itself 330 be deleted. 331 332 *name*: the name of the node to find. 333 The value may be a ``dns.name.Name`` or a ``str``. If absolute, the 334 name must be a subdomain of the zone's origin. If ``zone.relativize`` 335 is ``True``, then the name will be relativized. 336 337 *rdtype*, an ``int`` or ``str``, the rdata type desired. 338 339 *covers*, an ``int`` or ``str`` or ``None``, the covered type. 340 Usually this value is ``dns.rdatatype.NONE``, but if the 341 rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, 342 then the covers value will be the rdata type the SIG/RRSIG 343 covers. The library treats the SIG and RRSIG types as if they 344 were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). 345 This makes RRSIGs much easier to work with than if RRSIGs 346 covering different rdata types were aggregated into a single 347 RRSIG rdataset. 348 """ 349 350 name = self._validate_name(name) 351 rdtype = dns.rdatatype.RdataType.make(rdtype) 352 if covers is not None: 353 covers = dns.rdatatype.RdataType.make(covers) 354 node = self.get_node(name) 355 if node is not None: 356 node.delete_rdataset(self.rdclass, rdtype, covers) 357 if len(node) == 0: 358 self.delete_node(name) 359 360 def replace_rdataset(self, name, replacement): 361 """Replace an rdataset at name. 362 363 It is not an error if there is no rdataset matching I{replacement}. 364 365 Ownership of the *replacement* object is transferred to the zone; 366 in other words, this method does not store a copy of *replacement* 367 at the node, it stores *replacement* itself. 368 369 If the node does not exist, it is created. 370 371 *name*: the name of the node to find. 372 The value may be a ``dns.name.Name`` or a ``str``. If absolute, the 373 name must be a subdomain of the zone's origin. If ``zone.relativize`` 374 is ``True``, then the name will be relativized. 375 376 *replacement*, a ``dns.rdataset.Rdataset``, the replacement rdataset. 377 """ 378 379 if replacement.rdclass != self.rdclass: 380 raise ValueError('replacement.rdclass != zone.rdclass') 381 node = self.find_node(name, True) 382 node.replace_rdataset(replacement) 383 384 def find_rrset(self, name, rdtype, covers=dns.rdatatype.NONE): 385 """Look for an rdataset with the specified name and type in the zone, 386 and return an RRset encapsulating it. 387 388 This method is less efficient than the similar 389 ``find_rdataset()`` because it creates an RRset instead of 390 returning the matching rdataset. It may be more convenient 391 for some uses since it returns an object which binds the owner 392 name to the rdataset. 393 394 This method may not be used to create new nodes or rdatasets; 395 use ``find_rdataset`` instead. 396 397 *name*: the name of the node to find. 398 The value may be a ``dns.name.Name`` or a ``str``. If absolute, the 399 name must be a subdomain of the zone's origin. If ``zone.relativize`` 400 is ``True``, then the name will be relativized. 401 402 *rdtype*, an ``int`` or ``str``, the rdata type desired. 403 404 *covers*, an ``int`` or ``str`` or ``None``, the covered type. 405 Usually this value is ``dns.rdatatype.NONE``, but if the 406 rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, 407 then the covers value will be the rdata type the SIG/RRSIG 408 covers. The library treats the SIG and RRSIG types as if they 409 were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). 410 This makes RRSIGs much easier to work with than if RRSIGs 411 covering different rdata types were aggregated into a single 412 RRSIG rdataset. 413 414 *create*, a ``bool``. If true, the node will be created if it does 415 not exist. 416 417 Raises ``KeyError`` if the name is not known and create was 418 not specified, or if the name was not a subdomain of the origin. 419 420 Returns a ``dns.rrset.RRset`` or ``None``. 421 """ 422 423 name = self._validate_name(name) 424 rdtype = dns.rdatatype.RdataType.make(rdtype) 425 if covers is not None: 426 covers = dns.rdatatype.RdataType.make(covers) 427 rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers) 428 rrset = dns.rrset.RRset(name, self.rdclass, rdtype, covers) 429 rrset.update(rdataset) 430 return rrset 431 432 def get_rrset(self, name, rdtype, covers=dns.rdatatype.NONE): 433 """Look for an rdataset with the specified name and type in the zone, 434 and return an RRset encapsulating it. 435 436 This method is less efficient than the similar ``get_rdataset()`` 437 because it creates an RRset instead of returning the matching 438 rdataset. It may be more convenient for some uses since it 439 returns an object which binds the owner name to the rdataset. 440 441 This method may not be used to create new nodes or rdatasets; 442 use ``get_rdataset()`` instead. 443 444 *name*: the name of the node to find. 445 The value may be a ``dns.name.Name`` or a ``str``. If absolute, the 446 name must be a subdomain of the zone's origin. If ``zone.relativize`` 447 is ``True``, then the name will be relativized. 448 449 *rdtype*, an ``int`` or ``str``, the rdata type desired. 450 451 *covers*, an ``int`` or ``str`` or ``None``, the covered type. 452 Usually this value is ``dns.rdatatype.NONE``, but if the 453 rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, 454 then the covers value will be the rdata type the SIG/RRSIG 455 covers. The library treats the SIG and RRSIG types as if they 456 were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). 457 This makes RRSIGs much easier to work with than if RRSIGs 458 covering different rdata types were aggregated into a single 459 RRSIG rdataset. 460 461 *create*, a ``bool``. If true, the node will be created if it does 462 not exist. 463 464 Raises ``KeyError`` if the name is not known and create was 465 not specified, or if the name was not a subdomain of the origin. 466 467 Returns a ``dns.rrset.RRset`` or ``None``. 468 """ 469 470 try: 471 rrset = self.find_rrset(name, rdtype, covers) 472 except KeyError: 473 rrset = None 474 return rrset 475 476 def iterate_rdatasets(self, rdtype=dns.rdatatype.ANY, 477 covers=dns.rdatatype.NONE): 478 """Return a generator which yields (name, rdataset) tuples for 479 all rdatasets in the zone which have the specified *rdtype* 480 and *covers*. If *rdtype* is ``dns.rdatatype.ANY``, the default, 481 then all rdatasets will be matched. 482 483 *rdtype*, an ``int`` or ``str``, the rdata type desired. 484 485 *covers*, an ``int`` or ``str`` or ``None``, the covered type. 486 Usually this value is ``dns.rdatatype.NONE``, but if the 487 rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, 488 then the covers value will be the rdata type the SIG/RRSIG 489 covers. The library treats the SIG and RRSIG types as if they 490 were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). 491 This makes RRSIGs much easier to work with than if RRSIGs 492 covering different rdata types were aggregated into a single 493 RRSIG rdataset. 494 """ 495 496 rdtype = dns.rdatatype.RdataType.make(rdtype) 497 if covers is not None: 498 covers = dns.rdatatype.RdataType.make(covers) 499 for (name, node) in self.items(): 500 for rds in node: 501 if rdtype == dns.rdatatype.ANY or \ 502 (rds.rdtype == rdtype and rds.covers == covers): 503 yield (name, rds) 504 505 def iterate_rdatas(self, rdtype=dns.rdatatype.ANY, 506 covers=dns.rdatatype.NONE): 507 """Return a generator which yields (name, ttl, rdata) tuples for 508 all rdatas in the zone which have the specified *rdtype* 509 and *covers*. If *rdtype* is ``dns.rdatatype.ANY``, the default, 510 then all rdatas will be matched. 511 512 *rdtype*, an ``int`` or ``str``, the rdata type desired. 513 514 *covers*, an ``int`` or ``str`` or ``None``, the covered type. 515 Usually this value is ``dns.rdatatype.NONE``, but if the 516 rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, 517 then the covers value will be the rdata type the SIG/RRSIG 518 covers. The library treats the SIG and RRSIG types as if they 519 were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). 520 This makes RRSIGs much easier to work with than if RRSIGs 521 covering different rdata types were aggregated into a single 522 RRSIG rdataset. 523 """ 524 525 rdtype = dns.rdatatype.RdataType.make(rdtype) 526 if covers is not None: 527 covers = dns.rdatatype.RdataType.make(covers) 528 for (name, node) in self.items(): 529 for rds in node: 530 if rdtype == dns.rdatatype.ANY or \ 531 (rds.rdtype == rdtype and rds.covers == covers): 532 for rdata in rds: 533 yield (name, rds.ttl, rdata) 534 535 def to_file(self, f, sorted=True, relativize=True, nl=None, 536 want_comments=False): 537 """Write a zone to a file. 538 539 *f*, a file or `str`. If *f* is a string, it is treated 540 as the name of a file to open. 541 542 *sorted*, a ``bool``. If True, the default, then the file 543 will be written with the names sorted in DNSSEC order from 544 least to greatest. Otherwise the names will be written in 545 whatever order they happen to have in the zone's dictionary. 546 547 *relativize*, a ``bool``. If True, the default, then domain 548 names in the output will be relativized to the zone's origin 549 if possible. 550 551 *nl*, a ``str`` or None. The end of line string. If not 552 ``None``, the output will use the platform's native 553 end-of-line marker (i.e. LF on POSIX, CRLF on Windows). 554 555 *want_comments*, a ``bool``. If ``True``, emit end-of-line comments 556 as part of writing the file. If ``False``, the default, do not 557 emit them. 558 """ 559 560 with contextlib.ExitStack() as stack: 561 if isinstance(f, str): 562 f = stack.enter_context(open(f, 'wb')) 563 564 # must be in this way, f.encoding may contain None, or even 565 # attribute may not be there 566 file_enc = getattr(f, 'encoding', None) 567 if file_enc is None: 568 file_enc = 'utf-8' 569 570 if nl is None: 571 # binary mode, '\n' is not enough 572 nl_b = os.linesep.encode(file_enc) 573 nl = '\n' 574 elif isinstance(nl, str): 575 nl_b = nl.encode(file_enc) 576 else: 577 nl_b = nl 578 nl = nl.decode() 579 580 if sorted: 581 names = list(self.keys()) 582 names.sort() 583 else: 584 names = self.keys() 585 for n in names: 586 l = self[n].to_text(n, origin=self.origin, 587 relativize=relativize, 588 want_comments=want_comments) 589 l_b = l.encode(file_enc) 590 591 try: 592 f.write(l_b) 593 f.write(nl_b) 594 except TypeError: # textual mode 595 f.write(l) 596 f.write(nl) 597 598 def to_text(self, sorted=True, relativize=True, nl=None, 599 want_comments=False): 600 """Return a zone's text as though it were written to a file. 601 602 *sorted*, a ``bool``. If True, the default, then the file 603 will be written with the names sorted in DNSSEC order from 604 least to greatest. Otherwise the names will be written in 605 whatever order they happen to have in the zone's dictionary. 606 607 *relativize*, a ``bool``. If True, the default, then domain 608 names in the output will be relativized to the zone's origin 609 if possible. 610 611 *nl*, a ``str`` or None. The end of line string. If not 612 ``None``, the output will use the platform's native 613 end-of-line marker (i.e. LF on POSIX, CRLF on Windows). 614 615 *want_comments*, a ``bool``. If ``True``, emit end-of-line comments 616 as part of writing the file. If ``False``, the default, do not 617 emit them. 618 619 Returns a ``str``. 620 """ 621 temp_buffer = io.StringIO() 622 self.to_file(temp_buffer, sorted, relativize, nl, want_comments) 623 return_value = temp_buffer.getvalue() 624 temp_buffer.close() 625 return return_value 626 627 def check_origin(self): 628 """Do some simple checking of the zone's origin. 629 630 Raises ``dns.zone.NoSOA`` if there is no SOA RRset. 631 632 Raises ``dns.zone.NoNS`` if there is no NS RRset. 633 634 Raises ``KeyError`` if there is no origin node. 635 """ 636 if self.relativize: 637 name = dns.name.empty 638 else: 639 name = self.origin 640 if self.get_rdataset(name, dns.rdatatype.SOA) is None: 641 raise NoSOA 642 if self.get_rdataset(name, dns.rdatatype.NS) is None: 643 raise NoNS 644 645 # TransactionManager methods 646 647 def reader(self): 648 return Transaction(self, False, True) 649 650 def writer(self, replacement=False): 651 return Transaction(self, replacement, False) 652 653 def origin_information(self): 654 if self.relativize: 655 effective = dns.name.empty 656 else: 657 effective = self.origin 658 return (self.origin, self.relativize, effective) 659 660 def get_class(self): 661 return self.rdclass 662 663 664class Transaction(dns.transaction.Transaction): 665 666 _deleted_rdataset = dns.rdataset.Rdataset(dns.rdataclass.ANY, 667 dns.rdatatype.ANY) 668 669 def __init__(self, zone, replacement, read_only): 670 super().__init__(zone, replacement, read_only) 671 self.rdatasets = {} 672 673 @property 674 def zone(self): 675 return self.manager 676 677 def _get_rdataset(self, name, rdtype, covers): 678 rdataset = self.rdatasets.get((name, rdtype, covers)) 679 if rdataset is self._deleted_rdataset: 680 return None 681 elif rdataset is None: 682 rdataset = self.zone.get_rdataset(name, rdtype, covers) 683 return rdataset 684 685 def _put_rdataset(self, name, rdataset): 686 assert not self.read_only 687 self.zone._validate_name(name) 688 self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = rdataset 689 690 def _delete_name(self, name): 691 assert not self.read_only 692 # First remove any changes involving the name 693 remove = [] 694 for key in self.rdatasets: 695 if key[0] == name: 696 remove.append(key) 697 if len(remove) > 0: 698 for key in remove: 699 del self.rdatasets[key] 700 # Next add deletion records for any rdatasets matching the 701 # name in the zone 702 node = self.zone.get_node(name) 703 if node is not None: 704 for rdataset in node.rdatasets: 705 self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = \ 706 self._deleted_rdataset 707 708 def _delete_rdataset(self, name, rdtype, covers): 709 assert not self.read_only 710 try: 711 del self.rdatasets[(name, rdtype, covers)] 712 except KeyError: 713 pass 714 rdataset = self.zone.get_rdataset(name, rdtype, covers) 715 if rdataset is not None: 716 self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = \ 717 self._deleted_rdataset 718 719 def _name_exists(self, name): 720 for key, rdataset in self.rdatasets.items(): 721 if key[0] == name: 722 if rdataset != self._deleted_rdataset: 723 return True 724 else: 725 return None 726 self.zone._validate_name(name) 727 if self.zone.get_node(name): 728 return True 729 return False 730 731 def _changed(self): 732 if self.read_only: 733 return False 734 else: 735 return len(self.rdatasets) > 0 736 737 def _end_transaction(self, commit): 738 if commit and self._changed(): 739 for (name, rdtype, covers), rdataset in \ 740 self.rdatasets.items(): 741 if rdataset is self._deleted_rdataset: 742 self.zone.delete_rdataset(name, rdtype, covers) 743 else: 744 self.zone.replace_rdataset(name, rdataset) 745 746 def _set_origin(self, origin): 747 if self.zone.origin is None: 748 self.zone.origin = origin 749 750 def _iterate_rdatasets(self): 751 # Expensive but simple! Use a versioned zone for efficient txn 752 # iteration. 753 rdatasets = {} 754 for (name, rdataset) in self.zone.iterate_rdatasets(): 755 rdatasets[(name, rdataset.rdtype, rdataset.covers)] = rdataset 756 rdatasets.update(self.rdatasets) 757 for (name, _, _), rdataset in rdatasets.items(): 758 yield (name, rdataset) 759 760 761def from_text(text, origin=None, rdclass=dns.rdataclass.IN, 762 relativize=True, zone_factory=Zone, filename=None, 763 allow_include=False, check_origin=True, idna_codec=None): 764 """Build a zone object from a zone file format string. 765 766 *text*, a ``str``, the zone file format input. 767 768 *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin 769 of the zone; if not specified, the first ``$ORIGIN`` statement in the 770 zone file will determine the origin of the zone. 771 772 *rdclass*, an ``int``, the zone's rdata class; the default is class IN. 773 774 *relativize*, a ``bool``, determine's whether domain names are 775 relativized to the zone's origin. The default is ``True``. 776 777 *zone_factory*, the zone factory to use or ``None``. If ``None``, then 778 ``dns.zone.Zone`` will be used. The value may be any class or callable 779 that returns a subclass of ``dns.zone.Zone``. 780 781 *filename*, a ``str`` or ``None``, the filename to emit when 782 describing where an error occurred; the default is ``'<string>'``. 783 784 *allow_include*, a ``bool``. If ``True``, the default, then ``$INCLUDE`` 785 directives are permitted. If ``False``, then encoutering a ``$INCLUDE`` 786 will raise a ``SyntaxError`` exception. 787 788 *check_origin*, a ``bool``. If ``True``, the default, then sanity 789 checks of the origin node will be made by calling the zone's 790 ``check_origin()`` method. 791 792 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA 793 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder 794 is used. 795 796 Raises ``dns.zone.NoSOA`` if there is no SOA RRset. 797 798 Raises ``dns.zone.NoNS`` if there is no NS RRset. 799 800 Raises ``KeyError`` if there is no origin node. 801 802 Returns a subclass of ``dns.zone.Zone``. 803 """ 804 805 # 'text' can also be a file, but we don't publish that fact 806 # since it's an implementation detail. The official file 807 # interface is from_file(). 808 809 if filename is None: 810 filename = '<string>' 811 zone = zone_factory(origin, rdclass, relativize=relativize) 812 with zone.writer(True) as txn: 813 tok = dns.tokenizer.Tokenizer(text, filename, idna_codec=idna_codec) 814 reader = dns.zonefile.Reader(tok, rdclass, txn, 815 allow_include=allow_include) 816 try: 817 reader.read() 818 except dns.zonefile.UnknownOrigin: 819 # for backwards compatibility 820 raise dns.zone.UnknownOrigin 821 # Now that we're done reading, do some basic checking of the zone. 822 if check_origin: 823 zone.check_origin() 824 return zone 825 826 827def from_file(f, origin=None, rdclass=dns.rdataclass.IN, 828 relativize=True, zone_factory=Zone, filename=None, 829 allow_include=True, check_origin=True): 830 """Read a zone file and build a zone object. 831 832 *f*, a file or ``str``. If *f* is a string, it is treated 833 as the name of a file to open. 834 835 *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin 836 of the zone; if not specified, the first ``$ORIGIN`` statement in the 837 zone file will determine the origin of the zone. 838 839 *rdclass*, an ``int``, the zone's rdata class; the default is class IN. 840 841 *relativize*, a ``bool``, determine's whether domain names are 842 relativized to the zone's origin. The default is ``True``. 843 844 *zone_factory*, the zone factory to use or ``None``. If ``None``, then 845 ``dns.zone.Zone`` will be used. The value may be any class or callable 846 that returns a subclass of ``dns.zone.Zone``. 847 848 *filename*, a ``str`` or ``None``, the filename to emit when 849 describing where an error occurred; the default is ``'<string>'``. 850 851 *allow_include*, a ``bool``. If ``True``, the default, then ``$INCLUDE`` 852 directives are permitted. If ``False``, then encoutering a ``$INCLUDE`` 853 will raise a ``SyntaxError`` exception. 854 855 *check_origin*, a ``bool``. If ``True``, the default, then sanity 856 checks of the origin node will be made by calling the zone's 857 ``check_origin()`` method. 858 859 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA 860 encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder 861 is used. 862 863 Raises ``dns.zone.NoSOA`` if there is no SOA RRset. 864 865 Raises ``dns.zone.NoNS`` if there is no NS RRset. 866 867 Raises ``KeyError`` if there is no origin node. 868 869 Returns a subclass of ``dns.zone.Zone``. 870 """ 871 872 with contextlib.ExitStack() as stack: 873 if isinstance(f, str): 874 if filename is None: 875 filename = f 876 f = stack.enter_context(open(f)) 877 return from_text(f, origin, rdclass, relativize, zone_factory, 878 filename, allow_include, check_origin) 879 880 881def from_xfr(xfr, zone_factory=Zone, relativize=True, check_origin=True): 882 """Convert the output of a zone transfer generator into a zone object. 883 884 *xfr*, a generator of ``dns.message.Message`` objects, typically 885 ``dns.query.xfr()``. 886 887 *relativize*, a ``bool``, determine's whether domain names are 888 relativized to the zone's origin. The default is ``True``. 889 It is essential that the relativize setting matches the one specified 890 to the generator. 891 892 *check_origin*, a ``bool``. If ``True``, the default, then sanity 893 checks of the origin node will be made by calling the zone's 894 ``check_origin()`` method. 895 896 Raises ``dns.zone.NoSOA`` if there is no SOA RRset. 897 898 Raises ``dns.zone.NoNS`` if there is no NS RRset. 899 900 Raises ``KeyError`` if there is no origin node. 901 902 Returns a subclass of ``dns.zone.Zone``. 903 """ 904 905 z = None 906 for r in xfr: 907 if z is None: 908 if relativize: 909 origin = r.origin 910 else: 911 origin = r.answer[0].name 912 rdclass = r.answer[0].rdclass 913 z = zone_factory(origin, rdclass, relativize=relativize) 914 for rrset in r.answer: 915 znode = z.nodes.get(rrset.name) 916 if not znode: 917 znode = z.node_factory() 918 z.nodes[rrset.name] = znode 919 zrds = znode.find_rdataset(rrset.rdclass, rrset.rdtype, 920 rrset.covers, True) 921 zrds.update_ttl(rrset.ttl) 922 for rd in rrset: 923 zrds.add(rd) 924 if check_origin: 925 z.check_origin() 926 return z 927