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