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