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 DNS zone is malformed."""
41
43 """The DNS zone has no SOA RR at its origin."""
44
46 """The DNS zone has no NS RRset at its origin."""
47
49 """The DNS zone's origin is unknown."""
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
509 - def to_text(self, sorted=True, relativize=True, nl=None):
510 """Return a zone's text as though it were written to a file.
511
512 @param sorted: if True, the file will be written with the
513 names sorted in DNSSEC order from least to greatest. Otherwise
514 the names will be written in whatever order they happen to have
515 in the zone's dictionary.
516 @param relativize: if True, domain names in the output will be
517 relativized to the zone's origin (if possible).
518 @type relativize: bool
519 @param nl: The end of line string. If not specified, the
520 output will use the platform's native end-of-line marker (i.e.
521 LF on POSIX, CRLF on Windows, CR on Macintosh).
522 @type nl: string or None
523 """
524 temp_buffer = StringIO()
525 self.to_file(temp_buffer, sorted, relativize, nl)
526 return_value = temp_buffer.getvalue()
527 temp_buffer.close()
528 return return_value
529
545
546
548 """Read a DNS master file
549
550 @ivar tok: The tokenizer
551 @type tok: dns.tokenizer.Tokenizer object
552 @ivar ttl: The default TTL
553 @type ttl: int
554 @ivar last_name: The last name read
555 @type last_name: dns.name.Name object
556 @ivar current_origin: The current origin
557 @type current_origin: dns.name.Name object
558 @ivar relativize: should names in the zone be relativized?
559 @type relativize: bool
560 @ivar zone: the zone
561 @type zone: dns.zone.Zone object
562 @ivar saved_state: saved reader state (used when processing $INCLUDE)
563 @type saved_state: list of (tokenizer, current_origin, last_name, file)
564 tuples.
565 @ivar current_file: the file object of the $INCLUDed file being parsed
566 (None if no $INCLUDE is active).
567 @ivar allow_include: is $INCLUDE allowed?
568 @type allow_include: bool
569 @ivar check_origin: should sanity checks of the origin node be done?
570 The default is True.
571 @type check_origin: bool
572 """
573
574 - def __init__(self, tok, origin, rdclass, relativize, zone_factory=Zone,
575 allow_include=False, check_origin=True):
588
594
596 """Process one line from a DNS master file."""
597
598 if self.current_origin is None:
599 raise UnknownOrigin
600 token = self.tok.get(want_leading = True)
601 if not token.is_whitespace():
602 self.last_name = dns.name.from_text(token.value, self.current_origin)
603 else:
604 token = self.tok.get()
605 if token.is_eol_or_eof():
606
607 return
608 self.tok.unget(token)
609 name = self.last_name
610 if not name.is_subdomain(self.zone.origin):
611 self._eat_line()
612 return
613 if self.relativize:
614 name = name.relativize(self.zone.origin)
615 token = self.tok.get()
616 if not token.is_identifier():
617 raise dns.exception.SyntaxError
618
619 try:
620 ttl = dns.ttl.from_text(token.value)
621 token = self.tok.get()
622 if not token.is_identifier():
623 raise dns.exception.SyntaxError
624 except dns.ttl.BadTTL:
625 ttl = self.ttl
626
627 try:
628 rdclass = dns.rdataclass.from_text(token.value)
629 token = self.tok.get()
630 if not token.is_identifier():
631 raise dns.exception.SyntaxError
632 except dns.exception.SyntaxError:
633 raise dns.exception.SyntaxError
634 except:
635 rdclass = self.zone.rdclass
636 if rdclass != self.zone.rdclass:
637 raise dns.exception.SyntaxError("RR class is not zone's class")
638
639 try:
640 rdtype = dns.rdatatype.from_text(token.value)
641 except:
642 raise dns.exception.SyntaxError("unknown rdatatype '%s'" % token.value)
643 n = self.zone.nodes.get(name)
644 if n is None:
645 n = self.zone.node_factory()
646 self.zone.nodes[name] = n
647 try:
648 rd = dns.rdata.from_text(rdclass, rdtype, self.tok,
649 self.current_origin, False)
650 except dns.exception.SyntaxError:
651
652 (ty, va) = sys.exc_info()[:2]
653 raise va
654 except:
655
656
657
658
659
660 (ty, va) = sys.exc_info()[:2]
661 raise dns.exception.SyntaxError("caught exception %s: %s" % (str(ty), str(va)))
662
663 rd.choose_relativity(self.zone.origin, self.relativize)
664 covers = rd.covers()
665 rds = n.find_rdataset(rdclass, rdtype, covers, True)
666 rds.add(rd, ttl)
667
669
670
671 is_generate1 = re.compile("^.*\$({(\+|-?)(\d+),(\d+),(.)}).*$")
672 is_generate2 = re.compile("^.*\$({(\+|-?)(\d+)}).*$")
673 is_generate3 = re.compile("^.*\$({(\+|-?)(\d+),(\d+)}).*$")
674
675
676
677 g1 = is_generate1.match(side)
678 if g1:
679 mod, sign, offset, width, base = g1.groups()
680 if sign == '':
681 sign = '+'
682 g2 = is_generate2.match(side)
683 if g2:
684 mod, sign, offset = g2.groups()
685 if sign == '':
686 sign = '+'
687 width = 0
688 base = 'd'
689 g3 = is_generate3.match(side)
690 if g3:
691 mod, sign, offset, width = g1.groups()
692 if sign == '':
693 sign = '+'
694 width = g1.groups()[2]
695 base = 'd'
696
697 if not (g1 or g2 or g3):
698 mod = ''
699 sign = '+'
700 offset = 0
701 width = 0
702 base = 'd'
703
704 if base != 'd':
705 raise NotImplemented
706
707 return mod, sign, offset, width, base
708
710
711 """Process one line containing the GENERATE statement from a DNS
712 master file."""
713 if self.current_origin is None:
714 raise UnknownOrigin
715
716 token = self.tok.get()
717
718 try:
719 start, stop, step = dns.grange.from_text(token.value)
720 token = self.tok.get()
721 if not token.is_identifier():
722 raise dns.exception.SyntaxError
723 except:
724 raise dns.exception.SyntaxError
725
726
727 try:
728 lhs = token.value
729 token = self.tok.get()
730 if not token.is_identifier():
731 raise dns.exception.SyntaxError
732 except:
733 raise dns.exception.SyntaxError
734
735
736 try:
737 ttl = dns.ttl.from_text(token.value)
738 token = self.tok.get()
739 if not token.is_identifier():
740 raise dns.exception.SyntaxError
741 except dns.ttl.BadTTL:
742 ttl = self.ttl
743
744 try:
745 rdclass = dns.rdataclass.from_text(token.value)
746 token = self.tok.get()
747 if not token.is_identifier():
748 raise dns.exception.SyntaxError
749 except dns.exception.SyntaxError:
750 raise dns.exception.SyntaxError
751 except:
752 rdclass = self.zone.rdclass
753 if rdclass != self.zone.rdclass:
754 raise dns.exception.SyntaxError("RR class is not zone's class")
755
756 try:
757 rdtype = dns.rdatatype.from_text(token.value)
758 token = self.tok.get()
759 if not token.is_identifier():
760 raise dns.exception.SyntaxError
761 except:
762 raise dns.exception.SyntaxError("unknown rdatatype '%s'" %
763 token.value)
764
765
766 try:
767 rhs = token.value
768 except:
769 raise dns.exception.SyntaxError
770
771
772 lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs)
773 rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs)
774 for i in range(start, stop + 1, step):
775
776
777 if lsign == '+':
778 lindex = i + int(loffset)
779 elif lsign == '-':
780 lindex = i - int(loffset)
781
782 if rsign == '-':
783 rindex = i - int(roffset)
784 elif rsign == '+':
785 rindex = i + int(roffset)
786
787 lzfindex = str(lindex).zfill(int(lwidth))
788 rzfindex = str(rindex).zfill(int(rwidth))
789
790
791 name = lhs.replace('$%s' % (lmod), lzfindex)
792 rdata = rhs.replace('$%s' % (rmod), rzfindex)
793
794 self.last_name = dns.name.from_text(name, self.current_origin)
795 name = self.last_name
796 if not name.is_subdomain(self.zone.origin):
797 self._eat_line()
798 return
799 if self.relativize:
800 name = name.relativize(self.zone.origin)
801
802 n = self.zone.nodes.get(name)
803 if n is None:
804 n = self.zone.node_factory()
805 self.zone.nodes[name] = n
806 try:
807 rd = dns.rdata.from_text(rdclass, rdtype, rdata,
808 self.current_origin, False)
809 except dns.exception.SyntaxError:
810
811 (ty, va) = sys.exc_info()[:2]
812 raise va
813 except:
814
815
816
817
818
819 (ty, va) = sys.exc_info()[:2]
820 raise dns.exception.SyntaxError("caught exception %s: %s" %
821 (str(ty), str(va)))
822
823 rd.choose_relativity(self.zone.origin, self.relativize)
824 covers = rd.covers()
825 rds = n.find_rdataset(rdclass, rdtype, covers, True)
826 rds.add(rd, ttl)
827
829 """Read a DNS master file and build a zone object.
830
831 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
832 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
833 """
834
835 try:
836 while 1:
837 token = self.tok.get(True, True)
838 if token.is_eof():
839 if not self.current_file is None:
840 self.current_file.close()
841 if len(self.saved_state) > 0:
842 (self.tok,
843 self.current_origin,
844 self.last_name,
845 self.current_file,
846 self.ttl) = self.saved_state.pop(-1)
847 continue
848 break
849 elif token.is_eol():
850 continue
851 elif token.is_comment():
852 self.tok.get_eol()
853 continue
854 elif token.value[0] == '$':
855 u = token.value.upper()
856 if u == '$TTL':
857 token = self.tok.get()
858 if not token.is_identifier():
859 raise dns.exception.SyntaxError("bad $TTL")
860 self.ttl = dns.ttl.from_text(token.value)
861 self.tok.get_eol()
862 elif u == '$ORIGIN':
863 self.current_origin = self.tok.get_name()
864 self.tok.get_eol()
865 if self.zone.origin is None:
866 self.zone.origin = self.current_origin
867 elif u == '$INCLUDE' and self.allow_include:
868 token = self.tok.get()
869 filename = token.value
870 token = self.tok.get()
871 if token.is_identifier():
872 new_origin = dns.name.from_text(token.value, \
873 self.current_origin)
874 self.tok.get_eol()
875 elif not token.is_eol_or_eof():
876 raise dns.exception.SyntaxError("bad origin in $INCLUDE")
877 else:
878 new_origin = self.current_origin
879 self.saved_state.append((self.tok,
880 self.current_origin,
881 self.last_name,
882 self.current_file,
883 self.ttl))
884 self.current_file = file(filename, 'r')
885 self.tok = dns.tokenizer.Tokenizer(self.current_file,
886 filename)
887 self.current_origin = new_origin
888 elif u == '$GENERATE':
889 self._generate_line()
890 else:
891 raise dns.exception.SyntaxError("Unknown master file directive '" + u + "'")
892 continue
893 self.tok.unget(token)
894 self._rr_line()
895 except dns.exception.SyntaxError, detail:
896 (filename, line_number) = self.tok.where()
897 if detail is None:
898 detail = "syntax error"
899 raise dns.exception.SyntaxError("%s:%d: %s" % (filename, line_number, detail))
900
901
902 if self.check_origin:
903 self.zone.check_origin()
904
905 -def from_text(text, origin = None, rdclass = dns.rdataclass.IN,
906 relativize = True, zone_factory=Zone, filename=None,
907 allow_include=False, check_origin=True):
908 """Build a zone object from a master file format string.
909
910 @param text: the master file format input
911 @type text: string.
912 @param origin: The origin of the zone; if not specified, the first
913 $ORIGIN statement in the master file will determine the origin of the
914 zone.
915 @type origin: dns.name.Name object or string
916 @param rdclass: The zone's rdata class; the default is class IN.
917 @type rdclass: int
918 @param relativize: should names be relativized? The default is True
919 @type relativize: bool
920 @param zone_factory: The zone factory to use
921 @type zone_factory: function returning a Zone
922 @param filename: The filename to emit when describing where an error
923 occurred; the default is '<string>'.
924 @type filename: string
925 @param allow_include: is $INCLUDE allowed?
926 @type allow_include: bool
927 @param check_origin: should sanity checks of the origin node be done?
928 The default is True.
929 @type check_origin: bool
930 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
931 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
932 @rtype: dns.zone.Zone object
933 """
934
935
936
937
938
939 if filename is None:
940 filename = '<string>'
941 tok = dns.tokenizer.Tokenizer(text, filename)
942 reader = _MasterReader(tok, origin, rdclass, relativize, zone_factory,
943 allow_include=allow_include,
944 check_origin=check_origin)
945 reader.read()
946 return reader.zone
947
948 -def from_file(f, origin = None, rdclass = dns.rdataclass.IN,
949 relativize = True, zone_factory=Zone, filename=None,
950 allow_include=True, check_origin=True):
951 """Read a master file and build a zone object.
952
953 @param f: file or string. If I{f} is a string, it is treated
954 as the name of a file to open.
955 @param origin: The origin of the zone; if not specified, the first
956 $ORIGIN statement in the master file will determine the origin of the
957 zone.
958 @type origin: dns.name.Name object or string
959 @param rdclass: The zone's rdata class; the default is class IN.
960 @type rdclass: int
961 @param relativize: should names be relativized? The default is True
962 @type relativize: bool
963 @param zone_factory: The zone factory to use
964 @type zone_factory: function returning a Zone
965 @param filename: The filename to emit when describing where an error
966 occurred; the default is '<file>', or the value of I{f} if I{f} is a
967 string.
968 @type filename: string
969 @param allow_include: is $INCLUDE allowed?
970 @type allow_include: bool
971 @param check_origin: should sanity checks of the origin node be done?
972 The default is True.
973 @type check_origin: bool
974 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
975 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
976 @rtype: dns.zone.Zone object
977 """
978
979 if sys.hexversion >= 0x02030000:
980
981 str_type = basestring
982 opts = 'rU'
983 else:
984 str_type = str
985 opts = 'r'
986 if isinstance(f, str_type):
987 if filename is None:
988 filename = f
989 f = file(f, opts)
990 want_close = True
991 else:
992 if filename is None:
993 filename = '<file>'
994 want_close = False
995
996 try:
997 z = from_text(f, origin, rdclass, relativize, zone_factory,
998 filename, allow_include, check_origin)
999 finally:
1000 if want_close:
1001 f.close()
1002 return z
1003
1004 -def from_xfr(xfr, zone_factory=Zone, relativize=True, check_origin=True):
1005 """Convert the output of a zone transfer generator into a zone object.
1006
1007 @param xfr: The xfr generator
1008 @type xfr: generator of dns.message.Message objects
1009 @param relativize: should names be relativized? The default is True.
1010 It is essential that the relativize setting matches the one specified
1011 to dns.query.xfr().
1012 @type relativize: bool
1013 @param check_origin: should sanity checks of the origin node be done?
1014 The default is True.
1015 @type check_origin: bool
1016 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
1017 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
1018 @rtype: dns.zone.Zone object
1019 """
1020
1021 z = None
1022 for r in xfr:
1023 if z is None:
1024 if relativize:
1025 origin = r.origin
1026 else:
1027 origin = r.answer[0].name
1028 rdclass = r.answer[0].rdclass
1029 z = zone_factory(origin, rdclass, relativize=relativize)
1030 for rrset in r.answer:
1031 znode = z.nodes.get(rrset.name)
1032 if not znode:
1033 znode = z.node_factory()
1034 z.nodes[rrset.name] = znode
1035 zrds = znode.find_rdataset(rrset.rdclass, rrset.rdtype,
1036 rrset.covers, True)
1037 zrds.update_ttl(rrset.ttl)
1038 for rd in rrset:
1039 rd.choose_relativity(z.origin, relativize)
1040 zrds.add(rd)
1041 if check_origin:
1042 z.check_origin()
1043 return z
1044