1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """DNS Zones."""
17
18 from __future__ import generators
19
20 import sys
21 import re
22 from io import BytesIO
23
24 import dns.exception
25 import dns.name
26 import dns.node
27 import dns.rdataclass
28 import dns.rdatatype
29 import dns.rdata
30 import dns.rrset
31 import dns.tokenizer
32 import dns.ttl
33 import dns.grange
34 from ._compat import string_types, text_type
35
36
37 -class BadZone(dns.exception.DNSException):
38
39 """The DNS zone is malformed."""
40
41
43
44 """The DNS zone has no SOA RR at its origin."""
45
46
48
49 """The DNS zone has no NS RRset at its origin."""
50
51
53
54 """The DNS zone's origin is unknown."""
55
56
58
59 """A DNS zone.
60
61 A Zone is a mapping from names to nodes. The zone object may be
62 treated like a Python dictionary, e.g. zone[name] will retrieve
63 the node associated with that name. The I{name} may be a
64 dns.name.Name object, or it may be a string. In the either case,
65 if the name is relative it is treated as relative to the origin of
66 the zone.
67
68 @ivar rdclass: The zone's rdata class; the default is class IN.
69 @type rdclass: int
70 @ivar origin: The origin of the zone.
71 @type origin: dns.name.Name object
72 @ivar nodes: A dictionary mapping the names of nodes in the zone to the
73 nodes themselves.
74 @type nodes: dict
75 @ivar relativize: should names in the zone be relativized?
76 @type relativize: bool
77 @cvar node_factory: the factory used to create a new node
78 @type node_factory: class or callable
79 """
80
81 node_factory = dns.node.Node
82
83 __slots__ = ['rdclass', 'origin', 'nodes', 'relativize']
84
105
107 """Two zones are equal if they have the same origin, class, and
108 nodes.
109 @rtype: bool
110 """
111
112 if not isinstance(other, Zone):
113 return False
114 if self.rdclass != other.rdclass or \
115 self.origin != other.origin or \
116 self.nodes != other.nodes:
117 return False
118 return True
119
121 """Are two zones not equal?
122 @rtype: bool
123 """
124
125 return not self.__eq__(other)
126
139
143
147
151
154
157
160
163
166
169
170 iteritems = items
171
172 - def get(self, key):
175
177 return other in self.nodes
178
180 """Find a node in the zone, possibly creating it.
181
182 @param name: the name of the node to find
183 @type name: dns.name.Name object or string
184 @param create: should the node be created if it doesn't exist?
185 @type create: bool
186 @raises KeyError: the name is not known and create was not specified.
187 @rtype: dns.node.Node object
188 """
189
190 name = self._validate_name(name)
191 node = self.nodes.get(name)
192 if node is None:
193 if not create:
194 raise KeyError
195 node = self.node_factory()
196 self.nodes[name] = node
197 return node
198
199 - def get_node(self, name, create=False):
200 """Get a node in the zone, possibly creating it.
201
202 This method is like L{find_node}, except it returns None instead
203 of raising an exception if the node does not exist and creation
204 has not been requested.
205
206 @param name: the name of the node to find
207 @type name: dns.name.Name object or string
208 @param create: should the node be created if it doesn't exist?
209 @type create: bool
210 @rtype: dns.node.Node object or None
211 """
212
213 try:
214 node = self.find_node(name, create)
215 except KeyError:
216 node = None
217 return node
218
220 """Delete the specified node if it exists.
221
222 It is not an error if the node does not exist.
223 """
224
225 name = self._validate_name(name)
226 if name in self.nodes:
227 del self.nodes[name]
228
231 """Look for rdata with the specified name and type in the zone,
232 and return an rdataset encapsulating it.
233
234 The I{name}, I{rdtype}, and I{covers} parameters may be
235 strings, in which case they will be converted to their proper
236 type.
237
238 The rdataset returned is not a copy; changes to it will change
239 the zone.
240
241 KeyError is raised if the name or type are not found.
242 Use L{get_rdataset} if you want to have None returned instead.
243
244 @param name: the owner name to look for
245 @type name: DNS.name.Name object or string
246 @param rdtype: the rdata type desired
247 @type rdtype: int or string
248 @param covers: the covered type (defaults to None)
249 @type covers: int or string
250 @param create: should the node and rdataset be created if they do not
251 exist?
252 @type create: bool
253 @raises KeyError: the node or rdata could not be found
254 @rtype: dns.rrset.RRset object
255 """
256
257 name = self._validate_name(name)
258 if isinstance(rdtype, string_types):
259 rdtype = dns.rdatatype.from_text(rdtype)
260 if isinstance(covers, string_types):
261 covers = dns.rdatatype.from_text(covers)
262 node = self.find_node(name, create)
263 return node.find_rdataset(self.rdclass, rdtype, covers, create)
264
267 """Look for rdata with the specified name and type in the zone,
268 and return an rdataset encapsulating it.
269
270 The I{name}, I{rdtype}, and I{covers} parameters may be
271 strings, in which case they will be converted to their proper
272 type.
273
274 The rdataset returned is not a copy; changes to it will change
275 the zone.
276
277 None is returned if the name or type are not found.
278 Use L{find_rdataset} if you want to have KeyError raised instead.
279
280 @param name: the owner name to look for
281 @type name: DNS.name.Name object or string
282 @param rdtype: the rdata type desired
283 @type rdtype: int or string
284 @param covers: the covered type (defaults to None)
285 @type covers: int or string
286 @param create: should the node and rdataset be created if they do not
287 exist?
288 @type create: bool
289 @rtype: dns.rrset.RRset object
290 """
291
292 try:
293 rdataset = self.find_rdataset(name, rdtype, covers, create)
294 except KeyError:
295 rdataset = None
296 return rdataset
297
299 """Delete the rdataset matching I{rdtype} and I{covers}, if it
300 exists at the node specified by I{name}.
301
302 The I{name}, I{rdtype}, and I{covers} parameters may be
303 strings, in which case they will be converted to their proper
304 type.
305
306 It is not an error if the node does not exist, or if there is no
307 matching rdataset at the node.
308
309 If the node has no rdatasets after the deletion, it will itself
310 be deleted.
311
312 @param name: the owner name to look for
313 @type name: DNS.name.Name object or string
314 @param rdtype: the rdata type desired
315 @type rdtype: int or string
316 @param covers: the covered type (defaults to None)
317 @type covers: int or string
318 """
319
320 name = self._validate_name(name)
321 if isinstance(rdtype, string_types):
322 rdtype = dns.rdatatype.from_text(rdtype)
323 if isinstance(covers, string_types):
324 covers = dns.rdatatype.from_text(covers)
325 node = self.get_node(name)
326 if node is not None:
327 node.delete_rdataset(self.rdclass, rdtype, covers)
328 if len(node) == 0:
329 self.delete_node(name)
330
332 """Replace an rdataset at name.
333
334 It is not an error if there is no rdataset matching I{replacement}.
335
336 Ownership of the I{replacement} object is transferred to the zone;
337 in other words, this method does not store a copy of I{replacement}
338 at the node, it stores I{replacement} itself.
339
340 If the I{name} node does not exist, it is created.
341
342 @param name: the owner name
343 @type name: DNS.name.Name object or string
344 @param replacement: the replacement rdataset
345 @type replacement: dns.rdataset.Rdataset
346 """
347
348 if replacement.rdclass != self.rdclass:
349 raise ValueError('replacement.rdclass != zone.rdclass')
350 node = self.find_node(name, True)
351 node.replace_rdataset(replacement)
352
354 """Look for rdata with the specified name and type in the zone,
355 and return an RRset encapsulating it.
356
357 The I{name}, I{rdtype}, and I{covers} parameters may be
358 strings, in which case they will be converted to their proper
359 type.
360
361 This method is less efficient than the similar
362 L{find_rdataset} because it creates an RRset instead of
363 returning the matching rdataset. It may be more convenient
364 for some uses since it returns an object which binds the owner
365 name to the rdata.
366
367 This method may not be used to create new nodes or rdatasets;
368 use L{find_rdataset} instead.
369
370 KeyError is raised if the name or type are not found.
371 Use L{get_rrset} if you want to have None returned instead.
372
373 @param name: the owner name to look for
374 @type name: DNS.name.Name object or string
375 @param rdtype: the rdata type desired
376 @type rdtype: int or string
377 @param covers: the covered type (defaults to None)
378 @type covers: int or string
379 @raises KeyError: the node or rdata could not be found
380 @rtype: dns.rrset.RRset object
381 """
382
383 name = self._validate_name(name)
384 if isinstance(rdtype, string_types):
385 rdtype = dns.rdatatype.from_text(rdtype)
386 if isinstance(covers, string_types):
387 covers = dns.rdatatype.from_text(covers)
388 rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers)
389 rrset = dns.rrset.RRset(name, self.rdclass, rdtype, covers)
390 rrset.update(rdataset)
391 return rrset
392
394 """Look for rdata with the specified name and type in the zone,
395 and return an RRset encapsulating it.
396
397 The I{name}, I{rdtype}, and I{covers} parameters may be
398 strings, in which case they will be converted to their proper
399 type.
400
401 This method is less efficient than the similar L{get_rdataset}
402 because it creates an RRset instead of returning the matching
403 rdataset. It may be more convenient for some uses since it
404 returns an object which binds the owner name to the rdata.
405
406 This method may not be used to create new nodes or rdatasets;
407 use L{find_rdataset} instead.
408
409 None is returned if the name or type are not found.
410 Use L{find_rrset} if you want to have KeyError raised instead.
411
412 @param name: the owner name to look for
413 @type name: DNS.name.Name object or string
414 @param rdtype: the rdata type desired
415 @type rdtype: int or string
416 @param covers: the covered type (defaults to None)
417 @type covers: int or string
418 @rtype: dns.rrset.RRset object
419 """
420
421 try:
422 rrset = self.find_rrset(name, rdtype, covers)
423 except KeyError:
424 rrset = None
425 return rrset
426
449
473
474 - def to_file(self, f, sorted=True, relativize=True, nl=None):
475 """Write a zone to a file.
476
477 @param f: file or string. If I{f} is a string, it is treated
478 as the name of a file to open.
479 @param sorted: if True, the file will be written with the
480 names sorted in DNSSEC order from least to greatest. Otherwise
481 the names will be written in whatever order they happen to have
482 in the zone's dictionary.
483 @param relativize: if True, domain names in the output will be
484 relativized to the zone's origin (if possible).
485 @type relativize: bool
486 @param nl: The end of line string. If not specified, the
487 output will use the platform's native end-of-line marker (i.e.
488 LF on POSIX, CRLF on Windows, CR on Macintosh).
489 @type nl: string or None
490 """
491
492 str_type = string_types
493
494 if nl is None:
495 opts = 'wb'
496 else:
497 opts = 'wb'
498
499 if isinstance(f, str_type):
500 f = open(f, opts)
501 want_close = True
502 else:
503 want_close = False
504 try:
505 if sorted:
506 names = list(self.keys())
507 names.sort()
508 else:
509 names = self.iterkeys()
510 for n in names:
511 l = self[n].to_text(n, origin=self.origin,
512 relativize=relativize)
513 if isinstance(l, text_type):
514 l = l.encode()
515 if nl is None:
516 f.write(l)
517 f.write('\n')
518 else:
519 f.write(l)
520 f.write(nl)
521 finally:
522 if want_close:
523 f.close()
524
525 - def to_text(self, sorted=True, relativize=True, nl=None):
526 """Return a zone's text as though it were written to a file.
527
528 @param sorted: if True, the file will be written with the
529 names sorted in DNSSEC order from least to greatest. Otherwise
530 the names will be written in whatever order they happen to have
531 in the zone's dictionary.
532 @param relativize: if True, domain names in the output will be
533 relativized to the zone's origin (if possible).
534 @type relativize: bool
535 @param nl: The end of line string. If not specified, the
536 output will use the platform's native end-of-line marker (i.e.
537 LF on POSIX, CRLF on Windows, CR on Macintosh).
538 @type nl: string or None
539 """
540 temp_buffer = BytesIO()
541 self.to_file(temp_buffer, sorted, relativize, nl)
542 return_value = temp_buffer.getvalue()
543 temp_buffer.close()
544 return return_value
545
561
562
564
565 """Read a DNS master file
566
567 @ivar tok: The tokenizer
568 @type tok: dns.tokenizer.Tokenizer object
569 @ivar ttl: The default TTL
570 @type ttl: int
571 @ivar last_name: The last name read
572 @type last_name: dns.name.Name object
573 @ivar current_origin: The current origin
574 @type current_origin: dns.name.Name object
575 @ivar relativize: should names in the zone be relativized?
576 @type relativize: bool
577 @ivar zone: the zone
578 @type zone: dns.zone.Zone object
579 @ivar saved_state: saved reader state (used when processing $INCLUDE)
580 @type saved_state: list of (tokenizer, current_origin, last_name, file)
581 tuples.
582 @ivar current_file: the file object of the $INCLUDed file being parsed
583 (None if no $INCLUDE is active).
584 @ivar allow_include: is $INCLUDE allowed?
585 @type allow_include: bool
586 @ivar check_origin: should sanity checks of the origin node be done?
587 The default is True.
588 @type check_origin: bool
589 """
590
591 - def __init__(self, tok, origin, rdclass, relativize, zone_factory=Zone,
592 allow_include=False, check_origin=True):
605
611
613 """Process one line from a DNS master file."""
614
615 if self.current_origin is None:
616 raise UnknownOrigin
617 token = self.tok.get(want_leading=True)
618 if not token.is_whitespace():
619 self.last_name = dns.name.from_text(
620 token.value, self.current_origin)
621 else:
622 token = self.tok.get()
623 if token.is_eol_or_eof():
624
625 return
626 self.tok.unget(token)
627 name = self.last_name
628 if not name.is_subdomain(self.zone.origin):
629 self._eat_line()
630 return
631 if self.relativize:
632 name = name.relativize(self.zone.origin)
633 token = self.tok.get()
634 if not token.is_identifier():
635 raise dns.exception.SyntaxError
636
637 try:
638 ttl = dns.ttl.from_text(token.value)
639 token = self.tok.get()
640 if not token.is_identifier():
641 raise dns.exception.SyntaxError
642 except dns.ttl.BadTTL:
643 ttl = self.ttl
644
645 try:
646 rdclass = dns.rdataclass.from_text(token.value)
647 token = self.tok.get()
648 if not token.is_identifier():
649 raise dns.exception.SyntaxError
650 except dns.exception.SyntaxError:
651 raise dns.exception.SyntaxError
652 except:
653 rdclass = self.zone.rdclass
654 if rdclass != self.zone.rdclass:
655 raise dns.exception.SyntaxError("RR class is not zone's class")
656
657 try:
658 rdtype = dns.rdatatype.from_text(token.value)
659 except:
660 raise dns.exception.SyntaxError(
661 "unknown rdatatype '%s'" % token.value)
662 n = self.zone.nodes.get(name)
663 if n is None:
664 n = self.zone.node_factory()
665 self.zone.nodes[name] = n
666 try:
667 rd = dns.rdata.from_text(rdclass, rdtype, self.tok,
668 self.current_origin, False)
669 except dns.exception.SyntaxError:
670
671 (ty, va) = sys.exc_info()[:2]
672 raise va
673 except:
674
675
676
677
678
679 (ty, va) = sys.exc_info()[:2]
680 raise dns.exception.SyntaxError(
681 "caught exception %s: %s" % (str(ty), str(va)))
682
683 rd.choose_relativity(self.zone.origin, self.relativize)
684 covers = rd.covers()
685 rds = n.find_rdataset(rdclass, rdtype, covers, True)
686 rds.add(rd, ttl)
687
689
690
691 is_generate1 = re.compile("^.*\$({(\+|-?)(\d+),(\d+),(.)}).*$")
692 is_generate2 = re.compile("^.*\$({(\+|-?)(\d+)}).*$")
693 is_generate3 = re.compile("^.*\$({(\+|-?)(\d+),(\d+)}).*$")
694
695
696
697 g1 = is_generate1.match(side)
698 if g1:
699 mod, sign, offset, width, base = g1.groups()
700 if sign == '':
701 sign = '+'
702 g2 = is_generate2.match(side)
703 if g2:
704 mod, sign, offset = g2.groups()
705 if sign == '':
706 sign = '+'
707 width = 0
708 base = 'd'
709 g3 = is_generate3.match(side)
710 if g3:
711 mod, sign, offset, width = g1.groups()
712 if sign == '':
713 sign = '+'
714 width = g1.groups()[2]
715 base = 'd'
716
717 if not (g1 or g2 or g3):
718 mod = ''
719 sign = '+'
720 offset = 0
721 width = 0
722 base = 'd'
723
724 if base != 'd':
725 raise NotImplemented
726
727 return mod, sign, offset, width, base
728
730
731 """Process one line containing the GENERATE statement from a DNS
732 master file."""
733 if self.current_origin is None:
734 raise UnknownOrigin
735
736 token = self.tok.get()
737
738 try:
739 start, stop, step = dns.grange.from_text(token.value)
740 token = self.tok.get()
741 if not token.is_identifier():
742 raise dns.exception.SyntaxError
743 except:
744 raise dns.exception.SyntaxError
745
746
747 try:
748 lhs = token.value
749 token = self.tok.get()
750 if not token.is_identifier():
751 raise dns.exception.SyntaxError
752 except:
753 raise dns.exception.SyntaxError
754
755
756 try:
757 ttl = dns.ttl.from_text(token.value)
758 token = self.tok.get()
759 if not token.is_identifier():
760 raise dns.exception.SyntaxError
761 except dns.ttl.BadTTL:
762 ttl = self.ttl
763
764 try:
765 rdclass = dns.rdataclass.from_text(token.value)
766 token = self.tok.get()
767 if not token.is_identifier():
768 raise dns.exception.SyntaxError
769 except dns.exception.SyntaxError:
770 raise dns.exception.SyntaxError
771 except:
772 rdclass = self.zone.rdclass
773 if rdclass != self.zone.rdclass:
774 raise dns.exception.SyntaxError("RR class is not zone's class")
775
776 try:
777 rdtype = dns.rdatatype.from_text(token.value)
778 token = self.tok.get()
779 if not token.is_identifier():
780 raise dns.exception.SyntaxError
781 except:
782 raise dns.exception.SyntaxError("unknown rdatatype '%s'" %
783 token.value)
784
785
786 try:
787 rhs = token.value
788 except:
789 raise dns.exception.SyntaxError
790
791 lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs)
792 rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs)
793 for i in range(start, stop + 1, step):
794
795
796 if lsign == u'+':
797 lindex = i + int(loffset)
798 elif lsign == u'-':
799 lindex = i - int(loffset)
800
801 if rsign == u'-':
802 rindex = i - int(roffset)
803 elif rsign == u'+':
804 rindex = i + int(roffset)
805
806 lzfindex = str(lindex).zfill(int(lwidth))
807 rzfindex = str(rindex).zfill(int(rwidth))
808
809 name = lhs.replace(u'$%s' % (lmod), lzfindex)
810 rdata = rhs.replace(u'$%s' % (rmod), rzfindex)
811
812 self.last_name = dns.name.from_text(name, self.current_origin)
813 name = self.last_name
814 if not name.is_subdomain(self.zone.origin):
815 self._eat_line()
816 return
817 if self.relativize:
818 name = name.relativize(self.zone.origin)
819
820 n = self.zone.nodes.get(name)
821 if n is None:
822 n = self.zone.node_factory()
823 self.zone.nodes[name] = n
824 try:
825 rd = dns.rdata.from_text(rdclass, rdtype, rdata,
826 self.current_origin, False)
827 except dns.exception.SyntaxError:
828
829 (ty, va) = sys.exc_info()[:2]
830 raise va
831 except:
832
833
834
835
836
837 (ty, va) = sys.exc_info()[:2]
838 raise dns.exception.SyntaxError("caught exception %s: %s" %
839 (str(ty), str(va)))
840
841 rd.choose_relativity(self.zone.origin, self.relativize)
842 covers = rd.covers()
843 rds = n.find_rdataset(rdclass, rdtype, covers, True)
844 rds.add(rd, ttl)
845
847 """Read a DNS master file and build a zone object.
848
849 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
850 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
851 """
852
853 try:
854 while 1:
855 token = self.tok.get(True, True)
856 if token.is_eof():
857 if self.current_file is not None:
858 self.current_file.close()
859 if len(self.saved_state) > 0:
860 (self.tok,
861 self.current_origin,
862 self.last_name,
863 self.current_file,
864 self.ttl) = self.saved_state.pop(-1)
865 continue
866 break
867 elif token.is_eol():
868 continue
869 elif token.is_comment():
870 self.tok.get_eol()
871 continue
872 elif token.value[0] == u'$':
873 c = token.value.upper()
874 if c == u'$TTL':
875 token = self.tok.get()
876 if not token.is_identifier():
877 raise dns.exception.SyntaxError("bad $TTL")
878 self.ttl = dns.ttl.from_text(token.value)
879 self.tok.get_eol()
880 elif c == u'$ORIGIN':
881 self.current_origin = self.tok.get_name()
882 self.tok.get_eol()
883 if self.zone.origin is None:
884 self.zone.origin = self.current_origin
885 elif c == u'$INCLUDE' and self.allow_include:
886 token = self.tok.get()
887 filename = token.value
888 token = self.tok.get()
889 if token.is_identifier():
890 new_origin =\
891 dns.name.from_text(token.value,
892 self.current_origin)
893 self.tok.get_eol()
894 elif not token.is_eol_or_eof():
895 raise dns.exception.SyntaxError(
896 "bad origin in $INCLUDE")
897 else:
898 new_origin = self.current_origin
899 self.saved_state.append((self.tok,
900 self.current_origin,
901 self.last_name,
902 self.current_file,
903 self.ttl))
904 self.current_file = open(filename, 'r')
905 self.tok = dns.tokenizer.Tokenizer(self.current_file,
906 filename)
907 self.current_origin = new_origin
908 elif c == u'$GENERATE':
909 self._generate_line()
910 else:
911 raise dns.exception.SyntaxError(
912 "Unknown master file directive '" + c + "'")
913 continue
914 self.tok.unget(token)
915 self._rr_line()
916 except dns.exception.SyntaxError as detail:
917 (filename, line_number) = self.tok.where()
918 if detail is None:
919 detail = "syntax error"
920 raise dns.exception.SyntaxError(
921 "%s:%d: %s" % (filename, line_number, detail))
922
923
924 if self.check_origin:
925 self.zone.check_origin()
926
927
928 -def from_text(text, origin=None, rdclass=dns.rdataclass.IN,
929 relativize=True, zone_factory=Zone, filename=None,
930 allow_include=False, check_origin=True):
931 """Build a zone object from a master file format string.
932
933 @param text: the master file format input
934 @type text: string.
935 @param origin: The origin of the zone; if not specified, the first
936 $ORIGIN statement in the master file will determine the origin of the
937 zone.
938 @type origin: dns.name.Name object or string
939 @param rdclass: The zone's rdata class; the default is class IN.
940 @type rdclass: int
941 @param relativize: should names be relativized? The default is True
942 @type relativize: bool
943 @param zone_factory: The zone factory to use
944 @type zone_factory: function returning a Zone
945 @param filename: The filename to emit when describing where an error
946 occurred; the default is '<string>'.
947 @type filename: string
948 @param allow_include: is $INCLUDE allowed?
949 @type allow_include: bool
950 @param check_origin: should sanity checks of the origin node be done?
951 The default is True.
952 @type check_origin: bool
953 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
954 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
955 @rtype: dns.zone.Zone object
956 """
957
958
959
960
961
962 if filename is None:
963 filename = '<string>'
964 tok = dns.tokenizer.Tokenizer(text, filename)
965 reader = _MasterReader(tok, origin, rdclass, relativize, zone_factory,
966 allow_include=allow_include,
967 check_origin=check_origin)
968 reader.read()
969 return reader.zone
970
971
972 -def from_file(f, origin=None, rdclass=dns.rdataclass.IN,
973 relativize=True, zone_factory=Zone, filename=None,
974 allow_include=True, check_origin=True):
975 """Read a master file and build a zone object.
976
977 @param f: file or string. If I{f} is a string, it is treated
978 as the name of a file to open.
979 @param origin: The origin of the zone; if not specified, the first
980 $ORIGIN statement in the master file will determine the origin of the
981 zone.
982 @type origin: dns.name.Name object or string
983 @param rdclass: The zone's rdata class; the default is class IN.
984 @type rdclass: int
985 @param relativize: should names be relativized? The default is True
986 @type relativize: bool
987 @param zone_factory: The zone factory to use
988 @type zone_factory: function returning a Zone
989 @param filename: The filename to emit when describing where an error
990 occurred; the default is '<file>', or the value of I{f} if I{f} is a
991 string.
992 @type filename: string
993 @param allow_include: is $INCLUDE allowed?
994 @type allow_include: bool
995 @param check_origin: should sanity checks of the origin node be done?
996 The default is True.
997 @type check_origin: bool
998 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
999 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
1000 @rtype: dns.zone.Zone object
1001 """
1002
1003 str_type = string_types
1004 opts = 'rU'
1005
1006 if isinstance(f, str_type):
1007 if filename is None:
1008 filename = f
1009 f = open(f, opts)
1010 want_close = True
1011 else:
1012 if filename is None:
1013 filename = '<file>'
1014 want_close = False
1015
1016 try:
1017 z = from_text(f, origin, rdclass, relativize, zone_factory,
1018 filename, allow_include, check_origin)
1019 finally:
1020 if want_close:
1021 f.close()
1022 return z
1023
1024
1025 -def from_xfr(xfr, zone_factory=Zone, relativize=True, check_origin=True):
1026 """Convert the output of a zone transfer generator into a zone object.
1027
1028 @param xfr: The xfr generator
1029 @type xfr: generator of dns.message.Message objects
1030 @param relativize: should names be relativized? The default is True.
1031 It is essential that the relativize setting matches the one specified
1032 to dns.query.xfr().
1033 @type relativize: bool
1034 @param check_origin: should sanity checks of the origin node be done?
1035 The default is True.
1036 @type check_origin: bool
1037 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
1038 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
1039 @rtype: dns.zone.Zone object
1040 """
1041
1042 z = None
1043 for r in xfr:
1044 if z is None:
1045 if relativize:
1046 origin = r.origin
1047 else:
1048 origin = r.answer[0].name
1049 rdclass = r.answer[0].rdclass
1050 z = zone_factory(origin, rdclass, relativize=relativize)
1051 for rrset in r.answer:
1052 znode = z.nodes.get(rrset.name)
1053 if not znode:
1054 znode = z.node_factory()
1055 z.nodes[rrset.name] = znode
1056 zrds = znode.find_rdataset(rrset.rdclass, rrset.rdtype,
1057 rrset.covers, True)
1058 zrds.update_ttl(rrset.ttl)
1059 for rd in rrset:
1060 rd.choose_relativity(z.origin, relativize)
1061 zrds.add(rd)
1062 if check_origin:
1063 z.check_origin()
1064 return z
1065