1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """DNS Zones."""
19
20 from __future__ import generators
21
22 import sys
23 import re
24 import os
25 from io import BytesIO
26
27 import dns.exception
28 import dns.name
29 import dns.node
30 import dns.rdataclass
31 import dns.rdatatype
32 import dns.rdata
33 import dns.rdtypes.ANY.SOA
34 import dns.rrset
35 import dns.tokenizer
36 import dns.ttl
37 import dns.grange
38 from ._compat import string_types, text_type, PY3
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.rdataset.Rdataset 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.rdataset.Rdataset object or None
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 last_ttl: The last seen explicit TTL for an RR
593 @type last_ttl: int
594 @ivar last_ttl_known: Has last TTL been detected
595 @type last_ttl_known: bool
596 @ivar default_ttl: The default TTL from a $TTL directive or SOA RR
597 @type default_ttl: int
598 @ivar default_ttl_known: Has default TTL been detected
599 @type default_ttl_known: bool
600 @ivar last_name: The last name read
601 @type last_name: dns.name.Name object
602 @ivar current_origin: The current origin
603 @type current_origin: dns.name.Name object
604 @ivar relativize: should names in the zone be relativized?
605 @type relativize: bool
606 @ivar zone: the zone
607 @type zone: dns.zone.Zone object
608 @ivar saved_state: saved reader state (used when processing $INCLUDE)
609 @type saved_state: list of (tokenizer, current_origin, last_name, file,
610 last_ttl, last_ttl_known, default_ttl, default_ttl_known) tuples.
611 @ivar current_file: the file object of the $INCLUDed file being parsed
612 (None if no $INCLUDE is active).
613 @ivar allow_include: is $INCLUDE allowed?
614 @type allow_include: bool
615 @ivar check_origin: should sanity checks of the origin node be done?
616 The default is True.
617 @type check_origin: bool
618 """
619
620 - def __init__(self, tok, origin, rdclass, relativize, zone_factory=Zone,
621 allow_include=False, check_origin=True):
637
643
645 """Process one line from a DNS master file."""
646
647 if self.current_origin is None:
648 raise UnknownOrigin
649 token = self.tok.get(want_leading=True)
650 if not token.is_whitespace():
651 self.last_name = dns.name.from_text(
652 token.value, self.current_origin)
653 else:
654 token = self.tok.get()
655 if token.is_eol_or_eof():
656
657 return
658 self.tok.unget(token)
659 name = self.last_name
660 if not name.is_subdomain(self.zone.origin):
661 self._eat_line()
662 return
663 if self.relativize:
664 name = name.relativize(self.zone.origin)
665 token = self.tok.get()
666 if not token.is_identifier():
667 raise dns.exception.SyntaxError
668
669 try:
670 ttl = dns.ttl.from_text(token.value)
671 self.last_ttl = ttl
672 self.last_ttl_known = True
673 token = self.tok.get()
674 if not token.is_identifier():
675 raise dns.exception.SyntaxError
676 except dns.ttl.BadTTL:
677 if not (self.last_ttl_known or self.default_ttl_known):
678 raise dns.exception.SyntaxError("Missing default TTL value")
679 if self.default_ttl_known:
680 ttl = self.default_ttl
681 else:
682 ttl = self.last_ttl
683
684 try:
685 rdclass = dns.rdataclass.from_text(token.value)
686 token = self.tok.get()
687 if not token.is_identifier():
688 raise dns.exception.SyntaxError
689 except dns.exception.SyntaxError:
690 raise dns.exception.SyntaxError
691 except Exception:
692 rdclass = self.zone.rdclass
693 if rdclass != self.zone.rdclass:
694 raise dns.exception.SyntaxError("RR class is not zone's class")
695
696 try:
697 rdtype = dns.rdatatype.from_text(token.value)
698 except:
699 raise dns.exception.SyntaxError(
700 "unknown rdatatype '%s'" % token.value)
701 n = self.zone.nodes.get(name)
702 if n is None:
703 n = self.zone.node_factory()
704 self.zone.nodes[name] = n
705 try:
706 rd = dns.rdata.from_text(rdclass, rdtype, self.tok,
707 self.current_origin, False)
708 except dns.exception.SyntaxError:
709
710 (ty, va) = sys.exc_info()[:2]
711 raise va
712 except:
713
714
715
716
717
718 (ty, va) = sys.exc_info()[:2]
719 raise dns.exception.SyntaxError(
720 "caught exception {}: {}".format(str(ty), str(va)))
721
722 if not self.default_ttl_known and isinstance(rd, dns.rdtypes.ANY.SOA.SOA):
723
724
725
726 self.default_ttl = rd.minimum
727 self.default_ttl_known = True
728
729 rd.choose_relativity(self.zone.origin, self.relativize)
730 covers = rd.covers()
731 rds = n.find_rdataset(rdclass, rdtype, covers, True)
732 rds.add(rd, ttl)
733
735
736
737 is_generate1 = re.compile("^.*\$({(\+|-?)(\d+),(\d+),(.)}).*$")
738 is_generate2 = re.compile("^.*\$({(\+|-?)(\d+)}).*$")
739 is_generate3 = re.compile("^.*\$({(\+|-?)(\d+),(\d+)}).*$")
740
741
742
743 g1 = is_generate1.match(side)
744 if g1:
745 mod, sign, offset, width, base = g1.groups()
746 if sign == '':
747 sign = '+'
748 g2 = is_generate2.match(side)
749 if g2:
750 mod, sign, offset = g2.groups()
751 if sign == '':
752 sign = '+'
753 width = 0
754 base = 'd'
755 g3 = is_generate3.match(side)
756 if g3:
757 mod, sign, offset, width = g1.groups()
758 if sign == '':
759 sign = '+'
760 width = g1.groups()[2]
761 base = 'd'
762
763 if not (g1 or g2 or g3):
764 mod = ''
765 sign = '+'
766 offset = 0
767 width = 0
768 base = 'd'
769
770 if base != 'd':
771 raise NotImplementedError()
772
773 return mod, sign, offset, width, base
774
776
777 """Process one line containing the GENERATE statement from a DNS
778 master file."""
779 if self.current_origin is None:
780 raise UnknownOrigin
781
782 token = self.tok.get()
783
784 try:
785 start, stop, step = dns.grange.from_text(token.value)
786 token = self.tok.get()
787 if not token.is_identifier():
788 raise dns.exception.SyntaxError
789 except:
790 raise dns.exception.SyntaxError
791
792
793 try:
794 lhs = token.value
795 token = self.tok.get()
796 if not token.is_identifier():
797 raise dns.exception.SyntaxError
798 except:
799 raise dns.exception.SyntaxError
800
801
802 try:
803 ttl = dns.ttl.from_text(token.value)
804 self.last_ttl = ttl
805 self.last_ttl_known = True
806 token = self.tok.get()
807 if not token.is_identifier():
808 raise dns.exception.SyntaxError
809 except dns.ttl.BadTTL:
810 if not (self.last_ttl_known or self.default_ttl_known):
811 raise dns.exception.SyntaxError("Missing default TTL value")
812 if self.default_ttl_known:
813 ttl = self.default_ttl
814 else:
815 ttl = self.last_ttl
816
817 try:
818 rdclass = dns.rdataclass.from_text(token.value)
819 token = self.tok.get()
820 if not token.is_identifier():
821 raise dns.exception.SyntaxError
822 except dns.exception.SyntaxError:
823 raise dns.exception.SyntaxError
824 except Exception:
825 rdclass = self.zone.rdclass
826 if rdclass != self.zone.rdclass:
827 raise dns.exception.SyntaxError("RR class is not zone's class")
828
829 try:
830 rdtype = dns.rdatatype.from_text(token.value)
831 token = self.tok.get()
832 if not token.is_identifier():
833 raise dns.exception.SyntaxError
834 except Exception:
835 raise dns.exception.SyntaxError("unknown rdatatype '%s'" %
836 token.value)
837
838
839 try:
840 rhs = token.value
841 except:
842 raise dns.exception.SyntaxError
843
844 lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs)
845 rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs)
846 for i in range(start, stop + 1, step):
847
848
849 if lsign == u'+':
850 lindex = i + int(loffset)
851 elif lsign == u'-':
852 lindex = i - int(loffset)
853
854 if rsign == u'-':
855 rindex = i - int(roffset)
856 elif rsign == u'+':
857 rindex = i + int(roffset)
858
859 lzfindex = str(lindex).zfill(int(lwidth))
860 rzfindex = str(rindex).zfill(int(rwidth))
861
862 name = lhs.replace(u'$%s' % (lmod), lzfindex)
863 rdata = rhs.replace(u'$%s' % (rmod), rzfindex)
864
865 self.last_name = dns.name.from_text(name, self.current_origin)
866 name = self.last_name
867 if not name.is_subdomain(self.zone.origin):
868 self._eat_line()
869 return
870 if self.relativize:
871 name = name.relativize(self.zone.origin)
872
873 n = self.zone.nodes.get(name)
874 if n is None:
875 n = self.zone.node_factory()
876 self.zone.nodes[name] = n
877 try:
878 rd = dns.rdata.from_text(rdclass, rdtype, rdata,
879 self.current_origin, False)
880 except dns.exception.SyntaxError:
881
882 (ty, va) = sys.exc_info()[:2]
883 raise va
884 except:
885
886
887
888
889
890 (ty, va) = sys.exc_info()[:2]
891 raise dns.exception.SyntaxError("caught exception %s: %s" %
892 (str(ty), str(va)))
893
894 rd.choose_relativity(self.zone.origin, self.relativize)
895 covers = rd.covers()
896 rds = n.find_rdataset(rdclass, rdtype, covers, True)
897 rds.add(rd, ttl)
898
900 """Read a DNS master file and build a zone object.
901
902 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
903 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
904 """
905
906 try:
907 while 1:
908 token = self.tok.get(True, True)
909 if token.is_eof():
910 if self.current_file is not None:
911 self.current_file.close()
912 if len(self.saved_state) > 0:
913 (self.tok,
914 self.current_origin,
915 self.last_name,
916 self.current_file,
917 self.last_ttl,
918 self.last_ttl_known,
919 self.default_ttl,
920 self.default_ttl_known) = self.saved_state.pop(-1)
921 continue
922 break
923 elif token.is_eol():
924 continue
925 elif token.is_comment():
926 self.tok.get_eol()
927 continue
928 elif token.value[0] == u'$':
929 c = token.value.upper()
930 if c == u'$TTL':
931 token = self.tok.get()
932 if not token.is_identifier():
933 raise dns.exception.SyntaxError("bad $TTL")
934 self.default_ttl = dns.ttl.from_text(token.value)
935 self.default_ttl_known = True
936 self.tok.get_eol()
937 elif c == u'$ORIGIN':
938 self.current_origin = self.tok.get_name()
939 self.tok.get_eol()
940 if self.zone.origin is None:
941 self.zone.origin = self.current_origin
942 elif c == u'$INCLUDE' and self.allow_include:
943 token = self.tok.get()
944 filename = token.value
945 token = self.tok.get()
946 if token.is_identifier():
947 new_origin =\
948 dns.name.from_text(token.value,
949 self.current_origin)
950 self.tok.get_eol()
951 elif not token.is_eol_or_eof():
952 raise dns.exception.SyntaxError(
953 "bad origin in $INCLUDE")
954 else:
955 new_origin = self.current_origin
956 self.saved_state.append((self.tok,
957 self.current_origin,
958 self.last_name,
959 self.current_file,
960 self.last_ttl,
961 self.last_ttl_known,
962 self.default_ttl,
963 self.default_ttl_known))
964 self.current_file = open(filename, 'r')
965 self.tok = dns.tokenizer.Tokenizer(self.current_file,
966 filename)
967 self.current_origin = new_origin
968 elif c == u'$GENERATE':
969 self._generate_line()
970 else:
971 raise dns.exception.SyntaxError(
972 "Unknown master file directive '" + c + "'")
973 continue
974 self.tok.unget(token)
975 self._rr_line()
976 except dns.exception.SyntaxError as detail:
977 (filename, line_number) = self.tok.where()
978 if detail is None:
979 detail = "syntax error"
980 raise dns.exception.SyntaxError(
981 "%s:%d: %s" % (filename, line_number, detail))
982
983
984 if self.check_origin:
985 self.zone.check_origin()
986
987
988 -def from_text(text, origin=None, rdclass=dns.rdataclass.IN,
989 relativize=True, zone_factory=Zone, filename=None,
990 allow_include=False, check_origin=True):
991 """Build a zone object from a master file format string.
992
993 @param text: the master file format input
994 @type text: string.
995 @param origin: The origin of the zone; if not specified, the first
996 $ORIGIN statement in the master file will determine the origin of the
997 zone.
998 @type origin: dns.name.Name object or string
999 @param rdclass: The zone's rdata class; the default is class IN.
1000 @type rdclass: int
1001 @param relativize: should names be relativized? The default is True
1002 @type relativize: bool
1003 @param zone_factory: The zone factory to use
1004 @type zone_factory: function returning a Zone
1005 @param filename: The filename to emit when describing where an error
1006 occurred; the default is '<string>'.
1007 @type filename: string
1008 @param allow_include: is $INCLUDE allowed?
1009 @type allow_include: bool
1010 @param check_origin: should sanity checks of the origin node be done?
1011 The default is True.
1012 @type check_origin: bool
1013 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
1014 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
1015 @rtype: dns.zone.Zone object
1016 """
1017
1018
1019
1020
1021
1022 if filename is None:
1023 filename = '<string>'
1024 tok = dns.tokenizer.Tokenizer(text, filename)
1025 reader = _MasterReader(tok, origin, rdclass, relativize, zone_factory,
1026 allow_include=allow_include,
1027 check_origin=check_origin)
1028 reader.read()
1029 return reader.zone
1030
1031
1032 -def from_file(f, origin=None, rdclass=dns.rdataclass.IN,
1033 relativize=True, zone_factory=Zone, filename=None,
1034 allow_include=True, check_origin=True):
1035 """Read a master file and build a zone object.
1036
1037 @param f: file or string. If I{f} is a string, it is treated
1038 as the name of a file to open.
1039 @param origin: The origin of the zone; if not specified, the first
1040 $ORIGIN statement in the master file will determine the origin of the
1041 zone.
1042 @type origin: dns.name.Name object or string
1043 @param rdclass: The zone's rdata class; the default is class IN.
1044 @type rdclass: int
1045 @param relativize: should names be relativized? The default is True
1046 @type relativize: bool
1047 @param zone_factory: The zone factory to use
1048 @type zone_factory: function returning a Zone
1049 @param filename: The filename to emit when describing where an error
1050 occurred; the default is '<file>', or the value of I{f} if I{f} is a
1051 string.
1052 @type filename: string
1053 @param allow_include: is $INCLUDE allowed?
1054 @type allow_include: bool
1055 @param check_origin: should sanity checks of the origin node be done?
1056 The default is True.
1057 @type check_origin: bool
1058 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
1059 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
1060 @rtype: dns.zone.Zone object
1061 """
1062
1063 str_type = string_types
1064 if PY3:
1065 opts = 'r'
1066 else:
1067 opts = 'rU'
1068
1069 if isinstance(f, str_type):
1070 if filename is None:
1071 filename = f
1072 f = open(f, opts)
1073 want_close = True
1074 else:
1075 if filename is None:
1076 filename = '<file>'
1077 want_close = False
1078
1079 try:
1080 z = from_text(f, origin, rdclass, relativize, zone_factory,
1081 filename, allow_include, check_origin)
1082 finally:
1083 if want_close:
1084 f.close()
1085 return z
1086
1087
1088 -def from_xfr(xfr, zone_factory=Zone, relativize=True, check_origin=True):
1089 """Convert the output of a zone transfer generator into a zone object.
1090
1091 @param xfr: The xfr generator
1092 @type xfr: generator of dns.message.Message objects
1093 @param relativize: should names be relativized? The default is True.
1094 It is essential that the relativize setting matches the one specified
1095 to dns.query.xfr().
1096 @type relativize: bool
1097 @param check_origin: should sanity checks of the origin node be done?
1098 The default is True.
1099 @type check_origin: bool
1100 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
1101 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
1102 @rtype: dns.zone.Zone object
1103 """
1104
1105 z = None
1106 for r in xfr:
1107 if z is None:
1108 if relativize:
1109 origin = r.origin
1110 else:
1111 origin = r.answer[0].name
1112 rdclass = r.answer[0].rdclass
1113 z = zone_factory(origin, rdclass, relativize=relativize)
1114 for rrset in r.answer:
1115 znode = z.nodes.get(rrset.name)
1116 if not znode:
1117 znode = z.node_factory()
1118 z.nodes[rrset.name] = znode
1119 zrds = znode.find_rdataset(rrset.rdclass, rrset.rdtype,
1120 rrset.covers, True)
1121 zrds.update_ttl(rrset.ttl)
1122 for rd in rrset:
1123 rd.choose_relativity(z.origin, relativize)
1124 zrds.add(rd)
1125 if check_origin:
1126 z.check_origin()
1127 return z
1128