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