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